From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/utils/captioncompiler/captioncompiler.cpp | 1176 +-- mp/src/utils/captioncompiler/captioncompiler.vpc | 120 +- mp/src/utils/captioncompiler/cbase.h | 38 +- mp/src/utils/common/ISQLDBReplyTarget.h | 58 +- mp/src/utils/common/MySqlDatabase.cpp | 384 +- mp/src/utils/common/MySqlDatabase.h | 208 +- mp/src/utils/common/bsplib.cpp | 10128 +++++++++---------- mp/src/utils/common/bsplib.h | 808 +- mp/src/utils/common/cmdlib.cpp | 2014 ++-- mp/src/utils/common/cmdlib.h | 354 +- mp/src/utils/common/consolewnd.cpp | 666 +- mp/src/utils/common/consolewnd.h | 90 +- mp/src/utils/common/filesystem_tools.cpp | 418 +- mp/src/utils/common/filesystem_tools.h | 118 +- mp/src/utils/common/map_shared.cpp | 272 +- mp/src/utils/common/map_shared.h | 182 +- mp/src/utils/common/movie.h | 66 +- mp/src/utils/common/mpi_stats.cpp | 1676 +-- mp/src/utils/common/mpi_stats.h | 118 +- mp/src/utils/common/mstristrip.cpp | 1860 ++-- mp/src/utils/common/mstristrip.h | 86 +- mp/src/utils/common/pacifier.cpp | 126 +- mp/src/utils/common/pacifier.h | 46 +- mp/src/utils/common/physdll.cpp | 62 +- mp/src/utils/common/physdll.h | 60 +- mp/src/utils/common/polylib.cpp | 1830 ++-- mp/src/utils/common/polylib.h | 156 +- mp/src/utils/common/qfiles.h | 84 +- mp/src/utils/common/scratchpad_helpers.cpp | 206 +- mp/src/utils/common/scratchpad_helpers.h | 50 +- mp/src/utils/common/scriplib.cpp | 2698 ++--- mp/src/utils/common/scriplib.h | 192 +- mp/src/utils/common/threads.cpp | 514 +- mp/src/utils/common/threads.h | 130 +- mp/src/utils/common/tools_minidump.cpp | 122 +- mp/src/utils/common/tools_minidump.h | 70 +- mp/src/utils/common/utilmatlib.cpp | 368 +- mp/src/utils/common/utilmatlib.h | 82 +- mp/src/utils/common/vmpi_tools_shared.cpp | 748 +- mp/src/utils/common/vmpi_tools_shared.h | 90 +- mp/src/utils/common/wadlib.c | 668 +- mp/src/utils/common/wadlib.h | 92 +- mp/src/utils/glview/glos.h | 42 +- mp/src/utils/glview/glview.cpp | 2854 +++--- mp/src/utils/glview/glview.vpc | 110 +- mp/src/utils/height2normal/height2normal.cpp | 686 +- mp/src/utils/height2normal/height2normal.vpc | 82 +- mp/src/utils/motionmapper/motionmapper.cpp | 6544 ++++++------ mp/src/utils/motionmapper/motionmapper.h | 548 +- mp/src/utils/motionmapper/motionmapper.vpc | 176 +- mp/src/utils/nvtristriplib/nvtristrip.h | 246 +- mp/src/utils/phonemeextractor/extractor_utils.cpp | 54 +- mp/src/utils/phonemeextractor/phonemeextractor.cpp | 2848 +++--- mp/src/utils/phonemeextractor/phonemeextractor.vpc | 166 +- .../phonemeextractor/phonemeextractor_ims.cpp | 2148 ++-- .../phonemeextractor/phonemeextractor_ims.vpc | 196 +- mp/src/utils/phonemeextractor/talkback.h | 1464 +-- mp/src/utils/qc_eyes/QC_Eyes.cpp | 146 +- mp/src/utils/qc_eyes/QC_Eyes.h | 100 +- mp/src/utils/qc_eyes/QC_EyesDlg.cpp | 1410 +-- mp/src/utils/qc_eyes/QC_EyesDlg.h | 268 +- mp/src/utils/qc_eyes/StdAfx.cpp | 18 +- mp/src/utils/qc_eyes/StdAfx.h | 56 +- mp/src/utils/qc_eyes/qc_eyes.vpc | 134 +- mp/src/utils/qc_eyes/resource.h | 156 +- .../utils/serverplugin_sample/serverplugin_bot.cpp | 766 +- .../serverplugin_sample/serverplugin_empty.cpp | 1844 ++-- .../serverplugin_sample/serverplugin_empty.vpc | 124 +- mp/src/utils/smdlexp/smdlexp.cpp | 2192 ++-- mp/src/utils/smdlexp/smedefs.h | 356 +- mp/src/utils/smdlexp/smexprc.h | 56 +- mp/src/utils/tgadiff/tgadiff.cpp | 368 +- mp/src/utils/tgadiff/tgadiff.vpc | 50 +- mp/src/utils/vbsp/boundbox.cpp | 570 +- mp/src/utils/vbsp/boundbox.h | 158 +- mp/src/utils/vbsp/brushbsp.cpp | 2938 +++--- mp/src/utils/vbsp/csg.cpp | 1568 +-- mp/src/utils/vbsp/csg.h | 64 +- mp/src/utils/vbsp/cubemap.cpp | 1992 ++-- mp/src/utils/vbsp/detail.cpp | 1386 +-- mp/src/utils/vbsp/detail.h | 36 +- mp/src/utils/vbsp/detailobjects.cpp | 1932 ++-- mp/src/utils/vbsp/disp_ivp.cpp | 718 +- mp/src/utils/vbsp/disp_ivp.h | 98 +- mp/src/utils/vbsp/disp_vbsp.cpp | 1350 +-- mp/src/utils/vbsp/disp_vbsp.h | 92 +- mp/src/utils/vbsp/faces.cpp | 3618 +++---- mp/src/utils/vbsp/faces.h | 40 +- mp/src/utils/vbsp/glfile.cpp | 438 +- mp/src/utils/vbsp/ivp.cpp | 3312 +++--- mp/src/utils/vbsp/ivp.h | 150 +- mp/src/utils/vbsp/leakfile.cpp | 334 +- mp/src/utils/vbsp/manifest.cpp | 1136 +-- mp/src/utils/vbsp/manifest.h | 146 +- mp/src/utils/vbsp/map.cpp | 6608 ++++++------ mp/src/utils/vbsp/map.h | 36 +- mp/src/utils/vbsp/materialpatch.cpp | 880 +- mp/src/utils/vbsp/materialpatch.h | 120 +- mp/src/utils/vbsp/materialsub.cpp | 178 +- mp/src/utils/vbsp/materialsub.h | 48 +- mp/src/utils/vbsp/nodraw.cpp | 64 +- mp/src/utils/vbsp/normals.cpp | 100 +- mp/src/utils/vbsp/overlay.cpp | 974 +- mp/src/utils/vbsp/portals.cpp | 3368 +++--- mp/src/utils/vbsp/portals.h | 38 +- mp/src/utils/vbsp/prtfile.cpp | 748 +- mp/src/utils/vbsp/staticprop.cpp | 1482 +-- mp/src/utils/vbsp/textures.cpp | 1474 +-- mp/src/utils/vbsp/tree.cpp | 414 +- mp/src/utils/vbsp/vbsp.cpp | 2808 ++--- mp/src/utils/vbsp/vbsp.h | 1314 +-- mp/src/utils/vbsp/vbsp.vpc | 368 +- mp/src/utils/vbsp/worldvertextransitionfixup.cpp | 422 +- mp/src/utils/vbsp/worldvertextransitionfixup.h | 30 +- mp/src/utils/vbsp/writebsp.cpp | 3104 +++--- mp/src/utils/vbsp/writebsp.h | 68 +- mp/src/utils/vice/vice.cpp | 554 +- mp/src/utils/vice/vice.vpc | 82 +- mp/src/utils/vmpi/ichannel.h | 98 +- mp/src/utils/vmpi/imysqlwrapper.h | 230 +- mp/src/utils/vmpi/iphelpers.h | 324 +- mp/src/utils/vmpi/messbuf.h | 104 +- mp/src/utils/vmpi/threadhelpers.h | 220 +- mp/src/utils/vmpi/vmpi.h | 434 +- mp/src/utils/vmpi/vmpi_defs.h | 294 +- mp/src/utils/vmpi/vmpi_dispatch.h | 30 +- mp/src/utils/vmpi/vmpi_distribute_work.h | 178 +- mp/src/utils/vmpi/vmpi_filesystem.h | 106 +- mp/src/utils/vmpi/vmpi_parameters.h | 60 +- mp/src/utils/vrad/disp_vrad.cpp | 664 +- mp/src/utils/vrad/disp_vrad.h | 44 +- mp/src/utils/vrad/iincremental.h | 142 +- mp/src/utils/vrad/imagepacker.cpp | 282 +- mp/src/utils/vrad/imagepacker.h | 102 +- mp/src/utils/vrad/incremental.cpp | 1532 +-- mp/src/utils/vrad/incremental.h | 350 +- mp/src/utils/vrad/leaf_ambient_lighting.cpp | 1416 +-- mp/src/utils/vrad/leaf_ambient_lighting.h | 34 +- mp/src/utils/vrad/lightmap.cpp | 7154 ++++++------- mp/src/utils/vrad/lightmap.h | 282 +- mp/src/utils/vrad/macro_texture.cpp | 332 +- mp/src/utils/vrad/macro_texture.h | 48 +- mp/src/utils/vrad/mpivrad.cpp | 992 +- mp/src/utils/vrad/mpivrad.h | 72 +- mp/src/utils/vrad/origface.cpp | 102 +- mp/src/utils/vrad/radial.cpp | 1764 ++-- mp/src/utils/vrad/radial.h | 152 +- mp/src/utils/vrad/samplehash.cpp | 460 +- mp/src/utils/vrad/trace.cpp | 1306 +-- mp/src/utils/vrad/vismat.cpp | 966 +- mp/src/utils/vrad/vismat.h | 68 +- mp/src/utils/vrad/vrad.cpp | 5872 +++++------ mp/src/utils/vrad/vrad.h | 1220 +-- mp/src/utils/vrad/vrad_dispcoll.cpp | 2158 ++-- mp/src/utils/vrad/vrad_dispcoll.h | 158 +- mp/src/utils/vrad/vrad_dll.vpc | 450 +- mp/src/utils/vrad/vraddetailprops.cpp | 2070 ++-- mp/src/utils/vrad/vraddetailprops.h | 68 +- mp/src/utils/vrad/vraddisps.cpp | 3518 +++---- mp/src/utils/vrad/vraddll.cpp | 486 +- mp/src/utils/vrad/vraddll.h | 68 +- mp/src/utils/vrad/vradstaticprops.cpp | 3830 +++---- mp/src/utils/vrad_launcher/stdafx.cpp | 30 +- mp/src/utils/vrad_launcher/stdafx.h | 64 +- mp/src/utils/vrad_launcher/vrad_launcher.cpp | 290 +- mp/src/utils/vrad_launcher/vrad_launcher.vpc | 98 +- mp/src/utils/vtf2tga/vtf2tga.cpp | 632 +- mp/src/utils/vtf2tga/vtf2tga.vpc | 94 +- mp/src/utils/vtfdiff/vtfdiff.cpp | 876 +- mp/src/utils/vtfdiff/vtfdiff.vpc | 52 +- mp/src/utils/vvis/WaterDist.cpp | 60 +- mp/src/utils/vvis/flow.cpp | 1762 ++-- mp/src/utils/vvis/mpivis.cpp | 1280 +-- mp/src/utils/vvis/mpivis.h | 42 +- mp/src/utils/vvis/vis.h | 250 +- mp/src/utils/vvis/vvis.cpp | 2480 ++--- mp/src/utils/vvis/vvis_dll.vpc | 202 +- mp/src/utils/vvis_launcher/StdAfx.cpp | 30 +- mp/src/utils/vvis_launcher/StdAfx.h | 62 +- mp/src/utils/vvis_launcher/vvis_launcher.cpp | 158 +- mp/src/utils/vvis_launcher/vvis_launcher.vpc | 92 +- 181 files changed, 75797 insertions(+), 75797 deletions(-) (limited to 'mp/src/utils') diff --git a/mp/src/utils/captioncompiler/captioncompiler.cpp b/mp/src/utils/captioncompiler/captioncompiler.cpp index 44e1578f..0fee3529 100644 --- a/mp/src/utils/captioncompiler/captioncompiler.cpp +++ b/mp/src/utils/captioncompiler/captioncompiler.cpp @@ -1,588 +1,588 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: vcd_sound_check.cpp : Defines the entry point for the console application. -// -//===========================================================================// -#include -#include -#include "tier0/dbg.h" -#include "tier1/utldict.h" -#include "filesystem.h" -#include "cmdlib.h" -#include "scriplib.h" -#include "vstdlib/random.h" -#include "tier1/UtlBuffer.h" -#include "pacifier.h" -#include "appframework/tier3app.h" -#include "tier0/icommandline.h" -#include "vgui/IVGui.h" -#include "vgui_controls/controls.h" -#include "vgui/ILocalize.h" -#include "tier1/checksum_crc.h" -#include "tier1/UtlSortVector.h" -#include "tier1/utlmap.h" -#include "captioncompiler.h" - -#include "tier0/fasttimer.h" - -using namespace vgui; - -// #define TESTING 1 - - -bool uselogfile = false; -bool bX360 = false; - -struct AnalysisData -{ - CUtlSymbolTable symbols; -}; - -static AnalysisData g_Analysis; - -IBaseFileSystem *filesystem = NULL; - -static bool spewed = false; - -SpewRetval_t SpewFunc( SpewType_t type, char const *pMsg ) -{ - spewed = true; - - printf( "%s", pMsg ); - OutputDebugString( pMsg ); - - if ( type == SPEW_ERROR ) - { - printf( "\n" ); - OutputDebugString( "\n" ); - } - - return SPEW_CONTINUE; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : depth - -// *fmt - -// ... - -//----------------------------------------------------------------------------- -void vprint( int depth, const char *fmt, ... ) -{ - char string[ 8192 ]; - va_list va; - va_start( va, fmt ); - vsprintf( string, fmt, va ); - va_end( va ); - - FILE *fp = NULL; - - if ( uselogfile ) - { - fp = fopen( "log.txt", "ab" ); - } - - while ( depth-- > 0 ) - { - printf( " " ); - OutputDebugString( " " ); - if ( fp ) - { - fprintf( fp, " " ); - } - } - - ::printf( string ); - OutputDebugString( string ); - - if ( fp ) - { - char *p = string; - while ( *p ) - { - if ( *p == '\n' ) - { - fputc( '\r', fp ); - } - fputc( *p, fp ); - p++; - } - fclose( fp ); - } -} - -void logprint( char const *logfile, const char *fmt, ... ) -{ - char string[ 8192 ]; - va_list va; - va_start( va, fmt ); - vsprintf( string, fmt, va ); - va_end( va ); - - FILE *fp = NULL; - static bool first = true; - if ( first ) - { - first = false; - fp = fopen( logfile, "wb" ); - } - else - { - fp = fopen( logfile, "ab" ); - } - if ( fp ) - { - char *p = string; - while ( *p ) - { - if ( *p == '\n' ) - { - fputc( '\r', fp ); - } - fputc( *p, fp ); - p++; - } - fclose( fp ); - } -} - - -void Con_Printf( const char *fmt, ... ) -{ - va_list args; - static char output[1024]; - - va_start( args, fmt ); - vprintf( fmt, args ); - vsprintf( output, fmt, args ); - - vprint( 0, output ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void printusage( void ) -{ - vprint( 0, "usage: captioncompiler closecaptionfile.txt\n\ - \t-v = verbose output\n\ - \t-l = log to file log.txt\n\ - \ne.g.: kvc -l u:/xbox/game/hl2x/resource/closecaption_english.txt" ); - - // Exit app - exit( 1 ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CheckLogFile( void ) -{ - if ( uselogfile ) - { - _unlink( "log.txt" ); - vprint( 0, " Outputting to log.txt\n" ); - } -} - -void PrintHeader() -{ - vprint( 0, "Valve Software - captioncompiler.exe (%s)\n", __DATE__ ); - vprint( 0, "--- Close Caption File compiler ---\n" ); -} - -//----------------------------------------------------------------------------- -// The application object -//----------------------------------------------------------------------------- -class CCompileCaptionsApp : public CTier3SteamApp -{ - typedef CTier3SteamApp BaseClass; - -public: - // Methods of IApplication - virtual bool Create(); - virtual bool PreInit(); - virtual int Main(); - virtual void PostShutdown(); - virtual void Destroy(); - -private: - // Sets up the search paths - bool SetupSearchPaths(); - - void CompileCaptionFile( char const *infile, char const *outfile ); - void DescribeCaptions( char const *file ); -}; - - -bool CCompileCaptionsApp::Create() -{ - SpewOutputFunc( SpewFunc ); - SpewActivate( "kvc", 2 ); - - AppSystemInfo_t appSystems[] = - { - { "vgui2.dll", VGUI_IVGUI_INTERFACE_VERSION }, - { "", "" } // Required to terminate the list - }; - - return AddSystems( appSystems ); -} - -void CCompileCaptionsApp::Destroy() -{ -} - - -//----------------------------------------------------------------------------- -// Sets up the game path -//----------------------------------------------------------------------------- -bool CCompileCaptionsApp::SetupSearchPaths() -{ - if ( !BaseClass::SetupSearchPaths( NULL, false, true ) ) - return false; - - // Set gamedir. - Q_MakeAbsolutePath( gamedir, sizeof( gamedir ), GetGameInfoPath() ); - Q_AppendSlash( gamedir, sizeof( gamedir ) ); - - return true; -} - - -//----------------------------------------------------------------------------- -// Init, shutdown -//----------------------------------------------------------------------------- -bool CCompileCaptionsApp::PreInit( ) -{ - if ( !BaseClass::PreInit() ) - return false; - - g_pFileSystem = g_pFullFileSystem; - if ( !g_pFileSystem || !g_pVGui || !g_pVGuiLocalize ) - { - Error( "Unable to load required library interface!\n" ); - return false; - } - -// MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false ); - g_pFullFileSystem->SetWarningFunc( Warning ); - - // Add paths... - if ( !SetupSearchPaths() ) - return false; - - return true; -} - -void CCompileCaptionsApp::PostShutdown() -{ - g_pFileSystem = NULL; - BaseClass::PostShutdown(); -} - -void CCompileCaptionsApp::CompileCaptionFile( char const *infile, char const *outfile ) -{ - StringIndex_t maxindex = (StringIndex_t)-1; - int maxunicodesize = 0; - int totalsize = 0; - - int c = 0; - - int curblock = 0; - int usedBytes = 0; - int blockSize = MAX_BLOCK_SIZE; - - int freeSpace = 0; - - CUtlVector< CaptionLookup_t > directory; - CUtlBuffer data; - - CUtlRBTree< unsigned int > hashcollision( 0, 0, DefLessFunc( unsigned int ) ); - - for ( StringIndex_t i = g_pVGuiLocalize->GetFirstStringIndex(); i != INVALID_LOCALIZE_STRING_INDEX; i = g_pVGuiLocalize->GetNextStringIndex( i ), ++c ) - { - char const *entryName = g_pVGuiLocalize->GetNameByIndex( i ); - CaptionLookup_t entry; - entry.SetHash( entryName ); - - // vprint( 0, "%d / %d: %s == %u\n", c, i, g_pVGuiLocalize->GetNameByIndex( i ), entry.hash ); - - if ( hashcollision.Find( entry.hash ) != hashcollision.InvalidIndex() ) - { - Error( "Hash name collision on %s!!!\n", g_pVGuiLocalize->GetNameByIndex( i ) ); - } - - hashcollision.Insert( entry.hash ); - - const wchar_t *text = g_pVGuiLocalize->GetValueByIndex( i ); - if ( verbose ) - { - vprint( 0, "Processing: '%30.30s' = '%S'\n", entryName, text ); - } - int len = text ? ( wcslen( text ) + 1 ) * sizeof( short ) : 0; - if ( len > maxunicodesize ) - { - maxindex = i; - maxunicodesize = len; - } - - if ( len > blockSize ) - { - Error( "Caption text file '%s' contains a single caption '%s' of %d bytes (%d is max), change MAX_BLOCK_SIZE in captioncompiler.h to fix!!!\n", g_pVGuiLocalize->GetNameByIndex( i ), - entryName, len, blockSize ); - } - totalsize += len; - - if ( usedBytes + len >= blockSize ) - { - ++curblock; - - int leftover = ( blockSize - usedBytes ); - - totalsize += leftover; - - freeSpace += leftover; - - while ( --leftover >= 0 ) - { - data.PutChar( 0 ); - } - - usedBytes = len; - entry.offset = 0; - - data.Put( (const void *)text, len ); - } - else - { - entry.offset = usedBytes; - usedBytes += len; - data.Put( (const void *)text, len ); - } - - entry.length = len; - entry.blockNum = curblock; - - directory.AddToTail( entry ); - } - - int leftover = ( blockSize - usedBytes ); - totalsize += leftover; - freeSpace += leftover; - while ( --leftover >= 0 ) - { - data.PutChar( 0 ); - } - - vprint( 0, "Found %i strings in '%s'\n", c, infile ); - - if ( maxindex != INVALID_LOCALIZE_STRING_INDEX ) - { - vprint( 0, "Longest string '%s' = (%i) bytes average(%.3f)\n%", - g_pVGuiLocalize->GetNameByIndex( maxindex ), maxunicodesize, (float)totalsize/(float)c ); - } - - vprint( 0, "%d blocks (%d bytes each), %d bytes wasted (%.3f per block average), total bytes %d\n", - curblock + 1, blockSize, freeSpace, (float)freeSpace/(float)( curblock + 1 ), totalsize ); - - vprint( 0, "directory size %d entries, %d bytes, data size %d bytes\n", - directory.Count(), directory.Count() * sizeof( CaptionLookup_t ), data.TellPut() ); - - CompiledCaptionHeader_t header; - header.magic = COMPILED_CAPTION_FILEID; - header.version = COMPILED_CAPTION_VERSION; - header.numblocks = curblock + 1; - header.blocksize = blockSize; - header.directorysize = directory.Count(); - header.dataoffset = 0; - - // Now write the outfile - CUtlBuffer out; - out.Put( &header, sizeof( header ) ); - out.Put( directory.Base(), directory.Count() * sizeof( CaptionLookup_t ) ); - int curOffset = out.TellPut(); - // Round it up to the next 512 byte boundary - int nBytesDestBuffer = AlignValue( curOffset, 512 ); // align to HD sector - int nPadding = nBytesDestBuffer - curOffset; - while ( --nPadding >= 0 ) - { - out.PutChar( 0 ); - } - out.Put( data.Base(), data.TellPut() ); - - // Write out a corrected header - header.dataoffset = nBytesDestBuffer; - int savePos = out.TellPut(); - out.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); - out.Put( &header, sizeof( header ) ); - out.SeekPut( CUtlBuffer::SEEK_HEAD, savePos ); - - g_pFullFileSystem->WriteFile( outfile, NULL, out ); - - // Jeep: this function no longer exisits - /*if ( bX360 ) - { - UpdateOrCreateCaptionFile_X360( g_pFullFileSystem, outfile, NULL, true ); - }*/ -} - -void CCompileCaptionsApp::DescribeCaptions( char const *file ) -{ - CUtlBuffer buf; - if ( !g_pFullFileSystem->ReadFile( file, NULL, buf ) ) - { - Error( "Unable to read '%s' into buffer\n", file ); - } - - CompiledCaptionHeader_t header; - buf.Get( &header, sizeof( header ) ); - if ( header.magic != COMPILED_CAPTION_FILEID ) - Error( "Invalid file id for %s\n", file ); - if ( header.version != COMPILED_CAPTION_VERSION ) - Error( "Invalid file version for %s\n", file ); - - // Read the directory - CUtlSortVector< CaptionLookup_t, CCaptionLookupLess > directory; - directory.EnsureCapacity( header.directorysize ); - directory.CopyArray( (const CaptionLookup_t *)buf.PeekGet(), header.directorysize ); - directory.RedoSort( true ); - buf.SeekGet( CUtlBuffer::SEEK_HEAD, header.dataoffset ); - - int i; - CUtlVector< CaptionBlock_t > blocks; - for ( i = 0; i < header.numblocks; ++i ) - { - CaptionBlock_t& newBlock = blocks[ blocks.AddToTail() ]; - Q_memset( newBlock.data, 0, sizeof( newBlock.data ) ); - buf.Get( newBlock.data, header.blocksize ); - } - - CUtlMap< unsigned int, StringIndex_t > inverseMap( 0, 0, DefLessFunc( unsigned int ) ); - for ( StringIndex_t idx = g_pVGuiLocalize->GetFirstStringIndex(); idx != INVALID_LOCALIZE_STRING_INDEX; idx = g_pVGuiLocalize->GetNextStringIndex( idx ) ) - { - const char *name = g_pVGuiLocalize->GetNameByIndex( idx ); - CaptionLookup_t dummy; - dummy.SetHash( name ); - - inverseMap.Insert( dummy.hash, idx ); - } - - // Now print everything out... - for ( i = 0; i < header.directorysize; ++i ) - { - const CaptionLookup_t& entry = directory[ i ]; - char const *name = g_pVGuiLocalize->GetNameByIndex( inverseMap.Element( inverseMap.Find( entry.hash ) ) ); - const CaptionBlock_t& block = blocks[ entry.blockNum ]; - const wchar_t *data = (const wchar_t *)&block.data[ entry.offset ]; - wchar_t *temp = ( wchar_t * )_alloca( entry.length * sizeof( short ) ); - wcsncpy( temp, data, ( entry.length / sizeof( short ) ) - 1 ); - - vprint( 0, "%3.3d: (%40.40s) hash(%15.15u), block(%4.4d), offset(%4.4d), len(%4.4d) %S\n", - i, name, entry.hash, entry.blockNum, entry.offset, entry.length, temp ); - } -} - -//----------------------------------------------------------------------------- -// main application -//----------------------------------------------------------------------------- -int CCompileCaptionsApp::Main() -{ - CUtlVector< CUtlSymbol > worklist; - - int i = 1; - for ( i ; iParmCount() ; i++) - { - if ( CommandLine()->GetParm( i )[ 0 ] == '-' ) - { - switch( CommandLine()->GetParm( i )[ 1 ] ) - { - case 'l': - uselogfile = true; - break; - case 'v': - verbose = true; - break; - case 'x': - bX360 = true; - break; - case 'g': // -game - ++i; - break; - default: - printusage(); - break; - } - } - else if ( i != 0 ) - { - char fn[ 512 ]; - Q_strncpy( fn, CommandLine()->GetParm( i ), sizeof( fn ) ); - Q_FixSlashes( fn ); - Q_strlower( fn ); - - CUtlSymbol sym; - sym = fn; - worklist.AddToTail( sym ); - } - } - - if ( CommandLine()->ParmCount() < 2 || ( i != CommandLine()->ParmCount() ) || worklist.Count() != 1 ) - { - PrintHeader(); - printusage(); - } - - CheckLogFile(); - - PrintHeader(); - - char binaries[MAX_PATH]; - Q_strncpy( binaries, gamedir, MAX_PATH ); - Q_StripTrailingSlash( binaries ); - Q_strncat( binaries, "/../bin", MAX_PATH, MAX_PATH ); - - char outfile[ 512 ]; - if ( Q_stristr( worklist[ worklist.Count() - 1 ].String(), gamedir ) ) - { - Q_strncpy( outfile, &worklist[ worklist.Count() - 1 ].String()[ Q_strlen( gamedir ) ] , sizeof( outfile ) ); - } - else - { - Q_snprintf( outfile, sizeof( outfile ), "resource\\%s", worklist[ worklist.Count() - 1 ].String() ); - } - - char infile[ 512 ]; - Q_strncpy( infile, outfile, sizeof( infile ) ); - - Q_SetExtension( outfile, ".dat", sizeof( outfile ) ); - - vprint( 0, "gamedir[ %s ]\n", gamedir ); - vprint( 0, "infile[ %s ]\n", infile ); - vprint( 0, "outfile[ %s ]\n", outfile ); - - g_pFullFileSystem->AddSearchPath( binaries, "EXECUTABLE_PATH" ); - - if ( !g_pVGuiLocalize->AddFile( infile, "MOD", false ) ) - { - Error( "Unable to add localization file '%s'\n", infile ); - } - - vprint( 0, " Compiling Captions for '%s'...\n", infile ); - - CompileCaptionFile( infile, outfile ); - - if ( verbose ) - { - DescribeCaptions( outfile ); - } - - g_pVGuiLocalize->RemoveAll(); - - return 0; -} - - -//----------------------------------------------------------------------------- -// Purpose: Main entry point -//----------------------------------------------------------------------------- -DEFINE_CONSOLE_STEAM_APPLICATION_OBJECT( CCompileCaptionsApp ) +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: vcd_sound_check.cpp : Defines the entry point for the console application. +// +//===========================================================================// +#include +#include +#include "tier0/dbg.h" +#include "tier1/utldict.h" +#include "filesystem.h" +#include "cmdlib.h" +#include "scriplib.h" +#include "vstdlib/random.h" +#include "tier1/UtlBuffer.h" +#include "pacifier.h" +#include "appframework/tier3app.h" +#include "tier0/icommandline.h" +#include "vgui/IVGui.h" +#include "vgui_controls/controls.h" +#include "vgui/ILocalize.h" +#include "tier1/checksum_crc.h" +#include "tier1/UtlSortVector.h" +#include "tier1/utlmap.h" +#include "captioncompiler.h" + +#include "tier0/fasttimer.h" + +using namespace vgui; + +// #define TESTING 1 + + +bool uselogfile = false; +bool bX360 = false; + +struct AnalysisData +{ + CUtlSymbolTable symbols; +}; + +static AnalysisData g_Analysis; + +IBaseFileSystem *filesystem = NULL; + +static bool spewed = false; + +SpewRetval_t SpewFunc( SpewType_t type, char const *pMsg ) +{ + spewed = true; + + printf( "%s", pMsg ); + OutputDebugString( pMsg ); + + if ( type == SPEW_ERROR ) + { + printf( "\n" ); + OutputDebugString( "\n" ); + } + + return SPEW_CONTINUE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : depth - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void vprint( int depth, const char *fmt, ... ) +{ + char string[ 8192 ]; + va_list va; + va_start( va, fmt ); + vsprintf( string, fmt, va ); + va_end( va ); + + FILE *fp = NULL; + + if ( uselogfile ) + { + fp = fopen( "log.txt", "ab" ); + } + + while ( depth-- > 0 ) + { + printf( " " ); + OutputDebugString( " " ); + if ( fp ) + { + fprintf( fp, " " ); + } + } + + ::printf( string ); + OutputDebugString( string ); + + if ( fp ) + { + char *p = string; + while ( *p ) + { + if ( *p == '\n' ) + { + fputc( '\r', fp ); + } + fputc( *p, fp ); + p++; + } + fclose( fp ); + } +} + +void logprint( char const *logfile, const char *fmt, ... ) +{ + char string[ 8192 ]; + va_list va; + va_start( va, fmt ); + vsprintf( string, fmt, va ); + va_end( va ); + + FILE *fp = NULL; + static bool first = true; + if ( first ) + { + first = false; + fp = fopen( logfile, "wb" ); + } + else + { + fp = fopen( logfile, "ab" ); + } + if ( fp ) + { + char *p = string; + while ( *p ) + { + if ( *p == '\n' ) + { + fputc( '\r', fp ); + } + fputc( *p, fp ); + p++; + } + fclose( fp ); + } +} + + +void Con_Printf( const char *fmt, ... ) +{ + va_list args; + static char output[1024]; + + va_start( args, fmt ); + vprintf( fmt, args ); + vsprintf( output, fmt, args ); + + vprint( 0, output ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void printusage( void ) +{ + vprint( 0, "usage: captioncompiler closecaptionfile.txt\n\ + \t-v = verbose output\n\ + \t-l = log to file log.txt\n\ + \ne.g.: kvc -l u:/xbox/game/hl2x/resource/closecaption_english.txt" ); + + // Exit app + exit( 1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CheckLogFile( void ) +{ + if ( uselogfile ) + { + _unlink( "log.txt" ); + vprint( 0, " Outputting to log.txt\n" ); + } +} + +void PrintHeader() +{ + vprint( 0, "Valve Software - captioncompiler.exe (%s)\n", __DATE__ ); + vprint( 0, "--- Close Caption File compiler ---\n" ); +} + +//----------------------------------------------------------------------------- +// The application object +//----------------------------------------------------------------------------- +class CCompileCaptionsApp : public CTier3SteamApp +{ + typedef CTier3SteamApp BaseClass; + +public: + // Methods of IApplication + virtual bool Create(); + virtual bool PreInit(); + virtual int Main(); + virtual void PostShutdown(); + virtual void Destroy(); + +private: + // Sets up the search paths + bool SetupSearchPaths(); + + void CompileCaptionFile( char const *infile, char const *outfile ); + void DescribeCaptions( char const *file ); +}; + + +bool CCompileCaptionsApp::Create() +{ + SpewOutputFunc( SpewFunc ); + SpewActivate( "kvc", 2 ); + + AppSystemInfo_t appSystems[] = + { + { "vgui2.dll", VGUI_IVGUI_INTERFACE_VERSION }, + { "", "" } // Required to terminate the list + }; + + return AddSystems( appSystems ); +} + +void CCompileCaptionsApp::Destroy() +{ +} + + +//----------------------------------------------------------------------------- +// Sets up the game path +//----------------------------------------------------------------------------- +bool CCompileCaptionsApp::SetupSearchPaths() +{ + if ( !BaseClass::SetupSearchPaths( NULL, false, true ) ) + return false; + + // Set gamedir. + Q_MakeAbsolutePath( gamedir, sizeof( gamedir ), GetGameInfoPath() ); + Q_AppendSlash( gamedir, sizeof( gamedir ) ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Init, shutdown +//----------------------------------------------------------------------------- +bool CCompileCaptionsApp::PreInit( ) +{ + if ( !BaseClass::PreInit() ) + return false; + + g_pFileSystem = g_pFullFileSystem; + if ( !g_pFileSystem || !g_pVGui || !g_pVGuiLocalize ) + { + Error( "Unable to load required library interface!\n" ); + return false; + } + +// MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false ); + g_pFullFileSystem->SetWarningFunc( Warning ); + + // Add paths... + if ( !SetupSearchPaths() ) + return false; + + return true; +} + +void CCompileCaptionsApp::PostShutdown() +{ + g_pFileSystem = NULL; + BaseClass::PostShutdown(); +} + +void CCompileCaptionsApp::CompileCaptionFile( char const *infile, char const *outfile ) +{ + StringIndex_t maxindex = (StringIndex_t)-1; + int maxunicodesize = 0; + int totalsize = 0; + + int c = 0; + + int curblock = 0; + int usedBytes = 0; + int blockSize = MAX_BLOCK_SIZE; + + int freeSpace = 0; + + CUtlVector< CaptionLookup_t > directory; + CUtlBuffer data; + + CUtlRBTree< unsigned int > hashcollision( 0, 0, DefLessFunc( unsigned int ) ); + + for ( StringIndex_t i = g_pVGuiLocalize->GetFirstStringIndex(); i != INVALID_LOCALIZE_STRING_INDEX; i = g_pVGuiLocalize->GetNextStringIndex( i ), ++c ) + { + char const *entryName = g_pVGuiLocalize->GetNameByIndex( i ); + CaptionLookup_t entry; + entry.SetHash( entryName ); + + // vprint( 0, "%d / %d: %s == %u\n", c, i, g_pVGuiLocalize->GetNameByIndex( i ), entry.hash ); + + if ( hashcollision.Find( entry.hash ) != hashcollision.InvalidIndex() ) + { + Error( "Hash name collision on %s!!!\n", g_pVGuiLocalize->GetNameByIndex( i ) ); + } + + hashcollision.Insert( entry.hash ); + + const wchar_t *text = g_pVGuiLocalize->GetValueByIndex( i ); + if ( verbose ) + { + vprint( 0, "Processing: '%30.30s' = '%S'\n", entryName, text ); + } + int len = text ? ( wcslen( text ) + 1 ) * sizeof( short ) : 0; + if ( len > maxunicodesize ) + { + maxindex = i; + maxunicodesize = len; + } + + if ( len > blockSize ) + { + Error( "Caption text file '%s' contains a single caption '%s' of %d bytes (%d is max), change MAX_BLOCK_SIZE in captioncompiler.h to fix!!!\n", g_pVGuiLocalize->GetNameByIndex( i ), + entryName, len, blockSize ); + } + totalsize += len; + + if ( usedBytes + len >= blockSize ) + { + ++curblock; + + int leftover = ( blockSize - usedBytes ); + + totalsize += leftover; + + freeSpace += leftover; + + while ( --leftover >= 0 ) + { + data.PutChar( 0 ); + } + + usedBytes = len; + entry.offset = 0; + + data.Put( (const void *)text, len ); + } + else + { + entry.offset = usedBytes; + usedBytes += len; + data.Put( (const void *)text, len ); + } + + entry.length = len; + entry.blockNum = curblock; + + directory.AddToTail( entry ); + } + + int leftover = ( blockSize - usedBytes ); + totalsize += leftover; + freeSpace += leftover; + while ( --leftover >= 0 ) + { + data.PutChar( 0 ); + } + + vprint( 0, "Found %i strings in '%s'\n", c, infile ); + + if ( maxindex != INVALID_LOCALIZE_STRING_INDEX ) + { + vprint( 0, "Longest string '%s' = (%i) bytes average(%.3f)\n%", + g_pVGuiLocalize->GetNameByIndex( maxindex ), maxunicodesize, (float)totalsize/(float)c ); + } + + vprint( 0, "%d blocks (%d bytes each), %d bytes wasted (%.3f per block average), total bytes %d\n", + curblock + 1, blockSize, freeSpace, (float)freeSpace/(float)( curblock + 1 ), totalsize ); + + vprint( 0, "directory size %d entries, %d bytes, data size %d bytes\n", + directory.Count(), directory.Count() * sizeof( CaptionLookup_t ), data.TellPut() ); + + CompiledCaptionHeader_t header; + header.magic = COMPILED_CAPTION_FILEID; + header.version = COMPILED_CAPTION_VERSION; + header.numblocks = curblock + 1; + header.blocksize = blockSize; + header.directorysize = directory.Count(); + header.dataoffset = 0; + + // Now write the outfile + CUtlBuffer out; + out.Put( &header, sizeof( header ) ); + out.Put( directory.Base(), directory.Count() * sizeof( CaptionLookup_t ) ); + int curOffset = out.TellPut(); + // Round it up to the next 512 byte boundary + int nBytesDestBuffer = AlignValue( curOffset, 512 ); // align to HD sector + int nPadding = nBytesDestBuffer - curOffset; + while ( --nPadding >= 0 ) + { + out.PutChar( 0 ); + } + out.Put( data.Base(), data.TellPut() ); + + // Write out a corrected header + header.dataoffset = nBytesDestBuffer; + int savePos = out.TellPut(); + out.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); + out.Put( &header, sizeof( header ) ); + out.SeekPut( CUtlBuffer::SEEK_HEAD, savePos ); + + g_pFullFileSystem->WriteFile( outfile, NULL, out ); + + // Jeep: this function no longer exisits + /*if ( bX360 ) + { + UpdateOrCreateCaptionFile_X360( g_pFullFileSystem, outfile, NULL, true ); + }*/ +} + +void CCompileCaptionsApp::DescribeCaptions( char const *file ) +{ + CUtlBuffer buf; + if ( !g_pFullFileSystem->ReadFile( file, NULL, buf ) ) + { + Error( "Unable to read '%s' into buffer\n", file ); + } + + CompiledCaptionHeader_t header; + buf.Get( &header, sizeof( header ) ); + if ( header.magic != COMPILED_CAPTION_FILEID ) + Error( "Invalid file id for %s\n", file ); + if ( header.version != COMPILED_CAPTION_VERSION ) + Error( "Invalid file version for %s\n", file ); + + // Read the directory + CUtlSortVector< CaptionLookup_t, CCaptionLookupLess > directory; + directory.EnsureCapacity( header.directorysize ); + directory.CopyArray( (const CaptionLookup_t *)buf.PeekGet(), header.directorysize ); + directory.RedoSort( true ); + buf.SeekGet( CUtlBuffer::SEEK_HEAD, header.dataoffset ); + + int i; + CUtlVector< CaptionBlock_t > blocks; + for ( i = 0; i < header.numblocks; ++i ) + { + CaptionBlock_t& newBlock = blocks[ blocks.AddToTail() ]; + Q_memset( newBlock.data, 0, sizeof( newBlock.data ) ); + buf.Get( newBlock.data, header.blocksize ); + } + + CUtlMap< unsigned int, StringIndex_t > inverseMap( 0, 0, DefLessFunc( unsigned int ) ); + for ( StringIndex_t idx = g_pVGuiLocalize->GetFirstStringIndex(); idx != INVALID_LOCALIZE_STRING_INDEX; idx = g_pVGuiLocalize->GetNextStringIndex( idx ) ) + { + const char *name = g_pVGuiLocalize->GetNameByIndex( idx ); + CaptionLookup_t dummy; + dummy.SetHash( name ); + + inverseMap.Insert( dummy.hash, idx ); + } + + // Now print everything out... + for ( i = 0; i < header.directorysize; ++i ) + { + const CaptionLookup_t& entry = directory[ i ]; + char const *name = g_pVGuiLocalize->GetNameByIndex( inverseMap.Element( inverseMap.Find( entry.hash ) ) ); + const CaptionBlock_t& block = blocks[ entry.blockNum ]; + const wchar_t *data = (const wchar_t *)&block.data[ entry.offset ]; + wchar_t *temp = ( wchar_t * )_alloca( entry.length * sizeof( short ) ); + wcsncpy( temp, data, ( entry.length / sizeof( short ) ) - 1 ); + + vprint( 0, "%3.3d: (%40.40s) hash(%15.15u), block(%4.4d), offset(%4.4d), len(%4.4d) %S\n", + i, name, entry.hash, entry.blockNum, entry.offset, entry.length, temp ); + } +} + +//----------------------------------------------------------------------------- +// main application +//----------------------------------------------------------------------------- +int CCompileCaptionsApp::Main() +{ + CUtlVector< CUtlSymbol > worklist; + + int i = 1; + for ( i ; iParmCount() ; i++) + { + if ( CommandLine()->GetParm( i )[ 0 ] == '-' ) + { + switch( CommandLine()->GetParm( i )[ 1 ] ) + { + case 'l': + uselogfile = true; + break; + case 'v': + verbose = true; + break; + case 'x': + bX360 = true; + break; + case 'g': // -game + ++i; + break; + default: + printusage(); + break; + } + } + else if ( i != 0 ) + { + char fn[ 512 ]; + Q_strncpy( fn, CommandLine()->GetParm( i ), sizeof( fn ) ); + Q_FixSlashes( fn ); + Q_strlower( fn ); + + CUtlSymbol sym; + sym = fn; + worklist.AddToTail( sym ); + } + } + + if ( CommandLine()->ParmCount() < 2 || ( i != CommandLine()->ParmCount() ) || worklist.Count() != 1 ) + { + PrintHeader(); + printusage(); + } + + CheckLogFile(); + + PrintHeader(); + + char binaries[MAX_PATH]; + Q_strncpy( binaries, gamedir, MAX_PATH ); + Q_StripTrailingSlash( binaries ); + Q_strncat( binaries, "/../bin", MAX_PATH, MAX_PATH ); + + char outfile[ 512 ]; + if ( Q_stristr( worklist[ worklist.Count() - 1 ].String(), gamedir ) ) + { + Q_strncpy( outfile, &worklist[ worklist.Count() - 1 ].String()[ Q_strlen( gamedir ) ] , sizeof( outfile ) ); + } + else + { + Q_snprintf( outfile, sizeof( outfile ), "resource\\%s", worklist[ worklist.Count() - 1 ].String() ); + } + + char infile[ 512 ]; + Q_strncpy( infile, outfile, sizeof( infile ) ); + + Q_SetExtension( outfile, ".dat", sizeof( outfile ) ); + + vprint( 0, "gamedir[ %s ]\n", gamedir ); + vprint( 0, "infile[ %s ]\n", infile ); + vprint( 0, "outfile[ %s ]\n", outfile ); + + g_pFullFileSystem->AddSearchPath( binaries, "EXECUTABLE_PATH" ); + + if ( !g_pVGuiLocalize->AddFile( infile, "MOD", false ) ) + { + Error( "Unable to add localization file '%s'\n", infile ); + } + + vprint( 0, " Compiling Captions for '%s'...\n", infile ); + + CompileCaptionFile( infile, outfile ); + + if ( verbose ) + { + DescribeCaptions( outfile ); + } + + g_pVGuiLocalize->RemoveAll(); + + return 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Main entry point +//----------------------------------------------------------------------------- +DEFINE_CONSOLE_STEAM_APPLICATION_OBJECT( CCompileCaptionsApp ) diff --git a/mp/src/utils/captioncompiler/captioncompiler.vpc b/mp/src/utils/captioncompiler/captioncompiler.vpc index a6af7a7b..da5a3b4e 100644 --- a/mp/src/utils/captioncompiler/captioncompiler.vpc +++ b/mp/src/utils/captioncompiler/captioncompiler.vpc @@ -1,60 +1,60 @@ -//----------------------------------------------------------------------------- -// CAPTIONCOMPILER.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" - -$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" - -$Configuration -{ - $Compiler - { - $AdditionalIncludeDirectories "$BASE,..\common,$SRCDIR\game\shared,.\" - $PreprocessorDefinitions "$BASE;captioncompiler" - } -} - -$Project "Captioncompiler" -{ - $Folder "Source Files" - { - $File "captioncompiler.cpp" - $File "$SRCDIR\common\compiledcaptionswap.cpp" - $File "..\common\filesystem_tools.cpp" - } - - $Folder "Header Files" - { - $File "cbase.h" - $File "..\common\filesystem_tools.h" - } - - $Folder "Shared Code" - { - $File "..\common\cmdlib.cpp" - $File "..\common\cmdlib.h" - $File "$SRCDIR\public\filesystem_helpers.cpp" - $File "$SRCDIR\public\filesystem_helpers.h" - $File "$SRCDIR\public\filesystem_init.cpp" - $File "$SRCDIR\public\filesystem_init.h" - $File "$SRCDIR\public\mathlib\mathlib.h" - $File "..\common\pacifier.cpp" - $File "..\common\pacifier.h" - $File "..\common\scriplib.cpp" - $File "..\common\scriplib.h" - $File "$SRCDIR\public\stringregistry.cpp" - $File "$SRCDIR\public\stringregistry.h" - } - - $Folder "Link Libraries" - { - $Lib appframework - $Lib mathlib - $Lib tier2 - $Lib tier3 - } -} +//----------------------------------------------------------------------------- +// CAPTIONCOMPILER.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\common,$SRCDIR\game\shared,.\" + $PreprocessorDefinitions "$BASE;captioncompiler" + } +} + +$Project "Captioncompiler" +{ + $Folder "Source Files" + { + $File "captioncompiler.cpp" + $File "$SRCDIR\common\compiledcaptionswap.cpp" + $File "..\common\filesystem_tools.cpp" + } + + $Folder "Header Files" + { + $File "cbase.h" + $File "..\common\filesystem_tools.h" + } + + $Folder "Shared Code" + { + $File "..\common\cmdlib.cpp" + $File "..\common\cmdlib.h" + $File "$SRCDIR\public\filesystem_helpers.cpp" + $File "$SRCDIR\public\filesystem_helpers.h" + $File "$SRCDIR\public\filesystem_init.cpp" + $File "$SRCDIR\public\filesystem_init.h" + $File "$SRCDIR\public\mathlib\mathlib.h" + $File "..\common\pacifier.cpp" + $File "..\common\pacifier.h" + $File "..\common\scriplib.cpp" + $File "..\common\scriplib.h" + $File "$SRCDIR\public\stringregistry.cpp" + $File "$SRCDIR\public\stringregistry.h" + } + + $Folder "Link Libraries" + { + $Lib appframework + $Lib mathlib + $Lib tier2 + $Lib tier3 + } +} diff --git a/mp/src/utils/captioncompiler/cbase.h b/mp/src/utils/captioncompiler/cbase.h index 378d503f..42d73c93 100644 --- a/mp/src/utils/captioncompiler/cbase.h +++ b/mp/src/utils/captioncompiler/cbase.h @@ -1,19 +1,19 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//============================================================================= - -#ifndef CBASE_H -#define CBASE_H -#ifdef _WIN32 -#pragma once -#endif - -#include "basetypes.h" - -// This is just a dummy file to make this tool compile -#include "ai_activity.h" -#include "utlvector.h" - -#endif // CBASE_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef CBASE_H +#define CBASE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basetypes.h" + +// This is just a dummy file to make this tool compile +#include "ai_activity.h" +#include "utlvector.h" + +#endif // CBASE_H diff --git a/mp/src/utils/common/ISQLDBReplyTarget.h b/mp/src/utils/common/ISQLDBReplyTarget.h index 31406368..9049a5c7 100644 --- a/mp/src/utils/common/ISQLDBReplyTarget.h +++ b/mp/src/utils/common/ISQLDBReplyTarget.h @@ -1,29 +1,29 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef ISQLDLREPLYTARGET_H -#define ISQLDLREPLYTARGET_H -#ifdef _WIN32 -#pragma once -#endif - -//----------------------------------------------------------------------------- -// Purpose: Interface to handle results of SQL queries -//----------------------------------------------------------------------------- -class ISQLDBReplyTarget -{ -public: - // handles a response from the database - virtual void SQLDBResponse(int cmdID, int returnState, int returnVal, void *data) = 0; - - // called from a seperate thread; tells the reply target that a message is waiting for it - virtual void WakeUp() = 0; - -}; - - -#endif // ISQLDLREPLYTARGET_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ISQLDLREPLYTARGET_H +#define ISQLDLREPLYTARGET_H +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: Interface to handle results of SQL queries +//----------------------------------------------------------------------------- +class ISQLDBReplyTarget +{ +public: + // handles a response from the database + virtual void SQLDBResponse(int cmdID, int returnState, int returnVal, void *data) = 0; + + // called from a seperate thread; tells the reply target that a message is waiting for it + virtual void WakeUp() = 0; + +}; + + +#endif // ISQLDLREPLYTARGET_H diff --git a/mp/src/utils/common/MySqlDatabase.cpp b/mp/src/utils/common/MySqlDatabase.cpp index 46d8a4b9..2558ba08 100644 --- a/mp/src/utils/common/MySqlDatabase.cpp +++ b/mp/src/utils/common/MySqlDatabase.cpp @@ -1,192 +1,192 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "MySqlDatabase.h" - -//----------------------------------------------------------------------------- -// Purpose: Constructor -//----------------------------------------------------------------------------- -CMySqlDatabase::CMySqlDatabase() -{ -} - -//----------------------------------------------------------------------------- -// Purpose: Destructor -// blocks until db process thread has stopped -//----------------------------------------------------------------------------- -CMySqlDatabase::~CMySqlDatabase() -{ - // flag the thread to stop - m_bRunThread = false; - - // pulse the thread to make it run - ::SetEvent(m_hEvent); - - // make sure it's done - ::EnterCriticalSection(&m_csThread); - ::LeaveCriticalSection(&m_csThread); -} - -//----------------------------------------------------------------------------- -// Purpose: Thread access function -//----------------------------------------------------------------------------- -static DWORD WINAPI staticThreadFunc(void *param) -{ - ((CMySqlDatabase *)param)->RunThread(); - return 0; -} - -//----------------------------------------------------------------------------- -// Purpose: Establishes connection to the database and sets up this object to handle db command -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CMySqlDatabase::Initialize() -{ - // prepare critical sections - //!! need to download SDK and replace these with InitializeCriticalSectionAndSpinCount() calls - ::InitializeCriticalSection(&m_csThread); - ::InitializeCriticalSection(&m_csInQueue); - ::InitializeCriticalSection(&m_csOutQueue); - ::InitializeCriticalSection(&m_csDBAccess); - - // initialize wait calls - m_hEvent = ::CreateEvent(NULL, false, true, NULL); - - // start the DB-access thread - m_bRunThread = true; - - unsigned long threadID; - ::CreateThread(NULL, 0, staticThreadFunc, this, 0, &threadID); - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Main thread loop -//----------------------------------------------------------------------------- -void CMySqlDatabase::RunThread() -{ - ::EnterCriticalSection(&m_csThread); - while (m_bRunThread) - { - if (m_InQueue.Count() > 0) - { - // get a dispatched DB request - ::EnterCriticalSection(&m_csInQueue); - - // pop the front of the queue - int headIndex = m_InQueue.Head(); - msg_t msg = m_InQueue[headIndex]; - m_InQueue.Remove(headIndex); - - ::LeaveCriticalSection(&m_csInQueue); - - ::EnterCriticalSection(&m_csDBAccess); - - // run sqldb command - msg.result = msg.cmd->RunCommand(); - - ::LeaveCriticalSection(&m_csDBAccess); - - if (msg.replyTarget) - { - // put the results in the outgoing queue - ::EnterCriticalSection(&m_csOutQueue); - m_OutQueue.AddToTail(msg); - ::LeaveCriticalSection(&m_csOutQueue); - - // wake up out queue - msg.replyTarget->WakeUp(); - } - else - { - // there is no return data from the call, so kill the object now - msg.cmd->deleteThis(); - } - } - else - { - // nothing in incoming queue, so wait until we get the signal - ::WaitForSingleObject(m_hEvent, INFINITE); - } - - // check the size of the outqueue; if it's getting too big, sleep to let the main thread catch up - if (m_OutQueue.Count() > 50) - { - ::Sleep(2); - } - } - ::LeaveCriticalSection(&m_csThread); -} - -//----------------------------------------------------------------------------- -// Purpose: Adds a database command to the queue, and wakes the db thread -//----------------------------------------------------------------------------- -void CMySqlDatabase::AddCommandToQueue(ISQLDBCommand *cmd, ISQLDBReplyTarget *replyTarget, int returnState) -{ - ::EnterCriticalSection(&m_csInQueue); - - // add to the queue - msg_t msg = { cmd, replyTarget, 0, returnState }; - m_InQueue.AddToTail(msg); - - ::LeaveCriticalSection(&m_csInQueue); - - // signal the thread to start running - ::SetEvent(m_hEvent); -} - -//----------------------------------------------------------------------------- -// Purpose: Dispatches responses to SQLDB queries -//----------------------------------------------------------------------------- -bool CMySqlDatabase::RunFrame() -{ - bool doneWork = false; - - while (m_OutQueue.Count() > 0) - { - ::EnterCriticalSection(&m_csOutQueue); - - // pop the first item in the queue - int headIndex = m_OutQueue.Head(); - msg_t msg = m_OutQueue[headIndex]; - m_OutQueue.Remove(headIndex); - - ::LeaveCriticalSection(&m_csOutQueue); - - // run result - if (msg.replyTarget) - { - msg.replyTarget->SQLDBResponse(msg.cmd->GetID(), msg.returnState, msg.result, msg.cmd->GetReturnData()); - - // kill command - // it would be a good optimization to be able to reuse these - msg.cmd->deleteThis(); - } - - doneWork = true; - } - - return doneWork; -} - -//----------------------------------------------------------------------------- -// Purpose: load info - returns the number of sql db queries waiting to be processed -//----------------------------------------------------------------------------- -int CMySqlDatabase::QueriesInOutQueue() -{ - // the queue names are from the DB point of view, not the server - thus the reversal - return m_InQueue.Count(); -} - -//----------------------------------------------------------------------------- -// Purpose: number of queries finished processing, waiting to be responded to -//----------------------------------------------------------------------------- -int CMySqlDatabase::QueriesInFinishedQueue() -{ - return m_OutQueue.Count(); -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "MySqlDatabase.h" + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CMySqlDatabase::CMySqlDatabase() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +// blocks until db process thread has stopped +//----------------------------------------------------------------------------- +CMySqlDatabase::~CMySqlDatabase() +{ + // flag the thread to stop + m_bRunThread = false; + + // pulse the thread to make it run + ::SetEvent(m_hEvent); + + // make sure it's done + ::EnterCriticalSection(&m_csThread); + ::LeaveCriticalSection(&m_csThread); +} + +//----------------------------------------------------------------------------- +// Purpose: Thread access function +//----------------------------------------------------------------------------- +static DWORD WINAPI staticThreadFunc(void *param) +{ + ((CMySqlDatabase *)param)->RunThread(); + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Establishes connection to the database and sets up this object to handle db command +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CMySqlDatabase::Initialize() +{ + // prepare critical sections + //!! need to download SDK and replace these with InitializeCriticalSectionAndSpinCount() calls + ::InitializeCriticalSection(&m_csThread); + ::InitializeCriticalSection(&m_csInQueue); + ::InitializeCriticalSection(&m_csOutQueue); + ::InitializeCriticalSection(&m_csDBAccess); + + // initialize wait calls + m_hEvent = ::CreateEvent(NULL, false, true, NULL); + + // start the DB-access thread + m_bRunThread = true; + + unsigned long threadID; + ::CreateThread(NULL, 0, staticThreadFunc, this, 0, &threadID); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Main thread loop +//----------------------------------------------------------------------------- +void CMySqlDatabase::RunThread() +{ + ::EnterCriticalSection(&m_csThread); + while (m_bRunThread) + { + if (m_InQueue.Count() > 0) + { + // get a dispatched DB request + ::EnterCriticalSection(&m_csInQueue); + + // pop the front of the queue + int headIndex = m_InQueue.Head(); + msg_t msg = m_InQueue[headIndex]; + m_InQueue.Remove(headIndex); + + ::LeaveCriticalSection(&m_csInQueue); + + ::EnterCriticalSection(&m_csDBAccess); + + // run sqldb command + msg.result = msg.cmd->RunCommand(); + + ::LeaveCriticalSection(&m_csDBAccess); + + if (msg.replyTarget) + { + // put the results in the outgoing queue + ::EnterCriticalSection(&m_csOutQueue); + m_OutQueue.AddToTail(msg); + ::LeaveCriticalSection(&m_csOutQueue); + + // wake up out queue + msg.replyTarget->WakeUp(); + } + else + { + // there is no return data from the call, so kill the object now + msg.cmd->deleteThis(); + } + } + else + { + // nothing in incoming queue, so wait until we get the signal + ::WaitForSingleObject(m_hEvent, INFINITE); + } + + // check the size of the outqueue; if it's getting too big, sleep to let the main thread catch up + if (m_OutQueue.Count() > 50) + { + ::Sleep(2); + } + } + ::LeaveCriticalSection(&m_csThread); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a database command to the queue, and wakes the db thread +//----------------------------------------------------------------------------- +void CMySqlDatabase::AddCommandToQueue(ISQLDBCommand *cmd, ISQLDBReplyTarget *replyTarget, int returnState) +{ + ::EnterCriticalSection(&m_csInQueue); + + // add to the queue + msg_t msg = { cmd, replyTarget, 0, returnState }; + m_InQueue.AddToTail(msg); + + ::LeaveCriticalSection(&m_csInQueue); + + // signal the thread to start running + ::SetEvent(m_hEvent); +} + +//----------------------------------------------------------------------------- +// Purpose: Dispatches responses to SQLDB queries +//----------------------------------------------------------------------------- +bool CMySqlDatabase::RunFrame() +{ + bool doneWork = false; + + while (m_OutQueue.Count() > 0) + { + ::EnterCriticalSection(&m_csOutQueue); + + // pop the first item in the queue + int headIndex = m_OutQueue.Head(); + msg_t msg = m_OutQueue[headIndex]; + m_OutQueue.Remove(headIndex); + + ::LeaveCriticalSection(&m_csOutQueue); + + // run result + if (msg.replyTarget) + { + msg.replyTarget->SQLDBResponse(msg.cmd->GetID(), msg.returnState, msg.result, msg.cmd->GetReturnData()); + + // kill command + // it would be a good optimization to be able to reuse these + msg.cmd->deleteThis(); + } + + doneWork = true; + } + + return doneWork; +} + +//----------------------------------------------------------------------------- +// Purpose: load info - returns the number of sql db queries waiting to be processed +//----------------------------------------------------------------------------- +int CMySqlDatabase::QueriesInOutQueue() +{ + // the queue names are from the DB point of view, not the server - thus the reversal + return m_InQueue.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: number of queries finished processing, waiting to be responded to +//----------------------------------------------------------------------------- +int CMySqlDatabase::QueriesInFinishedQueue() +{ + return m_OutQueue.Count(); +} diff --git a/mp/src/utils/common/MySqlDatabase.h b/mp/src/utils/common/MySqlDatabase.h index caa5855c..52517f6d 100644 --- a/mp/src/utils/common/MySqlDatabase.h +++ b/mp/src/utils/common/MySqlDatabase.h @@ -1,104 +1,104 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef MYSQLDATABASE_H -#define MYSQLDATABASE_H -#ifdef _WIN32 -#pragma once -#endif - -#include -#include "ISQLDBReplyTarget.h" -#include "utlvector.h" -#include "UtlLinkedList.h" - -class ISQLDBCommand; - -//----------------------------------------------------------------------------- -// Purpose: Generic MySQL accessing database -// Provides threaded I/O queue functionality for accessing a mysql db -//----------------------------------------------------------------------------- -class CMySqlDatabase -{ -public: - // constructor - CMySqlDatabase(); - ~CMySqlDatabase(); - - // initialization - must be called before this object can be used - bool Initialize(); - - // Dispatches responses to SQLDB queries - bool RunFrame(); - - // load info - returns the number of sql db queries waiting to be processed - virtual int QueriesInOutQueue(); - - // number of queries finished processing, waiting to be responded to - virtual int QueriesInFinishedQueue(); - - // activates the thread - void RunThread(); - - // command queues - void AddCommandToQueue(ISQLDBCommand *cmd, ISQLDBReplyTarget *replyTarget, int returnState = 0); - -private: - - // threading data - bool m_bRunThread; - CRITICAL_SECTION m_csThread; - CRITICAL_SECTION m_csInQueue; - CRITICAL_SECTION m_csOutQueue; - CRITICAL_SECTION m_csDBAccess; - - // wait event - HANDLE m_hEvent; - - struct msg_t - { - ISQLDBCommand *cmd; - ISQLDBReplyTarget *replyTarget; - int result; - int returnState; - }; - - // command queues - CUtlLinkedList m_InQueue; - CUtlLinkedList m_OutQueue; -}; - -class Connection; - -//----------------------------------------------------------------------------- -// Purpose: Interface to a command -//----------------------------------------------------------------------------- -class ISQLDBCommand -{ -public: - // makes the command run (blocking), returning the success code - virtual int RunCommand() = 0; - - // return data - virtual void *GetReturnData() { return NULL; } - - // returns the command ID - virtual int GetID() { return 0; } - - // gets information about the command for if it failed - virtual void GetDebugInfo(char *buf, int bufSize) { buf[0] = 0; } - - // use to delete - virtual void deleteThis() = 0; - -protected: - // protected destructor, so that it has to be deleted through deleteThis() - virtual ~ISQLDBCommand() {} -}; - - -#endif // MYSQLDATABASE_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MYSQLDATABASE_H +#define MYSQLDATABASE_H +#ifdef _WIN32 +#pragma once +#endif + +#include +#include "ISQLDBReplyTarget.h" +#include "utlvector.h" +#include "UtlLinkedList.h" + +class ISQLDBCommand; + +//----------------------------------------------------------------------------- +// Purpose: Generic MySQL accessing database +// Provides threaded I/O queue functionality for accessing a mysql db +//----------------------------------------------------------------------------- +class CMySqlDatabase +{ +public: + // constructor + CMySqlDatabase(); + ~CMySqlDatabase(); + + // initialization - must be called before this object can be used + bool Initialize(); + + // Dispatches responses to SQLDB queries + bool RunFrame(); + + // load info - returns the number of sql db queries waiting to be processed + virtual int QueriesInOutQueue(); + + // number of queries finished processing, waiting to be responded to + virtual int QueriesInFinishedQueue(); + + // activates the thread + void RunThread(); + + // command queues + void AddCommandToQueue(ISQLDBCommand *cmd, ISQLDBReplyTarget *replyTarget, int returnState = 0); + +private: + + // threading data + bool m_bRunThread; + CRITICAL_SECTION m_csThread; + CRITICAL_SECTION m_csInQueue; + CRITICAL_SECTION m_csOutQueue; + CRITICAL_SECTION m_csDBAccess; + + // wait event + HANDLE m_hEvent; + + struct msg_t + { + ISQLDBCommand *cmd; + ISQLDBReplyTarget *replyTarget; + int result; + int returnState; + }; + + // command queues + CUtlLinkedList m_InQueue; + CUtlLinkedList m_OutQueue; +}; + +class Connection; + +//----------------------------------------------------------------------------- +// Purpose: Interface to a command +//----------------------------------------------------------------------------- +class ISQLDBCommand +{ +public: + // makes the command run (blocking), returning the success code + virtual int RunCommand() = 0; + + // return data + virtual void *GetReturnData() { return NULL; } + + // returns the command ID + virtual int GetID() { return 0; } + + // gets information about the command for if it failed + virtual void GetDebugInfo(char *buf, int bufSize) { buf[0] = 0; } + + // use to delete + virtual void deleteThis() = 0; + +protected: + // protected destructor, so that it has to be deleted through deleteThis() + virtual ~ISQLDBCommand() {} +}; + + +#endif // MYSQLDATABASE_H diff --git a/mp/src/utils/common/bsplib.cpp b/mp/src/utils/common/bsplib.cpp index 84d1a1d0..c3ad433e 100644 --- a/mp/src/utils/common/bsplib.cpp +++ b/mp/src/utils/common/bsplib.cpp @@ -1,5064 +1,5064 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Revision: $ -// $NoKeywords: $ -//=============================================================================// - -#include "cmdlib.h" -#include "mathlib/mathlib.h" -#include "bsplib.h" -#include "zip_utils.h" -#include "scriplib.h" -#include "utllinkedlist.h" -#include "bsptreedata.h" -#include "cmodel.h" -#include "gamebspfile.h" -#include "materialsystem/imaterial.h" -#include "materialsystem/hardwareverts.h" -#include "utlbuffer.h" -#include "utlrbtree.h" -#include "utlsymbol.h" -#include "utlstring.h" -#include "checksum_crc.h" -#include "physdll.h" -#include "tier0/dbg.h" -#include "lumpfiles.h" -#include "vtf/vtf.h" - -//============================================================================= - -// Boundary each lump should be aligned to -#define LUMP_ALIGNMENT 4 - -// Data descriptions for byte swapping - only needed -// for structures that are written to file for use by the game. -BEGIN_BYTESWAP_DATADESC( dheader_t ) - DEFINE_FIELD( ident, FIELD_INTEGER ), - DEFINE_FIELD( version, FIELD_INTEGER ), - DEFINE_EMBEDDED_ARRAY( lumps, HEADER_LUMPS ), - DEFINE_FIELD( mapRevision, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( lump_t ) - DEFINE_FIELD( fileofs, FIELD_INTEGER ), - DEFINE_FIELD( filelen, FIELD_INTEGER ), - DEFINE_FIELD( version, FIELD_INTEGER ), - DEFINE_ARRAY( fourCC, FIELD_CHARACTER, 4 ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dflagslump_t ) - DEFINE_FIELD( m_LevelFlags, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dplane_t ) - DEFINE_FIELD( normal, FIELD_VECTOR ), - DEFINE_FIELD( dist, FIELD_FLOAT ), - DEFINE_FIELD( type, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dleaf_version_0_t ) - DEFINE_FIELD( contents, FIELD_INTEGER ), - DEFINE_FIELD( cluster, FIELD_SHORT ), - DEFINE_BITFIELD( bf, FIELD_SHORT, 16 ), - DEFINE_ARRAY( mins, FIELD_SHORT, 3 ), - DEFINE_ARRAY( maxs, FIELD_SHORT, 3 ), - DEFINE_FIELD( firstleafface, FIELD_SHORT ), - DEFINE_FIELD( numleaffaces, FIELD_SHORT ), - DEFINE_FIELD( firstleafbrush, FIELD_SHORT ), - DEFINE_FIELD( numleafbrushes, FIELD_SHORT ), - DEFINE_FIELD( leafWaterDataID, FIELD_SHORT ), - DEFINE_EMBEDDED( m_AmbientLighting ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dleaf_t ) - DEFINE_FIELD( contents, FIELD_INTEGER ), - DEFINE_FIELD( cluster, FIELD_SHORT ), - DEFINE_BITFIELD( bf, FIELD_SHORT, 16 ), - DEFINE_ARRAY( mins, FIELD_SHORT, 3 ), - DEFINE_ARRAY( maxs, FIELD_SHORT, 3 ), - DEFINE_FIELD( firstleafface, FIELD_SHORT ), - DEFINE_FIELD( numleaffaces, FIELD_SHORT ), - DEFINE_FIELD( firstleafbrush, FIELD_SHORT ), - DEFINE_FIELD( numleafbrushes, FIELD_SHORT ), - DEFINE_FIELD( leafWaterDataID, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( CompressedLightCube ) // array of 6 ColorRGBExp32 (3 bytes and 1 char) - DEFINE_ARRAY( m_Color, FIELD_CHARACTER, 6 * sizeof(ColorRGBExp32) ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dleafambientindex_t ) - DEFINE_FIELD( ambientSampleCount, FIELD_SHORT ), - DEFINE_FIELD( firstAmbientSample, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dleafambientlighting_t ) // array of 6 ColorRGBExp32 (3 bytes and 1 char) - DEFINE_EMBEDDED( cube ), - DEFINE_FIELD( x, FIELD_CHARACTER ), - DEFINE_FIELD( y, FIELD_CHARACTER ), - DEFINE_FIELD( z, FIELD_CHARACTER ), - DEFINE_FIELD( pad, FIELD_CHARACTER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dvertex_t ) - DEFINE_FIELD( point, FIELD_VECTOR ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dnode_t ) - DEFINE_FIELD( planenum, FIELD_INTEGER ), - DEFINE_ARRAY( children, FIELD_INTEGER, 2 ), - DEFINE_ARRAY( mins, FIELD_SHORT, 3 ), - DEFINE_ARRAY( maxs, FIELD_SHORT, 3 ), - DEFINE_FIELD( firstface, FIELD_SHORT ), - DEFINE_FIELD( numfaces, FIELD_SHORT ), - DEFINE_FIELD( area, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( texinfo_t ) - DEFINE_ARRAY( textureVecsTexelsPerWorldUnits, FIELD_FLOAT, 2 * 4 ), - DEFINE_ARRAY( lightmapVecsLuxelsPerWorldUnits, FIELD_FLOAT, 2 * 4 ), - DEFINE_FIELD( flags, FIELD_INTEGER ), - DEFINE_FIELD( texdata, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dtexdata_t ) - DEFINE_FIELD( reflectivity, FIELD_VECTOR ), - DEFINE_FIELD( nameStringTableID, FIELD_INTEGER ), - DEFINE_FIELD( width, FIELD_INTEGER ), - DEFINE_FIELD( height, FIELD_INTEGER ), - DEFINE_FIELD( view_width, FIELD_INTEGER ), - DEFINE_FIELD( view_height, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( ddispinfo_t ) - DEFINE_FIELD( startPosition, FIELD_VECTOR ), - DEFINE_FIELD( m_iDispVertStart, FIELD_INTEGER ), - DEFINE_FIELD( m_iDispTriStart, FIELD_INTEGER ), - DEFINE_FIELD( power, FIELD_INTEGER ), - DEFINE_FIELD( minTess, FIELD_INTEGER ), - DEFINE_FIELD( smoothingAngle, FIELD_FLOAT ), - DEFINE_FIELD( contents, FIELD_INTEGER ), - DEFINE_FIELD( m_iMapFace, FIELD_SHORT ), - DEFINE_FIELD( m_iLightmapAlphaStart, FIELD_INTEGER ), - DEFINE_FIELD( m_iLightmapSamplePositionStart, FIELD_INTEGER ), - DEFINE_EMBEDDED_ARRAY( m_EdgeNeighbors, 4 ), - DEFINE_EMBEDDED_ARRAY( m_CornerNeighbors, 4 ), - DEFINE_ARRAY( m_AllowedVerts, FIELD_INTEGER, ddispinfo_t::ALLOWEDVERTS_SIZE ), // unsigned long -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( CDispNeighbor ) - DEFINE_EMBEDDED_ARRAY( m_SubNeighbors, 2 ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( CDispCornerNeighbors ) - DEFINE_ARRAY( m_Neighbors, FIELD_SHORT, MAX_DISP_CORNER_NEIGHBORS ), - DEFINE_FIELD( m_nNeighbors, FIELD_CHARACTER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( CDispSubNeighbor ) - DEFINE_FIELD( m_iNeighbor, FIELD_SHORT ), - DEFINE_FIELD( m_NeighborOrientation, FIELD_CHARACTER ), - DEFINE_FIELD( m_Span, FIELD_CHARACTER ), - DEFINE_FIELD( m_NeighborSpan, FIELD_CHARACTER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( CDispVert ) - DEFINE_FIELD( m_vVector, FIELD_VECTOR ), - DEFINE_FIELD( m_flDist, FIELD_FLOAT ), - DEFINE_FIELD( m_flAlpha, FIELD_FLOAT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( CDispTri ) - DEFINE_FIELD( m_uiTags, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( CFaceMacroTextureInfo ) - DEFINE_FIELD( m_MacroTextureNameID, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dprimitive_t ) - DEFINE_FIELD( type, FIELD_CHARACTER ), - DEFINE_FIELD( firstIndex, FIELD_SHORT ), - DEFINE_FIELD( indexCount, FIELD_SHORT ), - DEFINE_FIELD( firstVert, FIELD_SHORT ), - DEFINE_FIELD( vertCount, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dprimvert_t ) - DEFINE_FIELD( pos, FIELD_VECTOR ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dface_t ) - DEFINE_FIELD( planenum, FIELD_SHORT ), - DEFINE_FIELD( side, FIELD_CHARACTER ), - DEFINE_FIELD( onNode, FIELD_CHARACTER ), - DEFINE_FIELD( firstedge, FIELD_INTEGER ), - DEFINE_FIELD( numedges, FIELD_SHORT ), - DEFINE_FIELD( texinfo, FIELD_SHORT ), - DEFINE_FIELD( dispinfo, FIELD_SHORT ), - DEFINE_FIELD( surfaceFogVolumeID, FIELD_SHORT ), - DEFINE_ARRAY( styles, FIELD_CHARACTER, MAXLIGHTMAPS ), - DEFINE_FIELD( lightofs, FIELD_INTEGER ), - DEFINE_FIELD( area, FIELD_FLOAT ), - DEFINE_ARRAY( m_LightmapTextureMinsInLuxels, FIELD_INTEGER, 2 ), - DEFINE_ARRAY( m_LightmapTextureSizeInLuxels, FIELD_INTEGER, 2 ), - DEFINE_FIELD( origFace, FIELD_INTEGER ), - DEFINE_FIELD( m_NumPrims, FIELD_SHORT ), - DEFINE_FIELD( firstPrimID, FIELD_SHORT ), - DEFINE_FIELD( smoothingGroups, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dfaceid_t ) - DEFINE_FIELD( hammerfaceid, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dbrush_t ) - DEFINE_FIELD( firstside, FIELD_INTEGER ), - DEFINE_FIELD( numsides, FIELD_INTEGER ), - DEFINE_FIELD( contents, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dbrushside_t ) - DEFINE_FIELD( planenum, FIELD_SHORT ), - DEFINE_FIELD( texinfo, FIELD_SHORT ), - DEFINE_FIELD( dispinfo, FIELD_SHORT ), - DEFINE_FIELD( bevel, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dedge_t ) - DEFINE_ARRAY( v, FIELD_SHORT, 2 ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dmodel_t ) - DEFINE_FIELD( mins, FIELD_VECTOR ), - DEFINE_FIELD( maxs, FIELD_VECTOR ), - DEFINE_FIELD( origin, FIELD_VECTOR ), - DEFINE_FIELD( headnode, FIELD_INTEGER ), - DEFINE_FIELD( firstface, FIELD_INTEGER ), - DEFINE_FIELD( numfaces, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dphysmodel_t ) - DEFINE_FIELD( modelIndex, FIELD_INTEGER ), - DEFINE_FIELD( dataSize, FIELD_INTEGER ), - DEFINE_FIELD( keydataSize, FIELD_INTEGER ), - DEFINE_FIELD( solidCount, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dphysdisp_t ) - DEFINE_FIELD( numDisplacements, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( darea_t ) - DEFINE_FIELD( numareaportals, FIELD_INTEGER ), - DEFINE_FIELD( firstareaportal, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dareaportal_t ) - DEFINE_FIELD( m_PortalKey, FIELD_SHORT ), - DEFINE_FIELD( otherarea, FIELD_SHORT ), - DEFINE_FIELD( m_FirstClipPortalVert, FIELD_SHORT ), - DEFINE_FIELD( m_nClipPortalVerts, FIELD_SHORT ), - DEFINE_FIELD( planenum, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dworldlight_t ) - DEFINE_FIELD( origin, FIELD_VECTOR ), - DEFINE_FIELD( intensity, FIELD_VECTOR ), - DEFINE_FIELD( normal, FIELD_VECTOR ), - DEFINE_FIELD( cluster, FIELD_INTEGER ), - DEFINE_FIELD( type, FIELD_INTEGER ), // enumeration - DEFINE_FIELD( style, FIELD_INTEGER ), - DEFINE_FIELD( stopdot, FIELD_FLOAT ), - DEFINE_FIELD( stopdot2, FIELD_FLOAT ), - DEFINE_FIELD( exponent, FIELD_FLOAT ), - DEFINE_FIELD( radius, FIELD_FLOAT ), - DEFINE_FIELD( constant_attn, FIELD_FLOAT ), - DEFINE_FIELD( linear_attn, FIELD_FLOAT ), - DEFINE_FIELD( quadratic_attn, FIELD_FLOAT ), - DEFINE_FIELD( flags, FIELD_INTEGER ), - DEFINE_FIELD( texinfo, FIELD_INTEGER ), - DEFINE_FIELD( owner, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dleafwaterdata_t ) - DEFINE_FIELD( surfaceZ, FIELD_FLOAT ), - DEFINE_FIELD( minZ, FIELD_FLOAT ), - DEFINE_FIELD( surfaceTexInfoID, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( doccluderdata_t ) - DEFINE_FIELD( flags, FIELD_INTEGER ), - DEFINE_FIELD( firstpoly, FIELD_INTEGER ), - DEFINE_FIELD( polycount, FIELD_INTEGER ), - DEFINE_FIELD( mins, FIELD_VECTOR ), - DEFINE_FIELD( maxs, FIELD_VECTOR ), - DEFINE_FIELD( area, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( doccluderpolydata_t ) - DEFINE_FIELD( firstvertexindex, FIELD_INTEGER ), - DEFINE_FIELD( vertexcount, FIELD_INTEGER ), - DEFINE_FIELD( planenum, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dcubemapsample_t ) - DEFINE_ARRAY( origin, FIELD_INTEGER, 3 ), - DEFINE_FIELD( size, FIELD_CHARACTER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( doverlay_t ) - DEFINE_FIELD( nId, FIELD_INTEGER ), - DEFINE_FIELD( nTexInfo, FIELD_SHORT ), - DEFINE_FIELD( m_nFaceCountAndRenderOrder, FIELD_SHORT ), - DEFINE_ARRAY( aFaces, FIELD_INTEGER, OVERLAY_BSP_FACE_COUNT ), - DEFINE_ARRAY( flU, FIELD_FLOAT, 2 ), - DEFINE_ARRAY( flV, FIELD_FLOAT, 2 ), - DEFINE_ARRAY( vecUVPoints, FIELD_VECTOR, 4 ), - DEFINE_FIELD( vecOrigin, FIELD_VECTOR ), - DEFINE_FIELD( vecBasisNormal, FIELD_VECTOR ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dwateroverlay_t ) - DEFINE_FIELD( nId, FIELD_INTEGER ), - DEFINE_FIELD( nTexInfo, FIELD_SHORT ), - DEFINE_FIELD( m_nFaceCountAndRenderOrder, FIELD_SHORT ), - DEFINE_ARRAY( aFaces, FIELD_INTEGER, WATEROVERLAY_BSP_FACE_COUNT ), - DEFINE_ARRAY( flU, FIELD_FLOAT, 2 ), - DEFINE_ARRAY( flV, FIELD_FLOAT, 2 ), - DEFINE_ARRAY( vecUVPoints, FIELD_VECTOR, 4 ), - DEFINE_FIELD( vecOrigin, FIELD_VECTOR ), - DEFINE_FIELD( vecBasisNormal, FIELD_VECTOR ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( doverlayfade_t ) - DEFINE_FIELD( flFadeDistMinSq, FIELD_FLOAT ), - DEFINE_FIELD( flFadeDistMaxSq, FIELD_FLOAT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dgamelumpheader_t ) - DEFINE_FIELD( lumpCount, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dgamelump_t ) - DEFINE_FIELD( id, FIELD_INTEGER ), // GameLumpId_t - DEFINE_FIELD( flags, FIELD_SHORT ), - DEFINE_FIELD( version, FIELD_SHORT ), - DEFINE_FIELD( fileofs, FIELD_INTEGER ), - DEFINE_FIELD( filelen, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -// From gamebspfile.h -BEGIN_BYTESWAP_DATADESC( StaticPropDictLump_t ) - DEFINE_ARRAY( m_Name, FIELD_CHARACTER, STATIC_PROP_NAME_LENGTH ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( StaticPropLump_t ) - DEFINE_FIELD( m_Origin, FIELD_VECTOR ), - DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle - DEFINE_FIELD( m_PropType, FIELD_SHORT ), - DEFINE_FIELD( m_FirstLeaf, FIELD_SHORT ), - DEFINE_FIELD( m_LeafCount, FIELD_SHORT ), - DEFINE_FIELD( m_Solid, FIELD_CHARACTER ), - DEFINE_FIELD( m_Flags, FIELD_CHARACTER ), - DEFINE_FIELD( m_Skin, FIELD_INTEGER ), - DEFINE_FIELD( m_FadeMinDist, FIELD_FLOAT ), - DEFINE_FIELD( m_FadeMaxDist, FIELD_FLOAT ), - DEFINE_FIELD( m_LightingOrigin, FIELD_VECTOR ), - DEFINE_FIELD( m_flForcedFadeScale, FIELD_FLOAT ), - DEFINE_FIELD( m_nMinDXLevel, FIELD_SHORT ), - DEFINE_FIELD( m_nMaxDXLevel, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( StaticPropLumpV4_t ) - DEFINE_FIELD( m_Origin, FIELD_VECTOR ), - DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle - DEFINE_FIELD( m_PropType, FIELD_SHORT ), - DEFINE_FIELD( m_FirstLeaf, FIELD_SHORT ), - DEFINE_FIELD( m_LeafCount, FIELD_SHORT ), - DEFINE_FIELD( m_Solid, FIELD_CHARACTER ), - DEFINE_FIELD( m_Flags, FIELD_CHARACTER ), - DEFINE_FIELD( m_Skin, FIELD_INTEGER ), - DEFINE_FIELD( m_FadeMinDist, FIELD_FLOAT ), - DEFINE_FIELD( m_FadeMaxDist, FIELD_FLOAT ), - DEFINE_FIELD( m_LightingOrigin, FIELD_VECTOR ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( StaticPropLumpV5_t ) - DEFINE_FIELD( m_Origin, FIELD_VECTOR ), - DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle - DEFINE_FIELD( m_PropType, FIELD_SHORT ), - DEFINE_FIELD( m_FirstLeaf, FIELD_SHORT ), - DEFINE_FIELD( m_LeafCount, FIELD_SHORT ), - DEFINE_FIELD( m_Solid, FIELD_CHARACTER ), - DEFINE_FIELD( m_Flags, FIELD_CHARACTER ), - DEFINE_FIELD( m_Skin, FIELD_INTEGER ), - DEFINE_FIELD( m_FadeMinDist, FIELD_FLOAT ), - DEFINE_FIELD( m_FadeMaxDist, FIELD_FLOAT ), - DEFINE_FIELD( m_LightingOrigin, FIELD_VECTOR ), - DEFINE_FIELD( m_flForcedFadeScale, FIELD_FLOAT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( StaticPropLeafLump_t ) - DEFINE_FIELD( m_Leaf, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( DetailObjectDictLump_t ) - DEFINE_ARRAY( m_Name, FIELD_CHARACTER, DETAIL_NAME_LENGTH ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( DetailObjectLump_t ) - DEFINE_FIELD( m_Origin, FIELD_VECTOR ), - DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle - DEFINE_FIELD( m_DetailModel, FIELD_SHORT ), - DEFINE_FIELD( m_Leaf, FIELD_SHORT ), - DEFINE_ARRAY( m_Lighting, FIELD_CHARACTER, 4 ), // ColorRGBExp32 - DEFINE_FIELD( m_LightStyles, FIELD_INTEGER ), - DEFINE_FIELD( m_LightStyleCount, FIELD_CHARACTER ), - DEFINE_FIELD( m_SwayAmount, FIELD_CHARACTER ), - DEFINE_FIELD( m_ShapeAngle, FIELD_CHARACTER ), - DEFINE_FIELD( m_ShapeSize, FIELD_CHARACTER ), - DEFINE_FIELD( m_Orientation, FIELD_CHARACTER ), - DEFINE_ARRAY( m_Padding2, FIELD_CHARACTER, 3 ), - DEFINE_FIELD( m_Type, FIELD_CHARACTER ), - DEFINE_ARRAY( m_Padding3, FIELD_CHARACTER, 3 ), - DEFINE_FIELD( m_flScale, FIELD_FLOAT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( DetailSpriteDictLump_t ) - DEFINE_FIELD( m_UL, FIELD_VECTOR2D ), - DEFINE_FIELD( m_LR, FIELD_VECTOR2D ), - DEFINE_FIELD( m_TexUL, FIELD_VECTOR2D ), - DEFINE_FIELD( m_TexLR, FIELD_VECTOR2D ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( DetailPropLightstylesLump_t ) - DEFINE_ARRAY( m_Lighting, FIELD_CHARACTER, 4 ), // ColorRGBExp32 - DEFINE_FIELD( m_Style, FIELD_CHARACTER ), -END_BYTESWAP_DATADESC() - -// From vradstaticprops.h -namespace HardwareVerts -{ -BEGIN_BYTESWAP_DATADESC( MeshHeader_t ) - DEFINE_FIELD( m_nLod, FIELD_INTEGER ), - DEFINE_FIELD( m_nVertexes, FIELD_INTEGER ), - DEFINE_FIELD( m_nOffset, FIELD_INTEGER ), - DEFINE_ARRAY( m_nUnused, FIELD_INTEGER, 4 ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( FileHeader_t ) - DEFINE_FIELD( m_nVersion, FIELD_INTEGER ), - DEFINE_FIELD( m_nChecksum, FIELD_INTEGER ), - DEFINE_FIELD( m_nVertexFlags, FIELD_INTEGER ), - DEFINE_FIELD( m_nVertexSize, FIELD_INTEGER ), - DEFINE_FIELD( m_nVertexes, FIELD_INTEGER ), - DEFINE_FIELD( m_nMeshes, FIELD_INTEGER ), - DEFINE_ARRAY( m_nUnused, FIELD_INTEGER, 4 ), -END_BYTESWAP_DATADESC() -} // end namespace - -static const char *s_LumpNames[] = { - "LUMP_ENTITIES", // 0 - "LUMP_PLANES", // 1 - "LUMP_TEXDATA", // 2 - "LUMP_VERTEXES", // 3 - "LUMP_VISIBILITY", // 4 - "LUMP_NODES", // 5 - "LUMP_TEXINFO", // 6 - "LUMP_FACES", // 7 - "LUMP_LIGHTING", // 8 - "LUMP_OCCLUSION", // 9 - "LUMP_LEAFS", // 10 - "LUMP_FACEIDS", // 11 - "LUMP_EDGES", // 12 - "LUMP_SURFEDGES", // 13 - "LUMP_MODELS", // 14 - "LUMP_WORLDLIGHTS", // 15 - "LUMP_LEAFFACES", // 16 - "LUMP_LEAFBRUSHES", // 17 - "LUMP_BRUSHES", // 18 - "LUMP_BRUSHSIDES", // 19 - "LUMP_AREAS", // 20 - "LUMP_AREAPORTALS", // 21 - "LUMP_UNUSED0", // 22 - "LUMP_UNUSED1", // 23 - "LUMP_UNUSED2", // 24 - "LUMP_UNUSED3", // 25 - "LUMP_DISPINFO", // 26 - "LUMP_ORIGINALFACES", // 27 - "LUMP_PHYSDISP", // 28 - "LUMP_PHYSCOLLIDE", // 29 - "LUMP_VERTNORMALS", // 30 - "LUMP_VERTNORMALINDICES", // 31 - "LUMP_DISP_LIGHTMAP_ALPHAS", // 32 - "LUMP_DISP_VERTS", // 33 - "LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS", // 34 - "LUMP_GAME_LUMP", // 35 - "LUMP_LEAFWATERDATA", // 36 - "LUMP_PRIMITIVES", // 37 - "LUMP_PRIMVERTS", // 38 - "LUMP_PRIMINDICES", // 39 - "LUMP_PAKFILE", // 40 - "LUMP_CLIPPORTALVERTS", // 41 - "LUMP_CUBEMAPS", // 42 - "LUMP_TEXDATA_STRING_DATA", // 43 - "LUMP_TEXDATA_STRING_TABLE", // 44 - "LUMP_OVERLAYS", // 45 - "LUMP_LEAFMINDISTTOWATER", // 46 - "LUMP_FACE_MACRO_TEXTURE_INFO", // 47 - "LUMP_DISP_TRIS", // 48 - "LUMP_PHYSCOLLIDESURFACE", // 49 - "LUMP_WATEROVERLAYS", // 50 - "LUMP_LEAF_AMBIENT_INDEX_HDR", // 51 - "LUMP_LEAF_AMBIENT_INDEX", // 52 - "LUMP_LIGHTING_HDR", // 53 - "LUMP_WORLDLIGHTS_HDR", // 54 - "LUMP_LEAF_AMBIENT_LIGHTING_HDR", // 55 - "LUMP_LEAF_AMBIENT_LIGHTING", // 56 - "LUMP_XZIPPAKFILE", // 57 - "LUMP_FACES_HDR", // 58 - "LUMP_MAP_FLAGS", // 59 - "LUMP_OVERLAY_FADES", // 60 -}; - -const char *GetLumpName( unsigned int lumpnum ) -{ - if ( lumpnum >= ARRAYSIZE( s_LumpNames ) ) - { - return "UNKNOWN"; - } - return s_LumpNames[lumpnum]; -} - -// "-hdr" tells us to use the HDR fields (if present) on the light sources. Also, tells us to write -// out the HDR lumps for lightmaps, ambient leaves, and lights sources. -bool g_bHDR = false; - -// Set to true to generate Xbox360 native output files -static bool g_bSwapOnLoad = false; -static bool g_bSwapOnWrite = false; - -VTFConvertFunc_t g_pVTFConvertFunc; -VHVFixupFunc_t g_pVHVFixupFunc; -CompressFunc_t g_pCompressFunc; - -CUtlVector< CUtlString > g_StaticPropNames; -CUtlVector< int > g_StaticPropInstances; - -CByteswap g_Swap; - -uint32 g_LevelFlags = 0; - -int nummodels; -dmodel_t dmodels[MAX_MAP_MODELS]; - -int visdatasize; -byte dvisdata[MAX_MAP_VISIBILITY]; -dvis_t *dvis = (dvis_t *)dvisdata; - -CUtlVector dlightdataHDR; -CUtlVector dlightdataLDR; -CUtlVector *pdlightdata = &dlightdataLDR; - -CUtlVector dentdata; - -int numleafs; -#if !defined( BSP_USE_LESS_MEMORY ) -dleaf_t dleafs[MAX_MAP_LEAFS]; -#else -dleaf_t *dleafs; -#endif - -CUtlVector g_LeafAmbientIndexLDR; -CUtlVector g_LeafAmbientIndexHDR; -CUtlVector *g_pLeafAmbientIndex = NULL; -CUtlVector g_LeafAmbientLightingLDR; -CUtlVector g_LeafAmbientLightingHDR; -CUtlVector *g_pLeafAmbientLighting = NULL; - -unsigned short g_LeafMinDistToWater[MAX_MAP_LEAFS]; - -int numplanes; -dplane_t dplanes[MAX_MAP_PLANES]; - -int numvertexes; -dvertex_t dvertexes[MAX_MAP_VERTS]; - -int g_numvertnormalindices; // dfaces reference these. These index g_vertnormals. -unsigned short g_vertnormalindices[MAX_MAP_VERTNORMALS]; - -int g_numvertnormals; -Vector g_vertnormals[MAX_MAP_VERTNORMALS]; - -int numnodes; -dnode_t dnodes[MAX_MAP_NODES]; - -CUtlVector texinfo( MAX_MAP_TEXINFO ); - -int numtexdata; -dtexdata_t dtexdata[MAX_MAP_TEXDATA]; - -// -// displacement map bsp file info: dispinfo -// -CUtlVector g_dispinfo; -CUtlVector g_DispVerts; -CUtlVector g_DispTris; -CUtlVector g_DispLightmapSamplePositions; // LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS - -int numorigfaces; -dface_t dorigfaces[MAX_MAP_FACES]; - -int g_numprimitives = 0; -dprimitive_t g_primitives[MAX_MAP_PRIMITIVES]; - -int g_numprimverts = 0; -dprimvert_t g_primverts[MAX_MAP_PRIMVERTS]; - -int g_numprimindices = 0; -unsigned short g_primindices[MAX_MAP_PRIMINDICES]; - -int numfaces; -dface_t dfaces[MAX_MAP_FACES]; - -int numfaceids; -CUtlVector dfaceids; - -int numfaces_hdr; -dface_t dfaces_hdr[MAX_MAP_FACES]; - -int numedges; -dedge_t dedges[MAX_MAP_EDGES]; - -int numleaffaces; -unsigned short dleaffaces[MAX_MAP_LEAFFACES]; - -int numleafbrushes; -unsigned short dleafbrushes[MAX_MAP_LEAFBRUSHES]; - -int numsurfedges; -int dsurfedges[MAX_MAP_SURFEDGES]; - -int numbrushes; -dbrush_t dbrushes[MAX_MAP_BRUSHES]; - -int numbrushsides; -dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; - -int numareas; -darea_t dareas[MAX_MAP_AREAS]; - -int numareaportals; -dareaportal_t dareaportals[MAX_MAP_AREAPORTALS]; - -int numworldlightsLDR; -dworldlight_t dworldlightsLDR[MAX_MAP_WORLDLIGHTS]; - -int numworldlightsHDR; -dworldlight_t dworldlightsHDR[MAX_MAP_WORLDLIGHTS]; - -int *pNumworldlights = &numworldlightsLDR; -dworldlight_t *dworldlights = dworldlightsLDR; - -int numleafwaterdata = 0; -dleafwaterdata_t dleafwaterdata[MAX_MAP_LEAFWATERDATA]; - -CUtlVector g_FaceMacroTextureInfos; - -Vector g_ClipPortalVerts[MAX_MAP_PORTALVERTS]; -int g_nClipPortalVerts; - -dcubemapsample_t g_CubemapSamples[MAX_MAP_CUBEMAPSAMPLES]; -int g_nCubemapSamples = 0; - -int g_nOverlayCount; -doverlay_t g_Overlays[MAX_MAP_OVERLAYS]; -doverlayfade_t g_OverlayFades[MAX_MAP_OVERLAYS]; - -int g_nWaterOverlayCount; -dwateroverlay_t g_WaterOverlays[MAX_MAP_WATEROVERLAYS]; - -CUtlVector g_TexDataStringData; -CUtlVector g_TexDataStringTable; - -byte *g_pPhysCollide = NULL; -int g_PhysCollideSize = 0; -int g_MapRevision = 0; - -byte *g_pPhysDisp = NULL; -int g_PhysDispSize = 0; - -CUtlVector g_OccluderData( 256, 256 ); -CUtlVector g_OccluderPolyData( 1024, 1024 ); -CUtlVector g_OccluderVertexIndices( 2048, 2048 ); - -template static void WriteData( T *pData, int count = 1 ); -template static void WriteData( int fieldType, T *pData, int count = 1 ); -template< class T > static void AddLump( int lumpnum, T *pData, int count, int version = 0 ); -template< class T > static void AddLump( int lumpnum, CUtlVector &data, int version = 0 ); - -dheader_t *g_pBSPHeader; -FileHandle_t g_hBSPFile; - -struct Lump_t -{ - void *pLumps[HEADER_LUMPS]; - int size[HEADER_LUMPS]; - bool bLumpParsed[HEADER_LUMPS]; -} g_Lumps; - -CGameLump g_GameLumps; - -static IZip *s_pakFile = 0; - -//----------------------------------------------------------------------------- -// Keep the file position aligned to an arbitrary boundary. -// Returns updated file position. -//----------------------------------------------------------------------------- -static unsigned int AlignFilePosition( FileHandle_t hFile, int alignment ) -{ - unsigned int currPosition = g_pFileSystem->Tell( hFile ); - - if ( alignment >= 2 ) - { - unsigned int newPosition = AlignValue( currPosition, alignment ); - unsigned int count = newPosition - currPosition; - if ( count ) - { - char *pBuffer; - char smallBuffer[4096]; - if ( count > sizeof( smallBuffer ) ) - { - pBuffer = (char *)malloc( count ); - } - else - { - pBuffer = smallBuffer; - } - - memset( pBuffer, 0, count ); - SafeWrite( hFile, pBuffer, count ); - - if ( pBuffer != smallBuffer ) - { - free( pBuffer ); - } - - currPosition = newPosition; - } - } - - return currPosition; -} - -//----------------------------------------------------------------------------- -// Purpose: // Get a pakfile instance -// Output : IZip* -//----------------------------------------------------------------------------- -IZip* GetPakFile( void ) -{ - if ( !s_pakFile ) - { - s_pakFile = IZip::CreateZip(); - } - return s_pakFile; -} - -//----------------------------------------------------------------------------- -// Purpose: Free the pak files -//----------------------------------------------------------------------------- -void ReleasePakFileLumps( void ) -{ - // Release the pak files - IZip::ReleaseZip( s_pakFile ); - s_pakFile = NULL; -} - -//----------------------------------------------------------------------------- -// Purpose: Set the sector alignment for all subsequent zip operations -//----------------------------------------------------------------------------- -void ForceAlignment( IZip *pak, bool bAlign, bool bCompatibleFormat, unsigned int alignmentSize ) -{ - pak->ForceAlignment( bAlign, bCompatibleFormat, alignmentSize ); -} - -//----------------------------------------------------------------------------- -// Purpose: Store data back out to .bsp file -//----------------------------------------------------------------------------- -static void WritePakFileLump( void ) -{ - CUtlBuffer buf( 0, 0 ); - GetPakFile()->ActivateByteSwapping( IsX360() ); - GetPakFile()->SaveToBuffer( buf ); - - // must respect pak file alignment - // pad up and ensure lump starts on same aligned boundary - AlignFilePosition( g_hBSPFile, GetPakFile()->GetAlignment() ); - - // Now store final buffers out to file - AddLump( LUMP_PAKFILE, (byte*)buf.Base(), buf.TellPut() ); -} - -//----------------------------------------------------------------------------- -// Purpose: Remove all entries -//----------------------------------------------------------------------------- -void ClearPakFile( IZip *pak ) -{ - pak->Reset(); -} - -//----------------------------------------------------------------------------- -// Purpose: Add file from disk to .bsp PAK lump -// Input : *relativename - -// *fullpath - -//----------------------------------------------------------------------------- -void AddFileToPak( IZip *pak, const char *relativename, const char *fullpath ) -{ - pak->AddFileToZip( relativename, fullpath ); -} - -//----------------------------------------------------------------------------- -// Purpose: Add buffer to .bsp PAK lump as named file -// Input : *relativename - -// *data - -// length - -//----------------------------------------------------------------------------- -void AddBufferToPak( IZip *pak, const char *pRelativeName, void *data, int length, bool bTextMode ) -{ - pak->AddBufferToZip( pRelativeName, data, length, bTextMode ); -} - -//----------------------------------------------------------------------------- -// Purpose: Check if a file already exists in the pack file. -// Input : *relativename - -//----------------------------------------------------------------------------- -bool FileExistsInPak( IZip *pak, const char *pRelativeName ) -{ - return pak->FileExistsInZip( pRelativeName ); -} - - -//----------------------------------------------------------------------------- -// Read a file from the pack file -//----------------------------------------------------------------------------- -bool ReadFileFromPak( IZip *pak, const char *pRelativeName, bool bTextMode, CUtlBuffer &buf ) -{ - return pak->ReadFileFromZip( pRelativeName, bTextMode, buf ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Remove file from .bsp PAK lump -// Input : *relativename - -//----------------------------------------------------------------------------- -void RemoveFileFromPak( IZip *pak, const char *relativename ) -{ - pak->RemoveFileFromZip( relativename ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Get next filename in directory -// Input : id, -1 to start, returns next id, or -1 at list conclusion -//----------------------------------------------------------------------------- -int GetNextFilename( IZip *pak, int id, char *pBuffer, int bufferSize, int &fileSize ) -{ - return pak->GetNextFilename( id, pBuffer, bufferSize, fileSize ); -} - -//----------------------------------------------------------------------------- -// Convert four-CC code to a handle + back -//----------------------------------------------------------------------------- -GameLumpHandle_t CGameLump::GetGameLumpHandle( GameLumpId_t id ) -{ - // NOTE: I'm also expecting game lump id's to be four-CC codes - Assert( id > HEADER_LUMPS ); - - FOR_EACH_LL(m_GameLumps, i) - { - if (m_GameLumps[i].m_Id == id) - return i; - } - - return InvalidGameLump(); -} - -GameLumpId_t CGameLump::GetGameLumpId( GameLumpHandle_t handle ) -{ - return m_GameLumps[handle].m_Id; -} - -int CGameLump::GetGameLumpFlags( GameLumpHandle_t handle ) -{ - return m_GameLumps[handle].m_Flags; -} - -int CGameLump::GetGameLumpVersion( GameLumpHandle_t handle ) -{ - return m_GameLumps[handle].m_Version; -} - - -//----------------------------------------------------------------------------- -// Game lump accessor methods -//----------------------------------------------------------------------------- - -void* CGameLump::GetGameLump( GameLumpHandle_t id ) -{ - return m_GameLumps[id].m_Memory.Base(); -} - -int CGameLump::GameLumpSize( GameLumpHandle_t id ) -{ - return m_GameLumps[id].m_Memory.NumAllocated(); -} - - -//----------------------------------------------------------------------------- -// Game lump iteration methods -//----------------------------------------------------------------------------- - -GameLumpHandle_t CGameLump::FirstGameLump() -{ - return (m_GameLumps.Count()) ? m_GameLumps.Head() : InvalidGameLump(); -} - -GameLumpHandle_t CGameLump::NextGameLump( GameLumpHandle_t handle ) -{ - - return (m_GameLumps.IsValidIndex(handle)) ? m_GameLumps.Next(handle) : InvalidGameLump(); -} - -GameLumpHandle_t CGameLump::InvalidGameLump() -{ - return 0xFFFF; -} - - -//----------------------------------------------------------------------------- -// Game lump creation/destruction method -//----------------------------------------------------------------------------- - -GameLumpHandle_t CGameLump::CreateGameLump( GameLumpId_t id, int size, int flags, int version ) -{ - Assert( GetGameLumpHandle(id) == InvalidGameLump() ); - GameLumpHandle_t handle = m_GameLumps.AddToTail(); - m_GameLumps[handle].m_Id = id; - m_GameLumps[handle].m_Flags = flags; - m_GameLumps[handle].m_Version = version; - m_GameLumps[handle].m_Memory.EnsureCapacity( size ); - return handle; -} - -void CGameLump::DestroyGameLump( GameLumpHandle_t handle ) -{ - m_GameLumps.Remove( handle ); -} - -void CGameLump::DestroyAllGameLumps() -{ - m_GameLumps.RemoveAll(); -} - -//----------------------------------------------------------------------------- -// Compute file size and clump count -//----------------------------------------------------------------------------- - -void CGameLump::ComputeGameLumpSizeAndCount( int& size, int& clumpCount ) -{ - // Figure out total size of the client lumps - size = 0; - clumpCount = 0; - GameLumpHandle_t h; - for( h = FirstGameLump(); h != InvalidGameLump(); h = NextGameLump( h ) ) - { - ++clumpCount; - size += GameLumpSize( h ); - } - - // Add on headers - size += sizeof( dgamelumpheader_t ) + clumpCount * sizeof( dgamelump_t ); -} - - -void CGameLump::SwapGameLump( GameLumpId_t id, int version, byte *dest, byte *src, int length ) -{ - int count = 0; - switch( id ) - { - case GAMELUMP_STATIC_PROPS: - // Swap the static prop model dict - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - g_Swap.SwapFieldsToTargetEndian( (StaticPropDictLump_t*)dest, (StaticPropDictLump_t*)src, count ); - src += sizeof(StaticPropDictLump_t) * count; - dest += sizeof(StaticPropDictLump_t) * count; - - // Swap the leaf list - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - g_Swap.SwapFieldsToTargetEndian( (StaticPropLeafLump_t*)dest, (StaticPropLeafLump_t*)src, count ); - src += sizeof(StaticPropLeafLump_t) * count; - dest += sizeof(StaticPropLeafLump_t) * count; - - // Swap the models - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - // The one-at-a-time swap is to compensate for these structures - // possibly being misaligned, which crashes the Xbox 360. - if ( version == 4 ) - { - StaticPropLumpV4_t lump; - for ( int i = 0; i < count; ++i ) - { - Q_memcpy( &lump, src, sizeof(StaticPropLumpV4_t) ); - g_Swap.SwapFieldsToTargetEndian( &lump, &lump ); - Q_memcpy( dest, &lump, sizeof(StaticPropLumpV4_t) ); - src += sizeof( StaticPropLumpV4_t ); - dest += sizeof( StaticPropLumpV4_t ); - } - } - else if ( version == 5 ) - { - StaticPropLumpV5_t lump; - for ( int i = 0; i < count; ++i ) - { - Q_memcpy( &lump, src, sizeof(StaticPropLumpV5_t) ); - g_Swap.SwapFieldsToTargetEndian( &lump, &lump ); - Q_memcpy( dest, &lump, sizeof(StaticPropLumpV5_t) ); - src += sizeof( StaticPropLumpV5_t ); - dest += sizeof( StaticPropLumpV5_t ); - } - } - else - { - if ( version != 6 ) - { - Error( "Unknown Static Prop Lump version %d didn't get swapped!\n", version ); - } - - StaticPropLump_t lump; - for ( int i = 0; i < count; ++i ) - { - Q_memcpy( &lump, src, sizeof(StaticPropLump_t) ); - g_Swap.SwapFieldsToTargetEndian( &lump, &lump ); - Q_memcpy( dest, &lump, sizeof(StaticPropLump_t) ); - src += sizeof( StaticPropLump_t ); - dest += sizeof( StaticPropLump_t ); - } - } - break; - - case GAMELUMP_DETAIL_PROPS: - // Swap the detail prop model dict - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - g_Swap.SwapFieldsToTargetEndian( (DetailObjectDictLump_t*)dest, (DetailObjectDictLump_t*)src, count ); - src += sizeof(DetailObjectDictLump_t) * count; - dest += sizeof(DetailObjectDictLump_t) * count; - - if ( version == 4 ) - { - // Swap the detail sprite dict - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - DetailSpriteDictLump_t spritelump; - for ( int i = 0; i < count; ++i ) - { - Q_memcpy( &spritelump, src, sizeof(DetailSpriteDictLump_t) ); - g_Swap.SwapFieldsToTargetEndian( &spritelump, &spritelump ); - Q_memcpy( dest, &spritelump, sizeof(DetailSpriteDictLump_t) ); - src += sizeof(DetailSpriteDictLump_t); - dest += sizeof(DetailSpriteDictLump_t); - } - - // Swap the models - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - DetailObjectLump_t objectlump; - for ( int i = 0; i < count; ++i ) - { - Q_memcpy( &objectlump, src, sizeof(DetailObjectLump_t) ); - g_Swap.SwapFieldsToTargetEndian( &objectlump, &objectlump ); - Q_memcpy( dest, &objectlump, sizeof(DetailObjectLump_t) ); - src += sizeof(DetailObjectLump_t); - dest += sizeof(DetailObjectLump_t); - } - } - break; - - case GAMELUMP_DETAIL_PROP_LIGHTING: - // Swap the LDR light styles - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - g_Swap.SwapFieldsToTargetEndian( (DetailPropLightstylesLump_t*)dest, (DetailPropLightstylesLump_t*)src, count ); - src += sizeof(DetailObjectDictLump_t) * count; - dest += sizeof(DetailObjectDictLump_t) * count; - break; - - case GAMELUMP_DETAIL_PROP_LIGHTING_HDR: - // Swap the HDR light styles - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - g_Swap.SwapFieldsToTargetEndian( (DetailPropLightstylesLump_t*)dest, (DetailPropLightstylesLump_t*)src, count ); - src += sizeof(DetailObjectDictLump_t) * count; - dest += sizeof(DetailObjectDictLump_t) * count; - break; - - default: - char idchars[5] = {0}; - Q_memcpy( idchars, &id, 4 ); - Warning( "Unknown game lump '%s' didn't get swapped!\n", idchars ); - memcpy ( dest, src, length); - break; - } -} - -//----------------------------------------------------------------------------- -// Game lump file I/O -//----------------------------------------------------------------------------- -void CGameLump::ParseGameLump( dheader_t* pHeader ) -{ - g_GameLumps.DestroyAllGameLumps(); - - g_Lumps.bLumpParsed[LUMP_GAME_LUMP] = true; - - int length = pHeader->lumps[LUMP_GAME_LUMP].filelen; - int ofs = pHeader->lumps[LUMP_GAME_LUMP].fileofs; - - if (length > 0) - { - // Read dictionary... - dgamelumpheader_t* pGameLumpHeader = (dgamelumpheader_t*)((byte *)pHeader + ofs); - if ( g_bSwapOnLoad ) - { - g_Swap.SwapFieldsToTargetEndian( pGameLumpHeader ); - } - dgamelump_t* pGameLump = (dgamelump_t*)(pGameLumpHeader + 1); - for (int i = 0; i < pGameLumpHeader->lumpCount; ++i ) - { - if ( g_bSwapOnLoad ) - { - g_Swap.SwapFieldsToTargetEndian( &pGameLump[i] ); - } - - int length = pGameLump[i].filelen; - GameLumpHandle_t lump = g_GameLumps.CreateGameLump( pGameLump[i].id, length, pGameLump[i].flags, pGameLump[i].version ); - if ( g_bSwapOnLoad ) - { - SwapGameLump( pGameLump[i].id, pGameLump[i].version, (byte*)g_GameLumps.GetGameLump(lump), (byte *)pHeader + pGameLump[i].fileofs, length ); - } - else - { - memcpy( g_GameLumps.GetGameLump(lump), (byte *)pHeader + pGameLump[i].fileofs, length ); - } - } - } -} - - -//----------------------------------------------------------------------------- -// String table methods -//----------------------------------------------------------------------------- -const char *TexDataStringTable_GetString( int stringID ) -{ - return &g_TexDataStringData[g_TexDataStringTable[stringID]]; -} - -int TexDataStringTable_AddOrFindString( const char *pString ) -{ - int i; - // garymcthack: Make this use an RBTree! - for( i = 0; i < g_TexDataStringTable.Count(); i++ ) - { - if( stricmp( pString, &g_TexDataStringData[g_TexDataStringTable[i]] ) == 0 ) - { - return i; - } - } - - int len = strlen( pString ); - int outOffset = g_TexDataStringData.AddMultipleToTail( len+1, pString ); - int outIndex = g_TexDataStringTable.AddToTail( outOffset ); - return outIndex; -} - -//----------------------------------------------------------------------------- -// Adds all game lumps into one big block -//----------------------------------------------------------------------------- - -static void AddGameLumps( ) -{ - // Figure out total size of the client lumps - int size, clumpCount; - g_GameLumps.ComputeGameLumpSizeAndCount( size, clumpCount ); - - // Set up the main lump dictionary entry - g_Lumps.size[LUMP_GAME_LUMP] = 0; // mark it written - - lump_t* lump = &g_pBSPHeader->lumps[LUMP_GAME_LUMP]; - - lump->fileofs = g_pFileSystem->Tell( g_hBSPFile ); - lump->filelen = size; - - // write header - dgamelumpheader_t header; - header.lumpCount = clumpCount; - WriteData( &header ); - - // write dictionary - dgamelump_t dict; - int offset = lump->fileofs + sizeof(header) + clumpCount * sizeof(dgamelump_t); - GameLumpHandle_t h; - for( h = g_GameLumps.FirstGameLump(); h != g_GameLumps.InvalidGameLump(); h = g_GameLumps.NextGameLump( h ) ) - { - dict.id = g_GameLumps.GetGameLumpId(h); - dict.version = g_GameLumps.GetGameLumpVersion(h); - dict.flags = g_GameLumps.GetGameLumpFlags(h); - dict.fileofs = offset; - dict.filelen = g_GameLumps.GameLumpSize( h ); - offset += dict.filelen; - - WriteData( &dict ); - } - - // write lumps.. - for( h = g_GameLumps.FirstGameLump(); h != g_GameLumps.InvalidGameLump(); h = g_GameLumps.NextGameLump( h ) ) - { - unsigned int lumpsize = g_GameLumps.GameLumpSize(h); - if ( g_bSwapOnWrite ) - { - g_GameLumps.SwapGameLump( g_GameLumps.GetGameLumpId(h), g_GameLumps.GetGameLumpVersion(h), (byte*)g_GameLumps.GetGameLump(h), (byte*)g_GameLumps.GetGameLump(h), lumpsize ); - } - SafeWrite( g_hBSPFile, g_GameLumps.GetGameLump(h), lumpsize ); - } - - // align to doubleword - AlignFilePosition( g_hBSPFile, 4 ); -} - - -//----------------------------------------------------------------------------- -// Adds the occluder lump... -//----------------------------------------------------------------------------- -static void AddOcclusionLump( ) -{ - g_Lumps.size[LUMP_OCCLUSION] = 0; // mark it written - - int nOccluderCount = g_OccluderData.Count(); - int nOccluderPolyDataCount = g_OccluderPolyData.Count(); - int nOccluderVertexIndices = g_OccluderVertexIndices.Count(); - - int nLumpLength = nOccluderCount * sizeof(doccluderdata_t) + - nOccluderPolyDataCount * sizeof(doccluderpolydata_t) + - nOccluderVertexIndices * sizeof(int) + - 3 * sizeof(int); - - lump_t *lump = &g_pBSPHeader->lumps[LUMP_OCCLUSION]; - - lump->fileofs = g_pFileSystem->Tell( g_hBSPFile ); - lump->filelen = nLumpLength; - lump->version = LUMP_OCCLUSION_VERSION; - lump->fourCC[0] = ( char )0; - lump->fourCC[1] = ( char )0; - lump->fourCC[2] = ( char )0; - lump->fourCC[3] = ( char )0; - - // Data is swapped in place, so the 'Count' variables aren't safe to use after they're written - WriteData( FIELD_INTEGER, &nOccluderCount ); - WriteData( (doccluderdata_t*)g_OccluderData.Base(), g_OccluderData.Count() ); - WriteData( FIELD_INTEGER, &nOccluderPolyDataCount ); - WriteData( (doccluderpolydata_t*)g_OccluderPolyData.Base(), g_OccluderPolyData.Count() ); - WriteData( FIELD_INTEGER, &nOccluderVertexIndices ); - WriteData( FIELD_INTEGER, (int*)g_OccluderVertexIndices.Base(), g_OccluderVertexIndices.Count() ); -} - - -//----------------------------------------------------------------------------- -// Loads the occluder lump... -//----------------------------------------------------------------------------- -static void UnserializeOcclusionLumpV2( CUtlBuffer &buf ) -{ - int nCount = buf.GetInt(); - if ( nCount ) - { - g_OccluderData.SetCount( nCount ); - buf.GetObjects( g_OccluderData.Base(), nCount ); - } - - nCount = buf.GetInt(); - if ( nCount ) - { - g_OccluderPolyData.SetCount( nCount ); - buf.GetObjects( g_OccluderPolyData.Base(), nCount ); - } - - nCount = buf.GetInt(); - if ( nCount ) - { - if ( g_bSwapOnLoad ) - { - g_Swap.SwapBufferToTargetEndian( (int*)buf.PeekGet(), (int*)buf.PeekGet(), nCount ); - } - g_OccluderVertexIndices.SetCount( nCount ); - buf.Get( g_OccluderVertexIndices.Base(), nCount * sizeof(g_OccluderVertexIndices[0]) ); - } -} - - -static void LoadOcclusionLump() -{ - g_OccluderData.RemoveAll(); - g_OccluderPolyData.RemoveAll(); - g_OccluderVertexIndices.RemoveAll(); - - int length, ofs; - - g_Lumps.bLumpParsed[LUMP_OCCLUSION] = true; - - length = g_pBSPHeader->lumps[LUMP_OCCLUSION].filelen; - ofs = g_pBSPHeader->lumps[LUMP_OCCLUSION].fileofs; - - CUtlBuffer buf( (byte *)g_pBSPHeader + ofs, length, CUtlBuffer::READ_ONLY ); - buf.ActivateByteSwapping( g_bSwapOnLoad ); - switch ( g_pBSPHeader->lumps[LUMP_OCCLUSION].version ) - { - case 2: - UnserializeOcclusionLumpV2( buf ); - break; - - case 0: - break; - - default: - Error("Unknown occlusion lump version!\n"); - break; - } -} - - -/* -=============== -CompressVis - -=============== -*/ -int CompressVis (byte *vis, byte *dest) -{ - int j; - int rep; - int visrow; - byte *dest_p; - - dest_p = dest; -// visrow = (r_numvisleafs + 7)>>3; - visrow = (dvis->numclusters + 7)>>3; - - for (j=0 ; j>3; - row = (dvis->numclusters+7)>>3; - out = decompressed; - - do - { - if (*in) - { - *out++ = *in++; - continue; - } - - c = in[1]; - if (!c) - Error ("DecompressVis: 0 repeat"); - in += 2; - if ((out - decompressed) + c > row) - { - c = row - (out - decompressed); - Warning( "warning: Vis decompression overrun\n" ); - } - while (c) - { - *out++ = 0; - c--; - } - } while (out - decompressed < row); -} - -//----------------------------------------------------------------------------- -// Lump-specific swap functions -//----------------------------------------------------------------------------- -struct swapcollideheader_t -{ - DECLARE_BYTESWAP_DATADESC(); - int size; - int vphysicsID; - short version; - short modelType; -}; - -struct swapcompactsurfaceheader_t : swapcollideheader_t -{ - DECLARE_BYTESWAP_DATADESC(); - int surfaceSize; - Vector dragAxisAreas; - int axisMapSize; -}; - -struct swapmoppsurfaceheader_t : swapcollideheader_t -{ - DECLARE_BYTESWAP_DATADESC(); - int moppSize; -}; - -BEGIN_BYTESWAP_DATADESC( swapcollideheader_t ) - DEFINE_FIELD( size, FIELD_INTEGER ), - DEFINE_FIELD( vphysicsID, FIELD_INTEGER ), - DEFINE_FIELD( version, FIELD_SHORT ), - DEFINE_FIELD( modelType, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC_( swapcompactsurfaceheader_t, swapcollideheader_t ) - DEFINE_FIELD( surfaceSize, FIELD_INTEGER ), - DEFINE_FIELD( dragAxisAreas, FIELD_VECTOR ), - DEFINE_FIELD( axisMapSize, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC_( swapmoppsurfaceheader_t, swapcollideheader_t ) - DEFINE_FIELD( moppSize, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - - -static void SwapPhyscollideLump( byte *pDestBase, byte *pSrcBase, unsigned int &count ) -{ - IPhysicsCollision *physcollision = NULL; - CSysModule *pPhysicsModule = g_pFullFileSystem->LoadModule( "vphysics.dll" ); - if ( pPhysicsModule ) - { - CreateInterfaceFn physicsFactory = Sys_GetFactory( pPhysicsModule ); - if ( physicsFactory ) - { - physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); - } - } - - if ( !physcollision ) - { - Warning("!!! WARNING: Can't swap the physcollide lump!\n" ); - return; - } - - // physics data is variable length. The last physmodel is a NULL pointer - // with modelIndex -1, dataSize -1 - dphysmodel_t *pPhysModel; - byte *pSrc = pSrcBase; - - // first the src chunks have to be aligned properly - // swap increases size, allocate enough expansion room - byte *pSrcAlignedBase = (byte*)malloc( 2*count ); - byte *basePtr = pSrcAlignedBase; - byte *pSrcAligned = pSrcAlignedBase; - - do - { - if ( g_bSwapOnLoad ) - { - g_Swap.SwapFieldsToTargetEndian( (dphysmodel_t*)pSrcAligned, (dphysmodel_t*)pSrc ); - } - else - { - Q_memcpy( pSrcAligned, pSrc, sizeof(dphysmodel_t) ); - } - pPhysModel = (dphysmodel_t*)pSrcAligned; - - pSrc += sizeof(dphysmodel_t); - pSrcAligned += sizeof(dphysmodel_t); - - if ( pPhysModel->dataSize > 0 ) - { - // Align the collide headers - for ( int i = 0; i < pPhysModel->solidCount; ++i ) - { - // Get data size - int size; - Q_memcpy( &size, pSrc, sizeof(int) ); - if ( g_bSwapOnLoad ) - size = SwapLong( size ); - - // Fixup size - int padBytes = 0; - if ( size % 4 != 0 ) - { - padBytes = ( 4 - size % 4 ); - count += padBytes; - pPhysModel->dataSize += padBytes; - } - - // Copy data and size into alligned buffer - int newsize = size + padBytes; - if ( g_bSwapOnLoad ) - newsize = SwapLong( newsize ); - - Q_memcpy( pSrcAligned, &newsize, sizeof(int) ); - Q_memcpy( pSrcAligned + sizeof(int), pSrc + sizeof(int), size ); - pSrcAligned += size + padBytes + sizeof(int); - pSrc += size + sizeof(int); - } - - int padBytes = 0; - int dataSize = pPhysModel->dataSize + pPhysModel->keydataSize; - Q_memcpy( pSrcAligned, pSrc, pPhysModel->keydataSize ); - pSrc += pPhysModel->keydataSize; - pSrcAligned += pPhysModel->keydataSize; - if ( dataSize % 4 != 0 ) - { - // Next chunk will be unaligned - padBytes = ( 4 - dataSize % 4 ); - pPhysModel->keydataSize += padBytes; - count += padBytes; - Q_memset( pSrcAligned, 0, padBytes ); - pSrcAligned += padBytes; - } - } - } while ( pPhysModel->dataSize > 0 ); - - // Now the data can be swapped properly - pSrcBase = pSrcAlignedBase; - pSrc = pSrcBase; - byte *pDest = pDestBase; - - do - { - // src headers are in native format - pPhysModel = (dphysmodel_t*)pSrc; - if ( g_bSwapOnWrite ) - { - g_Swap.SwapFieldsToTargetEndian( (dphysmodel_t*)pDest, (dphysmodel_t*)pSrc ); - } - else - { - Q_memcpy( pDest, pSrc, sizeof(dphysmodel_t) ); - } - - pSrc += sizeof(dphysmodel_t); - pDest += sizeof(dphysmodel_t); - - pSrcBase = pSrc; - pDestBase = pDest; - - if ( pPhysModel->dataSize > 0 ) - { - vcollide_t collide = {0}; - int dataSize = pPhysModel->dataSize + pPhysModel->keydataSize; - - if ( g_bSwapOnWrite ) - { - // Load the collide data - physcollision->VCollideLoad( &collide, pPhysModel->solidCount, (const char *)pSrc, dataSize, false ); - } - - int *offsets = new int[ pPhysModel->solidCount ]; - - // Swap the collision data headers - for ( int i = 0; i < pPhysModel->solidCount; ++i ) - { - int headerSize = 0; - swapcollideheader_t *baseHdr = (swapcollideheader_t*)pSrc; - short modelType = baseHdr->modelType; - if ( g_bSwapOnLoad ) - { - g_Swap.SwapBufferToTargetEndian( &modelType ); - } - - if ( modelType == 0 ) // COLLIDE_POLY - { - headerSize = sizeof(swapcompactsurfaceheader_t); - swapcompactsurfaceheader_t swapHdr; - Q_memcpy( &swapHdr, pSrc, headerSize ); - g_Swap.SwapFieldsToTargetEndian( &swapHdr, &swapHdr ); - Q_memcpy( pDest, &swapHdr, headerSize ); - } - else if ( modelType == 1 ) // COLLIDE_MOPP - { - // The PC still unserializes these, but we don't support them - if ( g_bSwapOnWrite ) - { - collide.solids[i] = NULL; - } - - headerSize = sizeof(swapmoppsurfaceheader_t); - swapmoppsurfaceheader_t swapHdr; - Q_memcpy( &swapHdr, pSrc, headerSize ); - g_Swap.SwapFieldsToTargetEndian( &swapHdr, &swapHdr ); - Q_memcpy( pDest, &swapHdr, headerSize ); - - } - else - { - // Shouldn't happen - Assert( 0 ); - } - - if ( g_bSwapOnLoad ) - { - // src needs the native header data to load the vcollides - Q_memcpy( pSrc, pDest, headerSize ); - } - // HACK: Need either surfaceSize or moppSize - both sit at the same offset in the structure - swapmoppsurfaceheader_t *hdr = (swapmoppsurfaceheader_t*)pSrc; - pSrc += hdr->size + sizeof(int); - pDest += hdr->size + sizeof(int); - offsets[i] = hdr->size; - } - - pSrc = pSrcBase; - pDest = pDestBase; - if ( g_bSwapOnLoad ) - { - physcollision->VCollideLoad( &collide, pPhysModel->solidCount, (const char *)pSrc, dataSize, true ); - } - - // Write out the ledge tree data - for ( int i = 0; i < pPhysModel->solidCount; ++i ) - { - if ( collide.solids[i] ) - { - // skip over the size member - pSrc += sizeof(int); - pDest += sizeof(int); - int offset = physcollision->CollideWrite( (char*)pDest, collide.solids[i], g_bSwapOnWrite ); - pSrc += offset; - pDest += offset; - } - else - { - pSrc += offsets[i] + sizeof(int); - pDest += offsets[i] + sizeof(int); - } - } - - // copy the keyvalues data - Q_memcpy( pDest, pSrc, pPhysModel->keydataSize ); - pDest += pPhysModel->keydataSize; - pSrc += pPhysModel->keydataSize; - - // Free the memory - physcollision->VCollideUnload( &collide ); - delete [] offsets; - } - - // avoid infinite loop on badly formed file - if ( (pSrc - basePtr) > count ) - break; - - } while ( pPhysModel->dataSize > 0 ); - - free( pSrcAlignedBase ); -} - - -// UNDONE: This code is not yet tested. -static void SwapPhysdispLump( byte *pDest, byte *pSrc, int count ) -{ - // the format of this lump is one unsigned short dispCount, then dispCount unsigned shorts of sizes - // followed by an array of variable length (each element is the length of the corresponding entry in the - // previous table) byte-stream data structure of the displacement collision models - // these byte-stream structs are endian-neutral because each element is byte-sized - unsigned short dispCount = *(unsigned short*)pSrc; - if ( g_bSwapOnLoad ) - { - g_Swap.SwapBufferToTargetEndian( &dispCount ); - } - g_Swap.SwapBufferToTargetEndian( (unsigned short*)pDest, (unsigned short*)pSrc, dispCount + 1 ); - - const int nBytes = (dispCount + 1) * sizeof( unsigned short ); - pSrc += nBytes; - pDest += nBytes; - count -= nBytes; - - g_Swap.SwapBufferToTargetEndian( pDest, pSrc, count ); -} - - -static void SwapVisibilityLump( byte *pDest, byte *pSrc, int count ) -{ - int firstInt = *(int*)pSrc; - if ( g_bSwapOnLoad ) - { - g_Swap.SwapBufferToTargetEndian( &firstInt ); - } - int intCt = firstInt * 2 + 1; - const int hdrSize = intCt * sizeof(int); - g_Swap.SwapBufferToTargetEndian( (int*)pDest, (int*)pSrc, intCt ); - g_Swap.SwapBufferToTargetEndian( pDest + hdrSize, pSrc + hdrSize, count - hdrSize ); -} - -//============================================================================= -void Lumps_Init( void ) -{ - memset( &g_Lumps, 0, sizeof(g_Lumps) ); -} - -int LumpVersion( int lump ) -{ - return g_pBSPHeader->lumps[lump].version; -} - -bool HasLump( int lump ) -{ - return g_pBSPHeader->lumps[lump].filelen > 0; -} - -void ValidateLump( int lump, int length, int size, int forceVersion ) -{ - if ( length % size ) - { - Error( "ValidateLump: odd size for lump %d", lump ); - } - - if ( forceVersion >= 0 && forceVersion != g_pBSPHeader->lumps[lump].version ) - { - Error( "ValidateLump: old version for lump %d in map!", lump ); - } -} - -//----------------------------------------------------------------------------- -// Add Lumps of integral types without datadescs -//----------------------------------------------------------------------------- -template< class T > -int CopyLumpInternal( int fieldType, int lump, T *dest, int forceVersion ) -{ - g_Lumps.bLumpParsed[lump] = true; - - // Vectors are passed in as floats - int fieldSize = ( fieldType == FIELD_VECTOR ) ? sizeof(Vector) : sizeof(T); - unsigned int length = g_pBSPHeader->lumps[lump].filelen; - unsigned int ofs = g_pBSPHeader->lumps[lump].fileofs; - - // count must be of the integral type - unsigned int count = length / sizeof(T); - - ValidateLump( lump, length, fieldSize, forceVersion ); - - if ( g_bSwapOnLoad ) - { - switch( lump ) - { - case LUMP_VISIBILITY: - SwapVisibilityLump( (byte*)dest, ((byte*)g_pBSPHeader + ofs), count ); - break; - - case LUMP_PHYSCOLLIDE: - // SwapPhyscollideLump may change size - SwapPhyscollideLump( (byte*)dest, ((byte*)g_pBSPHeader + ofs), count ); - length = count; - break; - - case LUMP_PHYSDISP: - SwapPhysdispLump( (byte*)dest, ((byte*)g_pBSPHeader + ofs), count ); - break; - - default: - g_Swap.SwapBufferToTargetEndian( dest, (T*)((byte*)g_pBSPHeader + ofs), count ); - break; - } - } - else - { - memcpy( dest, (byte*)g_pBSPHeader + ofs, length ); - } - - // Return actual count of elements - return length / fieldSize; -} - -template< class T > -int CopyLump( int fieldType, int lump, T *dest, int forceVersion = -1 ) -{ - return CopyLumpInternal( fieldType, lump, dest, forceVersion ); -} - -template< class T > -void CopyLump( int fieldType, int lump, CUtlVector &dest, int forceVersion = -1 ) -{ - Assert( fieldType != FIELD_VECTOR ); // TODO: Support this if necessary - dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); - CopyLumpInternal( fieldType, lump, dest.Base(), forceVersion ); -} - -template< class T > -void CopyOptionalLump( int fieldType, int lump, CUtlVector &dest, int forceVersion = -1 ) -{ - // not fatal if not present - if ( !HasLump( lump ) ) - return; - - dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); - CopyLumpInternal( fieldType, lump, dest.Base(), forceVersion ); -} - -template< class T > -int CopyVariableLump( int fieldType, int lump, void **dest, int forceVersion = -1 ) -{ - int length = g_pBSPHeader->lumps[lump].filelen; - *dest = malloc( length ); - - return CopyLumpInternal( fieldType, lump, (T*)*dest, forceVersion ); -} - -//----------------------------------------------------------------------------- -// Add Lumps of object types with datadescs -//----------------------------------------------------------------------------- -template< class T > -int CopyLumpInternal( int lump, T *dest, int forceVersion ) -{ - g_Lumps.bLumpParsed[lump] = true; - - unsigned int length = g_pBSPHeader->lumps[lump].filelen; - unsigned int ofs = g_pBSPHeader->lumps[lump].fileofs; - unsigned int count = length / sizeof(T); - - ValidateLump( lump, length, sizeof(T), forceVersion ); - - if ( g_bSwapOnLoad ) - { - g_Swap.SwapFieldsToTargetEndian( dest, (T*)((byte*)g_pBSPHeader + ofs), count ); - } - else - { - memcpy( dest, (byte*)g_pBSPHeader + ofs, length ); - } - - return count; -} - -template< class T > -int CopyLump( int lump, T *dest, int forceVersion = -1 ) -{ - return CopyLumpInternal( lump, dest, forceVersion ); -} - -template< class T > -void CopyLump( int lump, CUtlVector &dest, int forceVersion = -1 ) -{ - dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); - CopyLumpInternal( lump, dest.Base(), forceVersion ); -} - -template< class T > -void CopyOptionalLump( int lump, CUtlVector &dest, int forceVersion = -1 ) -{ - // not fatal if not present - if ( !HasLump( lump ) ) - return; - - dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); - CopyLumpInternal( lump, dest.Base(), forceVersion ); -} - -template< class T > -int CopyVariableLump( int lump, void **dest, int forceVersion = -1 ) -{ - int length = g_pBSPHeader->lumps[lump].filelen; - *dest = malloc( length ); - - return CopyLumpInternal( lump, (T*)*dest, forceVersion ); -} - -//----------------------------------------------------------------------------- -// Add/Write unknown lumps -//----------------------------------------------------------------------------- -void Lumps_Parse( void ) -{ - int i; - - for ( i = 0; i < HEADER_LUMPS; i++ ) - { - if ( !g_Lumps.bLumpParsed[i] && g_pBSPHeader->lumps[i].filelen ) - { - g_Lumps.size[i] = CopyVariableLump( FIELD_CHARACTER, i, &g_Lumps.pLumps[i], -1 ); - Msg( "Reading unknown lump #%d (%d bytes)\n", i, g_Lumps.size[i] ); - } - } -} - -void Lumps_Write( void ) -{ - int i; - - for ( i = 0; i < HEADER_LUMPS; i++ ) - { - if ( g_Lumps.size[i] ) - { - Msg( "Writing unknown lump #%d (%d bytes)\n", i, g_Lumps.size[i] ); - AddLump( i, (byte*)g_Lumps.pLumps[i], g_Lumps.size[i] ); - } - if ( g_Lumps.pLumps[i] ) - { - free( g_Lumps.pLumps[i] ); - g_Lumps.pLumps[i] = NULL; - } - } -} - -int LoadLeafs( void ) -{ -#if defined( BSP_USE_LESS_MEMORY ) - dleafs = (dleaf_t*)malloc( g_pBSPHeader->lumps[LUMP_LEAFS].filelen ); -#endif - - switch ( LumpVersion( LUMP_LEAFS ) ) - { - case 0: - { - g_Lumps.bLumpParsed[LUMP_LEAFS] = true; - int length = g_pBSPHeader->lumps[LUMP_LEAFS].filelen; - int size = sizeof( dleaf_version_0_t ); - if ( length % size ) - { - Error( "odd size for LUMP_LEAFS\n" ); - } - int count = length / size; - - void *pSrcBase = ( ( byte * )g_pBSPHeader + g_pBSPHeader->lumps[LUMP_LEAFS].fileofs ); - dleaf_version_0_t *pSrc = (dleaf_version_0_t *)pSrcBase; - dleaf_t *pDst = dleafs; - - // version 0 predates HDR, build the LDR - g_LeafAmbientLightingLDR.SetCount( count ); - g_LeafAmbientIndexLDR.SetCount( count ); - - dleafambientlighting_t *pDstLeafAmbientLighting = &g_LeafAmbientLightingLDR[0]; - for ( int i = 0; i < count; i++ ) - { - g_LeafAmbientIndexLDR[i].ambientSampleCount = 1; - g_LeafAmbientIndexLDR[i].firstAmbientSample = i; - - if ( g_bSwapOnLoad ) - { - g_Swap.SwapFieldsToTargetEndian( pSrc ); - } - // pDst is a subset of pSrc; - *pDst = *( ( dleaf_t * )( void * )pSrc ); - pDstLeafAmbientLighting->cube = pSrc->m_AmbientLighting; - pDstLeafAmbientLighting->x = pDstLeafAmbientLighting->y = pDstLeafAmbientLighting->z = pDstLeafAmbientLighting->pad = 0; - pDst++; - pSrc++; - pDstLeafAmbientLighting++; - } - return count; - } - - case 1: - return CopyLump( LUMP_LEAFS, dleafs ); - - default: - Assert( 0 ); - Error( "Unknown LUMP_LEAFS version\n" ); - return 0; - } -} - -void LoadLeafAmbientLighting( int numLeafs ) -{ - if ( LumpVersion( LUMP_LEAFS ) == 0 ) - { - // an older leaf version already built the LDR ambient lighting on load - return; - } - - // old BSP with ambient, or new BSP with no lighting, convert ambient light to new format or create dummy ambient - if ( !HasLump( LUMP_LEAF_AMBIENT_INDEX ) ) - { - // a bunch of legacy maps, have these lumps with garbage versions - // expect them to be NOT the current version - if ( HasLump(LUMP_LEAF_AMBIENT_LIGHTING) ) - { - Assert( LumpVersion( LUMP_LEAF_AMBIENT_LIGHTING ) != LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); - } - if ( HasLump(LUMP_LEAF_AMBIENT_LIGHTING_HDR) ) - { - Assert( LumpVersion( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) != LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); - } - - void *pSrcBase = ( ( byte * )g_pBSPHeader + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].fileofs ); - CompressedLightCube *pSrc = NULL; - if ( HasLump( LUMP_LEAF_AMBIENT_LIGHTING ) ) - { - pSrc = (CompressedLightCube*)pSrcBase; - } - g_LeafAmbientIndexLDR.SetCount( numLeafs ); - g_LeafAmbientLightingLDR.SetCount( numLeafs ); - - void *pSrcBaseHDR = ( ( byte * )g_pBSPHeader + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].fileofs ); - CompressedLightCube *pSrcHDR = NULL; - if ( HasLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) ) - { - pSrcHDR = (CompressedLightCube*)pSrcBaseHDR; - } - g_LeafAmbientIndexHDR.SetCount( numLeafs ); - g_LeafAmbientLightingHDR.SetCount( numLeafs ); - - for ( int i = 0; i < numLeafs; i++ ) - { - g_LeafAmbientIndexLDR[i].ambientSampleCount = 1; - g_LeafAmbientIndexLDR[i].firstAmbientSample = i; - g_LeafAmbientIndexHDR[i].ambientSampleCount = 1; - g_LeafAmbientIndexHDR[i].firstAmbientSample = i; - - Q_memset( &g_LeafAmbientLightingLDR[i], 0, sizeof(g_LeafAmbientLightingLDR[i]) ); - Q_memset( &g_LeafAmbientLightingHDR[i], 0, sizeof(g_LeafAmbientLightingHDR[i]) ); - - if ( pSrc ) - { - if ( g_bSwapOnLoad ) - { - g_Swap.SwapFieldsToTargetEndian( &pSrc[i] ); - } - g_LeafAmbientLightingLDR[i].cube = pSrc[i]; - } - if ( pSrcHDR ) - { - if ( g_bSwapOnLoad ) - { - g_Swap.SwapFieldsToTargetEndian( &pSrcHDR[i] ); - } - g_LeafAmbientLightingHDR[i].cube = pSrcHDR[i]; - } - } - - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING] = true; - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX] = true; - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING_HDR] = true; - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX_HDR] = true; - } - else - { - CopyOptionalLump( LUMP_LEAF_AMBIENT_LIGHTING, g_LeafAmbientLightingLDR ); - CopyOptionalLump( LUMP_LEAF_AMBIENT_INDEX, g_LeafAmbientIndexLDR ); - CopyOptionalLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR, g_LeafAmbientLightingHDR ); - CopyOptionalLump( LUMP_LEAF_AMBIENT_INDEX_HDR, g_LeafAmbientIndexHDR ); - } -} - -void ValidateHeader( const char *filename, const dheader_t *pHeader ) -{ - if ( pHeader->ident != IDBSPHEADER ) - { - Error ("%s is not a IBSP file", filename); - } - if ( pHeader->version < MINBSPVERSION || pHeader->version > BSPVERSION ) - { - Error ("%s is version %i, not %i", filename, pHeader->version, BSPVERSION); - } -} - -//----------------------------------------------------------------------------- -// Low level BSP opener for external parsing. Parses headers, but nothing else. -// You must close the BSP, via CloseBSPFile(). -//----------------------------------------------------------------------------- -void OpenBSPFile( const char *filename ) -{ - Lumps_Init(); - - // load the file header - LoadFile( filename, (void **)&g_pBSPHeader ); - - if ( g_bSwapOnLoad ) - { - g_Swap.ActivateByteSwapping( true ); - g_Swap.SwapFieldsToTargetEndian( g_pBSPHeader ); - } - - ValidateHeader( filename, g_pBSPHeader ); - - g_MapRevision = g_pBSPHeader->mapRevision; -} - -//----------------------------------------------------------------------------- -// CloseBSPFile -//----------------------------------------------------------------------------- -void CloseBSPFile( void ) -{ - free( g_pBSPHeader ); - g_pBSPHeader = NULL; -} - -//----------------------------------------------------------------------------- -// LoadBSPFile -//----------------------------------------------------------------------------- -void LoadBSPFile( const char *filename ) -{ - OpenBSPFile( filename ); - - nummodels = CopyLump( LUMP_MODELS, dmodels ); - numvertexes = CopyLump( LUMP_VERTEXES, dvertexes ); - numplanes = CopyLump( LUMP_PLANES, dplanes ); - numleafs = LoadLeafs(); - numnodes = CopyLump( LUMP_NODES, dnodes ); - CopyLump( LUMP_TEXINFO, texinfo ); - numtexdata = CopyLump( LUMP_TEXDATA, dtexdata ); - - CopyLump( LUMP_DISPINFO, g_dispinfo ); - CopyLump( LUMP_DISP_VERTS, g_DispVerts ); - CopyLump( LUMP_DISP_TRIS, g_DispTris ); - CopyLump( FIELD_CHARACTER, LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS, g_DispLightmapSamplePositions ); - CopyLump( LUMP_FACE_MACRO_TEXTURE_INFO, g_FaceMacroTextureInfos ); - - numfaces = CopyLump(LUMP_FACES, dfaces, LUMP_FACES_VERSION); - if ( HasLump( LUMP_FACES_HDR ) ) - numfaces_hdr = CopyLump( LUMP_FACES_HDR, dfaces_hdr, LUMP_FACES_VERSION ); - else - numfaces_hdr = 0; - - CopyOptionalLump( LUMP_FACEIDS, dfaceids ); - - g_numprimitives = CopyLump( LUMP_PRIMITIVES, g_primitives ); - g_numprimverts = CopyLump( LUMP_PRIMVERTS, g_primverts ); - g_numprimindices = CopyLump( FIELD_SHORT, LUMP_PRIMINDICES, g_primindices ); - numorigfaces = CopyLump( LUMP_ORIGINALFACES, dorigfaces ); // original faces - numleaffaces = CopyLump( FIELD_SHORT, LUMP_LEAFFACES, dleaffaces ); - numleafbrushes = CopyLump( FIELD_SHORT, LUMP_LEAFBRUSHES, dleafbrushes ); - numsurfedges = CopyLump( FIELD_INTEGER, LUMP_SURFEDGES, dsurfedges ); - numedges = CopyLump( LUMP_EDGES, dedges ); - numbrushes = CopyLump( LUMP_BRUSHES, dbrushes ); - numbrushsides = CopyLump( LUMP_BRUSHSIDES, dbrushsides ); - numareas = CopyLump( LUMP_AREAS, dareas ); - numareaportals = CopyLump( LUMP_AREAPORTALS, dareaportals ); - - visdatasize = CopyLump ( FIELD_CHARACTER, LUMP_VISIBILITY, dvisdata ); - CopyOptionalLump( FIELD_CHARACTER, LUMP_LIGHTING, dlightdataLDR, LUMP_LIGHTING_VERSION ); - CopyOptionalLump( FIELD_CHARACTER, LUMP_LIGHTING_HDR, dlightdataHDR, LUMP_LIGHTING_VERSION ); - - LoadLeafAmbientLighting( numleafs ); - - CopyLump( FIELD_CHARACTER, LUMP_ENTITIES, dentdata ); - numworldlightsLDR = CopyLump( LUMP_WORLDLIGHTS, dworldlightsLDR ); - numworldlightsHDR = CopyLump( LUMP_WORLDLIGHTS_HDR, dworldlightsHDR ); - - numleafwaterdata = CopyLump( LUMP_LEAFWATERDATA, dleafwaterdata ); - g_PhysCollideSize = CopyVariableLump( FIELD_CHARACTER, LUMP_PHYSCOLLIDE, (void**)&g_pPhysCollide ); - g_PhysDispSize = CopyVariableLump( FIELD_CHARACTER, LUMP_PHYSDISP, (void**)&g_pPhysDisp ); - - g_numvertnormals = CopyLump( FIELD_VECTOR, LUMP_VERTNORMALS, (float*)g_vertnormals ); - g_numvertnormalindices = CopyLump( FIELD_SHORT, LUMP_VERTNORMALINDICES, g_vertnormalindices ); - - g_nClipPortalVerts = CopyLump( FIELD_VECTOR, LUMP_CLIPPORTALVERTS, (float*)g_ClipPortalVerts ); - g_nCubemapSamples = CopyLump( LUMP_CUBEMAPS, g_CubemapSamples ); - - CopyLump( FIELD_CHARACTER, LUMP_TEXDATA_STRING_DATA, g_TexDataStringData ); - CopyLump( FIELD_INTEGER, LUMP_TEXDATA_STRING_TABLE, g_TexDataStringTable ); - - g_nOverlayCount = CopyLump( LUMP_OVERLAYS, g_Overlays ); - g_nWaterOverlayCount = CopyLump( LUMP_WATEROVERLAYS, g_WaterOverlays ); - CopyLump( LUMP_OVERLAY_FADES, g_OverlayFades ); - - dflagslump_t flags_lump; - - if ( HasLump( LUMP_MAP_FLAGS ) ) - CopyLump ( LUMP_MAP_FLAGS, &flags_lump ); - else - memset( &flags_lump, 0, sizeof( flags_lump ) ); // default flags to 0 - - g_LevelFlags = flags_lump.m_LevelFlags; - - LoadOcclusionLump(); - - CopyLump( FIELD_SHORT, LUMP_LEAFMINDISTTOWATER, g_LeafMinDistToWater ); - - /* - int crap; - for( crap = 0; crap < g_nBSPStringTable; crap++ ) - { - Msg( "stringtable %d", ( int )crap ); - Msg( " %d:", ( int )g_BSPStringTable[crap] ); - puts( &g_BSPStringData[g_BSPStringTable[crap]] ); - puts( "\n" ); - } - */ - - // Load PAK file lump into appropriate data structure - byte *pakbuffer = NULL; - int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer ); - if ( paksize > 0 ) - { - GetPakFile()->ActivateByteSwapping( IsX360() ); - GetPakFile()->ParseFromBuffer( pakbuffer, paksize ); - } - else - { - GetPakFile()->Reset(); - } - - free( pakbuffer ); - - g_GameLumps.ParseGameLump( g_pBSPHeader ); - - // NOTE: Do NOT call CopyLump after Lumps_Parse() it parses all un-Copied lumps - // parse any additional lumps - Lumps_Parse(); - - // everything has been copied out - CloseBSPFile(); - - g_Swap.ActivateByteSwapping( false ); -} - -//----------------------------------------------------------------------------- -// Reset any state. -//----------------------------------------------------------------------------- -void UnloadBSPFile() -{ - nummodels = 0; - numvertexes = 0; - numplanes = 0; - - numleafs = 0; -#if defined( BSP_USE_LESS_MEMORY ) - if ( dleafs ) - { - free( dleafs ); - dleafs = NULL; - } -#endif - - numnodes = 0; - texinfo.Purge(); - numtexdata = 0; - - g_dispinfo.Purge(); - g_DispVerts.Purge(); - g_DispTris.Purge(); - - g_DispLightmapSamplePositions.Purge(); - g_FaceMacroTextureInfos.Purge(); - - numfaces = 0; - numfaces_hdr = 0; - - dfaceids.Purge(); - - g_numprimitives = 0; - g_numprimverts = 0; - g_numprimindices = 0; - numorigfaces = 0; - numleaffaces = 0; - numleafbrushes = 0; - numsurfedges = 0; - numedges = 0; - numbrushes = 0; - numbrushsides = 0; - numareas = 0; - numareaportals = 0; - - visdatasize = 0; - dlightdataLDR.Purge(); - dlightdataHDR.Purge(); - - g_LeafAmbientLightingLDR.Purge(); - g_LeafAmbientLightingHDR.Purge(); - g_LeafAmbientIndexHDR.Purge(); - g_LeafAmbientIndexLDR.Purge(); - - dentdata.Purge(); - numworldlightsLDR = 0; - numworldlightsHDR = 0; - - numleafwaterdata = 0; - - if ( g_pPhysCollide ) - { - free( g_pPhysCollide ); - g_pPhysCollide = NULL; - } - g_PhysCollideSize = 0; - - if ( g_pPhysDisp ) - { - free( g_pPhysDisp ); - g_pPhysDisp = NULL; - } - g_PhysDispSize = 0; - - g_numvertnormals = 0; - g_numvertnormalindices = 0; - - g_nClipPortalVerts = 0; - g_nCubemapSamples = 0; - - g_TexDataStringData.Purge(); - g_TexDataStringTable.Purge(); - - g_nOverlayCount = 0; - g_nWaterOverlayCount = 0; - - g_LevelFlags = 0; - - g_OccluderData.Purge(); - g_OccluderPolyData.Purge(); - g_OccluderVertexIndices.Purge(); - - g_GameLumps.DestroyAllGameLumps(); - - for ( int i = 0; i < HEADER_LUMPS; i++ ) - { - if ( g_Lumps.pLumps[i] ) - { - free( g_Lumps.pLumps[i] ); - g_Lumps.pLumps[i] = NULL; - } - } - - ReleasePakFileLumps(); -} - -//----------------------------------------------------------------------------- -// LoadBSPFileFilesystemOnly -//----------------------------------------------------------------------------- -void LoadBSPFile_FileSystemOnly( const char *filename ) -{ - Lumps_Init(); - - // - // load the file header - // - LoadFile( filename, (void **)&g_pBSPHeader ); - - ValidateHeader( filename, g_pBSPHeader ); - - // Load PAK file lump into appropriate data structure - byte *pakbuffer = NULL; - int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer, 1 ); - if ( paksize > 0 ) - { - GetPakFile()->ParseFromBuffer( pakbuffer, paksize ); - } - else - { - GetPakFile()->Reset(); - } - - free( pakbuffer ); - - // everything has been copied out - free( g_pBSPHeader ); - g_pBSPHeader = NULL; -} - -void ExtractZipFileFromBSP( char *pBSPFileName, char *pZipFileName ) -{ - Lumps_Init(); - - // - // load the file header - // - LoadFile( pBSPFileName, (void **)&g_pBSPHeader); - - ValidateHeader( pBSPFileName, g_pBSPHeader ); - - byte *pakbuffer = NULL; - int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer ); - if ( paksize > 0 ) - { - FILE *fp; - fp = fopen( pZipFileName, "wb" ); - if( !fp ) - { - fprintf( stderr, "can't open %s\n", pZipFileName ); - return; - } - - fwrite( pakbuffer, paksize, 1, fp ); - fclose( fp ); - } - else - { - fprintf( stderr, "zip file is zero length!\n" ); - } -} - -/* -============= -LoadBSPFileTexinfo - -Only loads the texinfo lump, so qdata can scan for textures -============= -*/ -void LoadBSPFileTexinfo( const char *filename ) -{ - FILE *f; - int length, ofs; - - g_pBSPHeader = (dheader_t*)malloc( sizeof(dheader_t) ); - - f = fopen( filename, "rb" ); - fread( g_pBSPHeader, sizeof(dheader_t), 1, f); - - ValidateHeader( filename, g_pBSPHeader ); - - length = g_pBSPHeader->lumps[LUMP_TEXINFO].filelen; - ofs = g_pBSPHeader->lumps[LUMP_TEXINFO].fileofs; - - int nCount = length / sizeof(texinfo_t); - - texinfo.Purge(); - texinfo.AddMultipleToTail( nCount ); - - fseek( f, ofs, SEEK_SET ); - fread( texinfo.Base(), length, 1, f ); - fclose( f ); - - // everything has been copied out - free( g_pBSPHeader ); - g_pBSPHeader = NULL; -} - -static void AddLumpInternal( int lumpnum, void *data, int len, int version ) -{ - lump_t *lump; - - g_Lumps.size[lumpnum] = 0; // mark it written - - lump = &g_pBSPHeader->lumps[lumpnum]; - - lump->fileofs = g_pFileSystem->Tell( g_hBSPFile ); - lump->filelen = len; - lump->version = version; - lump->fourCC[0] = ( char )0; - lump->fourCC[1] = ( char )0; - lump->fourCC[2] = ( char )0; - lump->fourCC[3] = ( char )0; - - SafeWrite( g_hBSPFile, data, len ); - - // pad out to the next dword - AlignFilePosition( g_hBSPFile, 4 ); -} - -template< class T > -static void SwapInPlace( T *pData, int count ) -{ - if ( !pData ) - return; - - // use the datadesc to swap the fields in place - g_Swap.SwapFieldsToTargetEndian( (T*)pData, pData, count ); -} - -template< class T > -static void SwapInPlace( int fieldType, T *pData, int count ) -{ - if ( !pData ) - return; - - // swap the data in place - g_Swap.SwapBufferToTargetEndian( (T*)pData, (T*)pData, count ); -} - -//----------------------------------------------------------------------------- -// Add raw data chunk to file (not a lump) -//----------------------------------------------------------------------------- -template< class T > -static void WriteData( int fieldType, T *pData, int count ) -{ - if ( g_bSwapOnWrite ) - { - SwapInPlace( fieldType, pData, count ); - } - SafeWrite( g_hBSPFile, pData, count * sizeof(T) ); -} - -template< class T > -static void WriteData( T *pData, int count ) -{ - if ( g_bSwapOnWrite ) - { - SwapInPlace( pData, count ); - } - SafeWrite( g_hBSPFile, pData, count * sizeof(T) ); -} - -//----------------------------------------------------------------------------- -// Add Lump of object types with datadescs -//----------------------------------------------------------------------------- -template< class T > -static void AddLump( int lumpnum, T *pData, int count, int version ) -{ - AddLumpInternal( lumpnum, pData, count * sizeof(T), version ); -} - -template< class T > -static void AddLump( int lumpnum, CUtlVector &data, int version ) -{ - AddLumpInternal( lumpnum, data.Base(), data.Count() * sizeof(T), version ); -} - -/* -============= -WriteBSPFile - -Swaps the bsp file in place, so it should not be referenced again -============= -*/ -void WriteBSPFile( const char *filename, char *pUnused ) -{ - if ( texinfo.Count() > MAX_MAP_TEXINFO ) - { - Error( "Map has too many texinfos (has %d, can have at most %d)\n", texinfo.Count(), MAX_MAP_TEXINFO ); - return; - } - - dheader_t outHeader; - g_pBSPHeader = &outHeader; - memset( g_pBSPHeader, 0, sizeof( dheader_t ) ); - - g_pBSPHeader->ident = IDBSPHEADER; - g_pBSPHeader->version = BSPVERSION; - g_pBSPHeader->mapRevision = g_MapRevision; - - g_hBSPFile = SafeOpenWrite( filename ); - WriteData( g_pBSPHeader ); // overwritten later - - AddLump( LUMP_PLANES, dplanes, numplanes ); - AddLump( LUMP_LEAFS, dleafs, numleafs, LUMP_LEAFS_VERSION ); - AddLump( LUMP_LEAF_AMBIENT_LIGHTING, g_LeafAmbientLightingLDR, LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); - AddLump( LUMP_LEAF_AMBIENT_INDEX, g_LeafAmbientIndexLDR ); - AddLump( LUMP_LEAF_AMBIENT_INDEX_HDR, g_LeafAmbientIndexHDR ); - AddLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR, g_LeafAmbientLightingHDR, LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); - - AddLump( LUMP_VERTEXES, dvertexes, numvertexes ); - AddLump( LUMP_NODES, dnodes, numnodes ); - AddLump( LUMP_TEXINFO, texinfo ); - AddLump( LUMP_TEXDATA, dtexdata, numtexdata ); - - AddLump( LUMP_DISPINFO, g_dispinfo ); - AddLump( LUMP_DISP_VERTS, g_DispVerts ); - AddLump( LUMP_DISP_TRIS, g_DispTris ); - AddLump( LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS, g_DispLightmapSamplePositions ); - AddLump( LUMP_FACE_MACRO_TEXTURE_INFO, g_FaceMacroTextureInfos ); - - AddLump( LUMP_PRIMITIVES, g_primitives, g_numprimitives ); - AddLump( LUMP_PRIMVERTS, g_primverts, g_numprimverts ); - AddLump( LUMP_PRIMINDICES, g_primindices, g_numprimindices ); - AddLump( LUMP_FACES, dfaces, numfaces, LUMP_FACES_VERSION ); - if (numfaces_hdr) - AddLump( LUMP_FACES_HDR, dfaces_hdr, numfaces_hdr, LUMP_FACES_VERSION ); - AddLump ( LUMP_FACEIDS, dfaceids, numfaceids ); - - AddLump( LUMP_ORIGINALFACES, dorigfaces, numorigfaces ); // original faces lump - AddLump( LUMP_BRUSHES, dbrushes, numbrushes ); - AddLump( LUMP_BRUSHSIDES, dbrushsides, numbrushsides ); - AddLump( LUMP_LEAFFACES, dleaffaces, numleaffaces ); - AddLump( LUMP_LEAFBRUSHES, dleafbrushes, numleafbrushes ); - AddLump( LUMP_SURFEDGES, dsurfedges, numsurfedges ); - AddLump( LUMP_EDGES, dedges, numedges ); - AddLump( LUMP_MODELS, dmodels, nummodels ); - AddLump( LUMP_AREAS, dareas, numareas ); - AddLump( LUMP_AREAPORTALS, dareaportals, numareaportals ); - - AddLump( LUMP_LIGHTING, dlightdataLDR, LUMP_LIGHTING_VERSION ); - AddLump( LUMP_LIGHTING_HDR, dlightdataHDR, LUMP_LIGHTING_VERSION ); - AddLump( LUMP_VISIBILITY, dvisdata, visdatasize ); - AddLump( LUMP_ENTITIES, dentdata ); - AddLump( LUMP_WORLDLIGHTS, dworldlightsLDR, numworldlightsLDR ); - AddLump( LUMP_WORLDLIGHTS_HDR, dworldlightsHDR, numworldlightsHDR ); - AddLump( LUMP_LEAFWATERDATA, dleafwaterdata, numleafwaterdata ); - - AddOcclusionLump(); - - dflagslump_t flags_lump; - flags_lump.m_LevelFlags = g_LevelFlags; - AddLump( LUMP_MAP_FLAGS, &flags_lump, 1 ); - - // NOTE: This is just for debugging, so it is disabled in release maps -#if 0 - // add the vis portals to the BSP for visualization - AddLump( LUMP_PORTALS, dportals, numportals ); - AddLump( LUMP_CLUSTERS, dclusters, numclusters ); - AddLump( LUMP_PORTALVERTS, dportalverts, numportalverts ); - AddLump( LUMP_CLUSTERPORTALS, dclusterportals, numclusterportals ); -#endif - - AddLump( LUMP_CLIPPORTALVERTS, (float*)g_ClipPortalVerts, g_nClipPortalVerts * 3 ); - AddLump( LUMP_CUBEMAPS, g_CubemapSamples, g_nCubemapSamples ); - AddLump( LUMP_TEXDATA_STRING_DATA, g_TexDataStringData ); - AddLump( LUMP_TEXDATA_STRING_TABLE, g_TexDataStringTable ); - AddLump( LUMP_OVERLAYS, g_Overlays, g_nOverlayCount ); - AddLump( LUMP_WATEROVERLAYS, g_WaterOverlays, g_nWaterOverlayCount ); - AddLump( LUMP_OVERLAY_FADES, g_OverlayFades, g_nOverlayCount ); - - if ( g_pPhysCollide ) - { - AddLump( LUMP_PHYSCOLLIDE, g_pPhysCollide, g_PhysCollideSize ); - } - - if ( g_pPhysDisp ) - { - AddLump ( LUMP_PHYSDISP, g_pPhysDisp, g_PhysDispSize ); - } - - AddLump( LUMP_VERTNORMALS, (float*)g_vertnormals, g_numvertnormals * 3 ); - AddLump( LUMP_VERTNORMALINDICES, g_vertnormalindices, g_numvertnormalindices ); - - AddLump( LUMP_LEAFMINDISTTOWATER, g_LeafMinDistToWater, numleafs ); - - AddGameLumps(); - - // Write pakfile lump to disk - WritePakFileLump(); - - // NOTE: Do NOT call AddLump after Lumps_Write() it writes all un-Added lumps - // write any additional lumps - Lumps_Write(); - - g_pFileSystem->Seek( g_hBSPFile, 0, FILESYSTEM_SEEK_HEAD ); - WriteData( g_pBSPHeader ); - g_pFileSystem->Close( g_hBSPFile ); -} - -// Generate the next clear lump filename for the bsp file -bool GenerateNextLumpFileName( const char *bspfilename, char *lumpfilename, int buffsize ) -{ - for (int i = 0; i < MAX_LUMPFILES; i++) - { - GenerateLumpFileName( bspfilename, lumpfilename, buffsize, i ); - - if ( !g_pFileSystem->FileExists( lumpfilename ) ) - return true; - } - - return false; -} - -void WriteLumpToFile( char *filename, int lump ) -{ - if ( !HasLump(lump) ) - return; - - char lumppre[MAX_PATH]; - if ( !GenerateNextLumpFileName( filename, lumppre, MAX_PATH ) ) - { - Warning( "Failed to find valid lump filename for bsp %s.\n", filename ); - return; - } - - // Open the file - FileHandle_t lumpfile = g_pFileSystem->Open(lumppre, "wb"); - if ( !lumpfile ) - { - Error ("Error opening %s! (Check for write enable)\n",filename); - return; - } - - int ofs = g_pBSPHeader->lumps[lump].fileofs; - int length = g_pBSPHeader->lumps[lump].filelen; - - // Write the header - lumpfileheader_t lumpHeader; - lumpHeader.lumpID = lump; - lumpHeader.lumpVersion = LumpVersion(lump); - lumpHeader.lumpLength = length; - lumpHeader.mapRevision = LittleLong( g_MapRevision ); - lumpHeader.lumpOffset = sizeof(lumpfileheader_t); // Lump starts after the header - SafeWrite (lumpfile, &lumpHeader, sizeof(lumpfileheader_t)); - - // Write the lump - SafeWrite (lumpfile, (byte *)g_pBSPHeader + ofs, length); -} - -void WriteLumpToFile( char *filename, int lump, int nLumpVersion, void *pBuffer, size_t nBufLen ) -{ - char lumppre[MAX_PATH]; - if ( !GenerateNextLumpFileName( filename, lumppre, MAX_PATH ) ) - { - Warning( "Failed to find valid lump filename for bsp %s.\n", filename ); - return; - } - - // Open the file - FileHandle_t lumpfile = g_pFileSystem->Open(lumppre, "wb"); - if ( !lumpfile ) - { - Error ("Error opening %s! (Check for write enable)\n",filename); - return; - } - - // Write the header - lumpfileheader_t lumpHeader; - lumpHeader.lumpID = lump; - lumpHeader.lumpVersion = nLumpVersion; - lumpHeader.lumpLength = nBufLen; - lumpHeader.mapRevision = LittleLong( g_MapRevision ); - lumpHeader.lumpOffset = sizeof(lumpfileheader_t); // Lump starts after the header - SafeWrite( lumpfile, &lumpHeader, sizeof(lumpfileheader_t)); - - // Write the lump - SafeWrite( lumpfile, pBuffer, nBufLen ); - - g_pFileSystem->Close( lumpfile ); -} - - -//============================================================================ -#define ENTRIES(a) (sizeof(a)/sizeof(*(a))) -#define ENTRYSIZE(a) (sizeof(*(a))) - -int ArrayUsage( const char *szItem, int items, int maxitems, int itemsize ) -{ - float percentage = maxitems ? items * 100.0 / maxitems : 0.0; - - Msg("%-17.17s %8i/%-8i %8i/%-8i (%4.1f%%) ", - szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage ); - if ( percentage > 80.0 ) - Msg( "VERY FULL!\n" ); - else if ( percentage > 95.0 ) - Msg( "SIZE DANGER!\n" ); - else if ( percentage > 99.9 ) - Msg( "SIZE OVERFLOW!!!\n" ); - else - Msg( "\n" ); - return items * itemsize; -} - -int GlobUsage( const char *szItem, int itemstorage, int maxstorage ) -{ - float percentage = maxstorage ? itemstorage * 100.0 / maxstorage : 0.0; - Msg("%-17.17s [variable] %8i/%-8i (%4.1f%%) ", - szItem, itemstorage, maxstorage, percentage ); - if ( percentage > 80.0 ) - Msg( "VERY FULL!\n" ); - else if ( percentage > 95.0 ) - Msg( "SIZE DANGER!\n" ); - else if ( percentage > 99.9 ) - Msg( "SIZE OVERFLOW!!!\n" ); - else - Msg( "\n" ); - return itemstorage; -} - -/* -============= -PrintBSPFileSizes - -Dumps info about current file -============= -*/ -void PrintBSPFileSizes (void) -{ - int totalmemory = 0; - -// if (!num_entities) -// ParseEntities (); - - Msg("\n"); - Msg( "%-17s %16s %16s %9s \n", "Object names", "Objects/Maxobjs", "Memory / Maxmem", "Fullness" ); - Msg( "%-17s %16s %16s %9s \n", "------------", "---------------", "---------------", "--------" ); - - totalmemory += ArrayUsage( "models", nummodels, ENTRIES(dmodels), ENTRYSIZE(dmodels) ); - totalmemory += ArrayUsage( "brushes", numbrushes, ENTRIES(dbrushes), ENTRYSIZE(dbrushes) ); - totalmemory += ArrayUsage( "brushsides", numbrushsides, ENTRIES(dbrushsides), ENTRYSIZE(dbrushsides) ); - totalmemory += ArrayUsage( "planes", numplanes, ENTRIES(dplanes), ENTRYSIZE(dplanes) ); - totalmemory += ArrayUsage( "vertexes", numvertexes, ENTRIES(dvertexes), ENTRYSIZE(dvertexes) ); - totalmemory += ArrayUsage( "nodes", numnodes, ENTRIES(dnodes), ENTRYSIZE(dnodes) ); - totalmemory += ArrayUsage( "texinfos", texinfo.Count(),MAX_MAP_TEXINFO, sizeof(texinfo_t) ); - totalmemory += ArrayUsage( "texdata", numtexdata, ENTRIES(dtexdata), ENTRYSIZE(dtexdata) ); - - totalmemory += ArrayUsage( "dispinfos", g_dispinfo.Count(), 0, sizeof( ddispinfo_t ) ); - totalmemory += ArrayUsage( "disp_verts", g_DispVerts.Count(), 0, sizeof( g_DispVerts[0] ) ); - totalmemory += ArrayUsage( "disp_tris", g_DispTris.Count(), 0, sizeof( g_DispTris[0] ) ); - totalmemory += ArrayUsage( "disp_lmsamples",g_DispLightmapSamplePositions.Count(),0,sizeof( g_DispLightmapSamplePositions[0] ) ); - - totalmemory += ArrayUsage( "faces", numfaces, ENTRIES(dfaces), ENTRYSIZE(dfaces) ); - totalmemory += ArrayUsage( "hdr faces", numfaces_hdr, ENTRIES(dfaces_hdr), ENTRYSIZE(dfaces_hdr) ); - totalmemory += ArrayUsage( "origfaces", numorigfaces, ENTRIES(dorigfaces), ENTRYSIZE(dorigfaces) ); // original faces - totalmemory += ArrayUsage( "leaves", numleafs, ENTRIES(dleafs), ENTRYSIZE(dleafs) ); - totalmemory += ArrayUsage( "leaffaces", numleaffaces, ENTRIES(dleaffaces), ENTRYSIZE(dleaffaces) ); - totalmemory += ArrayUsage( "leafbrushes", numleafbrushes, ENTRIES(dleafbrushes), ENTRYSIZE(dleafbrushes) ); - totalmemory += ArrayUsage( "areas", numareas, ENTRIES(dareas), ENTRYSIZE(dareas) ); - totalmemory += ArrayUsage( "surfedges", numsurfedges, ENTRIES(dsurfedges), ENTRYSIZE(dsurfedges) ); - totalmemory += ArrayUsage( "edges", numedges, ENTRIES(dedges), ENTRYSIZE(dedges) ); - totalmemory += ArrayUsage( "LDR worldlights", numworldlightsLDR, ENTRIES(dworldlightsLDR), ENTRYSIZE(dworldlightsLDR) ); - totalmemory += ArrayUsage( "HDR worldlights", numworldlightsHDR, ENTRIES(dworldlightsHDR), ENTRYSIZE(dworldlightsHDR) ); - - totalmemory += ArrayUsage( "leafwaterdata", numleafwaterdata,ENTRIES(dleafwaterdata), ENTRYSIZE(dleafwaterdata) ); - totalmemory += ArrayUsage( "waterstrips", g_numprimitives,ENTRIES(g_primitives), ENTRYSIZE(g_primitives) ); - totalmemory += ArrayUsage( "waterverts", g_numprimverts, ENTRIES(g_primverts), ENTRYSIZE(g_primverts) ); - totalmemory += ArrayUsage( "waterindices", g_numprimindices,ENTRIES(g_primindices),ENTRYSIZE(g_primindices) ); - totalmemory += ArrayUsage( "cubemapsamples", g_nCubemapSamples,ENTRIES(g_CubemapSamples),ENTRYSIZE(g_CubemapSamples) ); - totalmemory += ArrayUsage( "overlays", g_nOverlayCount, ENTRIES(g_Overlays), ENTRYSIZE(g_Overlays) ); - - totalmemory += GlobUsage( "LDR lightdata", dlightdataLDR.Count(), 0 ); - totalmemory += GlobUsage( "HDR lightdata", dlightdataHDR.Count(), 0 ); - totalmemory += GlobUsage( "visdata", visdatasize, sizeof(dvisdata) ); - totalmemory += GlobUsage( "entdata", dentdata.Count(), 384*1024 ); // goal is <384K - - totalmemory += ArrayUsage( "LDR ambient table", g_LeafAmbientIndexLDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientIndexLDR[0] ) ); - totalmemory += ArrayUsage( "HDR ambient table", g_LeafAmbientIndexHDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientIndexHDR[0] ) ); - totalmemory += ArrayUsage( "LDR leaf ambient lighting", g_LeafAmbientLightingLDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientLightingLDR[0] ) ); - totalmemory += ArrayUsage( "HDR leaf ambient lighting", g_LeafAmbientLightingHDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientLightingHDR[0] ) ); - - totalmemory += ArrayUsage( "occluders", g_OccluderData.Count(), 0, sizeof( g_OccluderData[0] ) ); - totalmemory += ArrayUsage( "occluder polygons", g_OccluderPolyData.Count(), 0, sizeof( g_OccluderPolyData[0] ) ); - totalmemory += ArrayUsage( "occluder vert ind",g_OccluderVertexIndices.Count(),0, sizeof( g_OccluderVertexIndices[0] ) ); - - GameLumpHandle_t h = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROPS ); - if (h != g_GameLumps.InvalidGameLump()) - totalmemory += GlobUsage( "detail props", 1, g_GameLumps.GameLumpSize(h) ); - h = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROP_LIGHTING ); - if (h != g_GameLumps.InvalidGameLump()) - totalmemory += GlobUsage( "dtl prp lght", 1, g_GameLumps.GameLumpSize(h) ); - h = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROP_LIGHTING_HDR ); - if (h != g_GameLumps.InvalidGameLump()) - totalmemory += GlobUsage( "HDR dtl prp lght", 1, g_GameLumps.GameLumpSize(h) ); - h = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); - if (h != g_GameLumps.InvalidGameLump()) - totalmemory += GlobUsage( "static props", 1, g_GameLumps.GameLumpSize(h) ); - - totalmemory += GlobUsage( "pakfile", GetPakFile()->EstimateSize(), 0 ); - // HACKHACK: Set physics limit at 4MB, in reality this is totally dynamic - totalmemory += GlobUsage( "physics", g_PhysCollideSize, 4*1024*1024 ); - totalmemory += GlobUsage( "physics terrain", g_PhysDispSize, 1*1024*1024 ); - - Msg( "\nLevel flags = %x\n", g_LevelFlags ); - - Msg( "\n" ); - - int triangleCount = 0; - - for ( int i = 0; i < numfaces; i++ ) - { - // face tris = numedges - 2 - triangleCount += dfaces[i].numedges - 2; - } - Msg("Total triangle count: %d\n", triangleCount ); - - // UNDONE: - // areaportals, portals, texdata, clusters, worldlights, portalverts -} - -/* -============= -PrintBSPPackDirectory - -Dumps a list of files stored in the bsp pack. -============= -*/ -void PrintBSPPackDirectory( void ) -{ - GetPakFile()->PrintDirectory(); -} - - -//============================================ - -int num_entities; -entity_t entities[MAX_MAP_ENTITIES]; - -void StripTrailing (char *e) -{ - char *s; - - s = e + strlen(e)-1; - while (s >= e && *s <= 32) - { - *s = 0; - s--; - } -} - -/* -================= -ParseEpair -================= -*/ -epair_t *ParseEpair (void) -{ - epair_t *e; - - e = (epair_t*)malloc (sizeof(epair_t)); - memset (e, 0, sizeof(epair_t)); - - if (strlen(token) >= MAX_KEY-1) - Error ("ParseEpar: token too long"); - e->key = copystring(token); - - GetToken (false); - if (strlen(token) >= MAX_VALUE-1) - Error ("ParseEpar: token too long"); - e->value = copystring(token); - - // strip trailing spaces - StripTrailing (e->key); - StripTrailing (e->value); - - return e; -} - - -/* -================ -ParseEntity -================ -*/ -qboolean ParseEntity (void) -{ - epair_t *e; - entity_t *mapent; - - if (!GetToken (true)) - return false; - - if (Q_stricmp (token, "{") ) - Error ("ParseEntity: { not found"); - - if (num_entities == MAX_MAP_ENTITIES) - Error ("num_entities == MAX_MAP_ENTITIES"); - - mapent = &entities[num_entities]; - num_entities++; - - do - { - if (!GetToken (true)) - Error ("ParseEntity: EOF without closing brace"); - if (!Q_stricmp (token, "}") ) - break; - e = ParseEpair (); - e->next = mapent->epairs; - mapent->epairs = e; - } while (1); - - return true; -} - -/* -================ -ParseEntities - -Parses the dentdata string into entities -================ -*/ -void ParseEntities (void) -{ - num_entities = 0; - ParseFromMemory (dentdata.Base(), dentdata.Count()); - - while (ParseEntity ()) - { - } -} - - -/* -================ -UnparseEntities - -Generates the dentdata string from all the entities -================ -*/ -void UnparseEntities (void) -{ - epair_t *ep; - char line[2048]; - int i; - char key[1024], value[1024]; - - CUtlBuffer buffer( 0, 0, CUtlBuffer::TEXT_BUFFER ); - buffer.EnsureCapacity( 256 * 1024 ); - - for (i=0 ; inext) - { - strcpy (key, ep->key); - StripTrailing (key); - strcpy (value, ep->value); - StripTrailing (value); - - sprintf(line, "\"%s\" \"%s\"\n", key, value); - buffer.PutString( line ); - } - buffer.PutString("}\n"); - } - int entdatasize = buffer.TellPut()+1; - - dentdata.SetSize( entdatasize ); - memcpy( dentdata.Base(), buffer.Base(), entdatasize-1 ); - dentdata[entdatasize-1] = 0; -} - -void PrintEntity (entity_t *ent) -{ - epair_t *ep; - - Msg ("------- entity %p -------\n", ent); - for (ep=ent->epairs ; ep ; ep=ep->next) - { - Msg ("%s = %s\n", ep->key, ep->value); - } - -} - -void SetKeyValue(entity_t *ent, const char *key, const char *value) -{ - epair_t *ep; - - for (ep=ent->epairs ; ep ; ep=ep->next) - if (!Q_stricmp (ep->key, key) ) - { - free (ep->value); - ep->value = copystring(value); - return; - } - ep = (epair_t*)malloc (sizeof(*ep)); - ep->next = ent->epairs; - ent->epairs = ep; - ep->key = copystring(key); - ep->value = copystring(value); -} - -char *ValueForKey (entity_t *ent, char *key) -{ - for (epair_t *ep=ent->epairs ; ep ; ep=ep->next) - if (!Q_stricmp (ep->key, key) ) - return ep->value; - return ""; -} - -vec_t FloatForKey (entity_t *ent, char *key) -{ - char *k = ValueForKey (ent, key); - return atof(k); -} - -vec_t FloatForKeyWithDefault (entity_t *ent, char *key, float default_value) -{ - for (epair_t *ep=ent->epairs ; ep ; ep=ep->next) - if (!Q_stricmp (ep->key, key) ) - return atof( ep->value ); - return default_value; -} - - - -int IntForKey (entity_t *ent, char *key) -{ - char *k = ValueForKey (ent, key); - return atol(k); -} - -int IntForKeyWithDefault(entity_t *ent, char *key, int nDefault ) -{ - char *k = ValueForKey (ent, key); - if ( !k[0] ) - return nDefault; - return atol(k); -} - -void GetVectorForKey (entity_t *ent, char *key, Vector& vec) -{ - - char *k = ValueForKey (ent, key); -// scanf into doubles, then assign, so it is vec_t size independent - double v1, v2, v3; - v1 = v2 = v3 = 0; - sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); - vec[0] = v1; - vec[1] = v2; - vec[2] = v3; -} - -void GetVector2DForKey (entity_t *ent, char *key, Vector2D& vec) -{ - double v1, v2; - - char *k = ValueForKey (ent, key); -// scanf into doubles, then assign, so it is vec_t size independent - v1 = v2 = 0; - sscanf (k, "%lf %lf", &v1, &v2); - vec[0] = v1; - vec[1] = v2; -} - -void GetAnglesForKey (entity_t *ent, char *key, QAngle& angle) -{ - char *k; - double v1, v2, v3; - - k = ValueForKey (ent, key); -// scanf into doubles, then assign, so it is vec_t size independent - v1 = v2 = v3 = 0; - sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); - angle[0] = v1; - angle[1] = v2; - angle[2] = v3; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void BuildFaceCalcWindingData( dface_t *pFace, int *points ) -{ - for( int i = 0; i < pFace->numedges; i++ ) - { - int eIndex = dsurfedges[pFace->firstedge+i]; - if( eIndex < 0 ) - { - points[i] = dedges[-eIndex].v[1]; - } - else - { - points[i] = dedges[eIndex].v[0]; - } - } -} - - -void TriStripToTriList( - unsigned short const *pTriStripIndices, - int nTriStripIndices, - unsigned short **pTriListIndices, - int *pnTriListIndices ) -{ - int nMaxTriListIndices = (nTriStripIndices - 2) * 3; - *pTriListIndices = new unsigned short[ nMaxTriListIndices ]; - *pnTriListIndices = 0; - - for( int i=0; i < nTriStripIndices - 2; i++ ) - { - if( pTriStripIndices[i] == pTriStripIndices[i+1] || - pTriStripIndices[i] == pTriStripIndices[i+2] || - pTriStripIndices[i+1] == pTriStripIndices[i+2] ) - { - } - else - { - // Flip odd numbered tris.. - if( i & 1 ) - { - (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+2]; - (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+1]; - (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i]; - } - else - { - (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i]; - (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+1]; - (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+2]; - } - } - } -} - - -void CalcTextureCoordsAtPoints( - float const texelsPerWorldUnits[2][4], - int const subtractOffset[2], - Vector const *pPoints, - int const nPoints, - Vector2D *pCoords ) -{ - for( int i=0; i < nPoints; i++ ) - { - for( int iCoord=0; iCoord < 2; iCoord++ ) - { - float *pDestCoord = &pCoords[i][iCoord]; - - *pDestCoord = 0; - for( int iDot=0; iDot < 3; iDot++ ) - *pDestCoord += pPoints[i][iDot] * texelsPerWorldUnits[iCoord][iDot]; - - *pDestCoord += texelsPerWorldUnits[iCoord][3]; - *pDestCoord -= subtractOffset[iCoord]; - } - } -} - - -/* -================ -CalcFaceExtents - -Fills in s->texmins[] and s->texsize[] -================ -*/ -void CalcFaceExtents(dface_t *s, int lightmapTextureMinsInLuxels[2], int lightmapTextureSizeInLuxels[2]) -{ - vec_t mins[2], maxs[2], val=0; - int i,j, e=0; - dvertex_t *v=NULL; - texinfo_t *tex=NULL; - - mins[0] = mins[1] = 1e24; - maxs[0] = maxs[1] = -1e24; - - tex = &texinfo[s->texinfo]; - - for (i=0 ; inumedges ; i++) - { - e = dsurfedges[s->firstedge+i]; - if (e >= 0) - v = dvertexes + dedges[e].v[0]; - else - v = dvertexes + dedges[-e].v[1]; - - for (j=0 ; j<2 ; j++) - { - val = v->point[0] * tex->lightmapVecsLuxelsPerWorldUnits[j][0] + - v->point[1] * tex->lightmapVecsLuxelsPerWorldUnits[j][1] + - v->point[2] * tex->lightmapVecsLuxelsPerWorldUnits[j][2] + - tex->lightmapVecsLuxelsPerWorldUnits[j][3]; - if (val < mins[j]) - mins[j] = val; - if (val > maxs[j]) - maxs[j] = val; - } - } - - int nMaxLightmapDim = (s->dispinfo == -1) ? MAX_LIGHTMAP_DIM_WITHOUT_BORDER : MAX_DISP_LIGHTMAP_DIM_WITHOUT_BORDER; - for (i=0 ; i<2 ; i++) - { - mins[i] = ( float )floor( mins[i] ); - maxs[i] = ( float )ceil( maxs[i] ); - - lightmapTextureMinsInLuxels[i] = ( int )mins[i]; - lightmapTextureSizeInLuxels[i] = ( int )( maxs[i] - mins[i] ); - if( lightmapTextureSizeInLuxels[i] > nMaxLightmapDim + 1 ) - { - Vector point = vec3_origin; - for (int j=0 ; jnumedges ; j++) - { - e = dsurfedges[s->firstedge+j]; - v = (e<0)?dvertexes + dedges[-e].v[1] : dvertexes + dedges[e].v[0]; - point += v->point; - Warning( "Bad surface extents point: %f %f %f\n", v->point.x, v->point.y, v->point.z ); - } - point *= 1.0f/s->numedges; - Error( "Bad surface extents - surface is too big to have a lightmap\n\tmaterial %s around point (%.1f %.1f %.1f)\n\t(dimension: %d, %d>%d)\n", - TexDataStringTable_GetString( dtexdata[texinfo[s->texinfo].texdata].nameStringTableID ), - point.x, point.y, point.z, - ( int )i, - ( int )lightmapTextureSizeInLuxels[i], - ( int )( nMaxLightmapDim + 1 ) - ); - } - } -} - - -void UpdateAllFaceLightmapExtents() -{ - for( int i=0; i < numfaces; i++ ) - { - dface_t *pFace = &dfaces[i]; - - if ( texinfo[pFace->texinfo].flags & (SURF_SKY|SURF_NOLIGHT) ) - continue; // non-lit texture - - CalcFaceExtents( pFace, pFace->m_LightmapTextureMinsInLuxels, pFace->m_LightmapTextureSizeInLuxels ); - } -} - - -//----------------------------------------------------------------------------- -// -// Helper class to iterate over leaves, used by tools -// -//----------------------------------------------------------------------------- - -#define TEST_EPSILON (0.03125) - - -class CToolBSPTree : public ISpatialQuery -{ -public: - // Returns the number of leaves - int LeafCount() const; - - // Enumerates the leaves along a ray, box, etc. - bool EnumerateLeavesAtPoint( Vector const& pt, ISpatialLeafEnumerator* pEnum, int context ); - bool EnumerateLeavesInBox( Vector const& mins, Vector const& maxs, ISpatialLeafEnumerator* pEnum, int context ); - bool EnumerateLeavesInSphere( Vector const& center, float radius, ISpatialLeafEnumerator* pEnum, int context ); - bool EnumerateLeavesAlongRay( Ray_t const& ray, ISpatialLeafEnumerator* pEnum, int context ); -}; - - -//----------------------------------------------------------------------------- -// Returns the number of leaves -//----------------------------------------------------------------------------- - -int CToolBSPTree::LeafCount() const -{ - return numleafs; -} - - -//----------------------------------------------------------------------------- -// Enumerates the leaves at a point -//----------------------------------------------------------------------------- - -bool CToolBSPTree::EnumerateLeavesAtPoint( Vector const& pt, - ISpatialLeafEnumerator* pEnum, int context ) -{ - int node = 0; - while( node >= 0 ) - { - dnode_t* pNode = &dnodes[node]; - dplane_t* pPlane = &dplanes[pNode->planenum]; - - if (DotProduct( pPlane->normal, pt ) <= pPlane->dist) - { - node = pNode->children[1]; - } - else - { - node = pNode->children[0]; - } - } - - return pEnum->EnumerateLeaf( - node - 1, context ); -} - - -//----------------------------------------------------------------------------- -// Enumerates the leaves in a box -//----------------------------------------------------------------------------- - -static bool EnumerateLeavesInBox_R( int node, Vector const& mins, - Vector const& maxs, ISpatialLeafEnumerator* pEnum, int context ) -{ - Vector cornermin, cornermax; - - while( node >= 0 ) - { - dnode_t* pNode = &dnodes[node]; - dplane_t* pPlane = &dplanes[pNode->planenum]; - - // Arbitrary split plane here - for (int i = 0; i < 3; ++i) - { - if (pPlane->normal[i] >= 0) - { - cornermin[i] = mins[i]; - cornermax[i] = maxs[i]; - } - else - { - cornermin[i] = maxs[i]; - cornermax[i] = mins[i]; - } - } - - if ( (DotProduct( pPlane->normal, cornermax ) - pPlane->dist) <= -TEST_EPSILON ) - { - node = pNode->children[1]; - } - else if ( (DotProduct( pPlane->normal, cornermin ) - pPlane->dist) >= TEST_EPSILON ) - { - node = pNode->children[0]; - } - else - { - if (!EnumerateLeavesInBox_R( pNode->children[0], mins, maxs, pEnum, context )) - { - return false; - } - - return EnumerateLeavesInBox_R( pNode->children[1], mins, maxs, pEnum, context ); - } - } - - return pEnum->EnumerateLeaf( - node - 1, context ); -} - -bool CToolBSPTree::EnumerateLeavesInBox( Vector const& mins, Vector const& maxs, - ISpatialLeafEnumerator* pEnum, int context ) -{ - return EnumerateLeavesInBox_R( 0, mins, maxs, pEnum, context ); -} - -//----------------------------------------------------------------------------- -// Enumerate leaves within a sphere -//----------------------------------------------------------------------------- - -static bool EnumerateLeavesInSphere_R( int node, Vector const& origin, - float radius, ISpatialLeafEnumerator* pEnum, int context ) -{ - while( node >= 0 ) - { - dnode_t* pNode = &dnodes[node]; - dplane_t* pPlane = &dplanes[pNode->planenum]; - - if (DotProduct( pPlane->normal, origin ) + radius - pPlane->dist <= -TEST_EPSILON ) - { - node = pNode->children[1]; - } - else if (DotProduct( pPlane->normal, origin ) - radius - pPlane->dist >= TEST_EPSILON ) - { - node = pNode->children[0]; - } - else - { - if (!EnumerateLeavesInSphere_R( pNode->children[0], - origin, radius, pEnum, context )) - { - return false; - } - - return EnumerateLeavesInSphere_R( pNode->children[1], - origin, radius, pEnum, context ); - } - } - - return pEnum->EnumerateLeaf( - node - 1, context ); -} - -bool CToolBSPTree::EnumerateLeavesInSphere( Vector const& center, float radius, ISpatialLeafEnumerator* pEnum, int context ) -{ - return EnumerateLeavesInSphere_R( 0, center, radius, pEnum, context ); -} - - -//----------------------------------------------------------------------------- -// Enumerate leaves along a ray -//----------------------------------------------------------------------------- - -static bool EnumerateLeavesAlongRay_R( int node, Ray_t const& ray, - Vector const& start, Vector const& end, ISpatialLeafEnumerator* pEnum, int context ) -{ - float front,back; - - while (node >= 0) - { - dnode_t* pNode = &dnodes[node]; - dplane_t* pPlane = &dplanes[pNode->planenum]; - - if ( pPlane->type <= PLANE_Z ) - { - front = start[pPlane->type] - pPlane->dist; - back = end[pPlane->type] - pPlane->dist; - } - else - { - front = DotProduct(start, pPlane->normal) - pPlane->dist; - back = DotProduct(end, pPlane->normal) - pPlane->dist; - } - - if (front <= -TEST_EPSILON && back <= -TEST_EPSILON) - { - node = pNode->children[1]; - } - else if (front >= TEST_EPSILON && back >= TEST_EPSILON) - { - node = pNode->children[0]; - } - else - { - // test the front side first - bool side = front < 0; - - // Compute intersection point based on the original ray - float splitfrac; - float denom = DotProduct( ray.m_Delta, pPlane->normal ); - if ( denom == 0.0f ) - { - splitfrac = 1.0f; - } - else - { - splitfrac = ( pPlane->dist - DotProduct( ray.m_Start, pPlane->normal ) ) / denom; - if (splitfrac < 0) - splitfrac = 0; - else if (splitfrac > 1) - splitfrac = 1; - } - - // Compute the split point - Vector split; - VectorMA( ray.m_Start, splitfrac, ray.m_Delta, split ); - - bool r = EnumerateLeavesAlongRay_R (pNode->children[side], ray, start, split, pEnum, context ); - if (!r) - return r; - return EnumerateLeavesAlongRay_R (pNode->children[!side], ray, split, end, pEnum, context); - } - } - - return pEnum->EnumerateLeaf( - node - 1, context ); -} - -bool CToolBSPTree::EnumerateLeavesAlongRay( Ray_t const& ray, ISpatialLeafEnumerator* pEnum, int context ) -{ - if (!ray.m_IsSwept) - { - Vector mins, maxs; - VectorAdd( ray.m_Start, ray.m_Extents, maxs ); - VectorSubtract( ray.m_Start, ray.m_Extents, mins ); - - return EnumerateLeavesInBox_R( 0, mins, maxs, pEnum, context ); - } - - // FIXME: Extruded ray not implemented yet - Assert( ray.m_IsRay ); - - Vector end; - VectorAdd( ray.m_Start, ray.m_Delta, end ); - return EnumerateLeavesAlongRay_R( 0, ray, ray.m_Start, end, pEnum, context ); -} - - -//----------------------------------------------------------------------------- -// Singleton accessor -//----------------------------------------------------------------------------- - -ISpatialQuery* ToolBSPTree() -{ - static CToolBSPTree s_ToolBSPTree; - return &s_ToolBSPTree; -} - - - -//----------------------------------------------------------------------------- -// Enumerates nodes in front to back order... -//----------------------------------------------------------------------------- - -// FIXME: Do we want this in the IBSPTree interface? - -static bool EnumerateNodesAlongRay_R( int node, Ray_t const& ray, float start, float end, - IBSPNodeEnumerator* pEnum, int context ) -{ - float front, back; - float startDotN, deltaDotN; - - while (node >= 0) - { - dnode_t* pNode = &dnodes[node]; - dplane_t* pPlane = &dplanes[pNode->planenum]; - - if ( pPlane->type <= PLANE_Z ) - { - startDotN = ray.m_Start[pPlane->type]; - deltaDotN = ray.m_Delta[pPlane->type]; - } - else - { - startDotN = DotProduct( ray.m_Start, pPlane->normal ); - deltaDotN = DotProduct( ray.m_Delta, pPlane->normal ); - } - - front = startDotN + start * deltaDotN - pPlane->dist; - back = startDotN + end * deltaDotN - pPlane->dist; - - if (front <= -TEST_EPSILON && back <= -TEST_EPSILON) - { - node = pNode->children[1]; - } - else if (front >= TEST_EPSILON && back >= TEST_EPSILON) - { - node = pNode->children[0]; - } - else - { - // test the front side first - bool side = front < 0; - - // Compute intersection point based on the original ray - float splitfrac; - if ( deltaDotN == 0.0f ) - { - splitfrac = 1.0f; - } - else - { - splitfrac = ( pPlane->dist - startDotN ) / deltaDotN; - if (splitfrac < 0.0f) - splitfrac = 0.0f; - else if (splitfrac > 1.0f) - splitfrac = 1.0f; - } - - bool r = EnumerateNodesAlongRay_R (pNode->children[side], ray, start, splitfrac, pEnum, context ); - if (!r) - return r; - - // Visit the node... - if (!pEnum->EnumerateNode( node, ray, splitfrac, context )) - return false; - - return EnumerateNodesAlongRay_R (pNode->children[!side], ray, splitfrac, end, pEnum, context); - } - } - - // Visit the leaf... - return pEnum->EnumerateLeaf( - node - 1, ray, start, end, context ); -} - - -bool EnumerateNodesAlongRay( Ray_t const& ray, IBSPNodeEnumerator* pEnum, int context ) -{ - Vector end; - VectorAdd( ray.m_Start, ray.m_Delta, end ); - return EnumerateNodesAlongRay_R( 0, ray, 0.0f, 1.0f, pEnum, context ); -} - - -//----------------------------------------------------------------------------- -// Helps us find all leaves associated with a particular cluster -//----------------------------------------------------------------------------- -CUtlVector g_ClusterLeaves; - -void BuildClusterTable( void ) -{ - int i, j; - int leafCount; - int leafList[MAX_MAP_LEAFS]; - - g_ClusterLeaves.SetCount( dvis->numclusters ); - for ( i = 0; i < dvis->numclusters; i++ ) - { - leafCount = 0; - for ( j = 0; j < numleafs; j++ ) - { - if ( dleafs[j].cluster == i ) - { - leafList[ leafCount ] = j; - leafCount++; - } - } - - g_ClusterLeaves[i].leafCount = leafCount; - if ( leafCount ) - { - g_ClusterLeaves[i].leafs.SetCount( leafCount ); - memcpy( g_ClusterLeaves[i].leafs.Base(), leafList, sizeof(int) * leafCount ); - } - } -} - -// There's a version of this in host.cpp!!! Make sure that they match. -void GetPlatformMapPath( const char *pMapPath, char *pPlatformMapPath, int dxlevel, int maxLength ) -{ - Q_StripExtension( pMapPath, pPlatformMapPath, maxLength ); - -// if( dxlevel <= 60 ) -// { -// Q_strncat( pPlatformMapPath, "_dx60", maxLength, COPY_ALL_CHARACTERS ); -// } - - Q_strncat( pPlatformMapPath, ".bsp", maxLength, COPY_ALL_CHARACTERS ); -} - -// There's a version of this in checksum_engine.cpp!!! Make sure that they match. -static bool CRC_MapFile(CRC32_t *crcvalue, const char *pszFileName) -{ - byte chunk[1024]; - lump_t *curLump; - - FileHandle_t fp = g_pFileSystem->Open( pszFileName, "rb" ); - if ( !fp ) - return false; - - // CRC across all lumps except for the Entities lump - for ( int l = 0; l < HEADER_LUMPS; ++l ) - { - if (l == LUMP_ENTITIES) - continue; - - curLump = &g_pBSPHeader->lumps[l]; - unsigned int nSize = curLump->filelen; - - g_pFileSystem->Seek( fp, curLump->fileofs, FILESYSTEM_SEEK_HEAD ); - - // Now read in 1K chunks - while ( nSize > 0 ) - { - int nBytesRead = 0; - - if ( nSize > 1024 ) - nBytesRead = g_pFileSystem->Read( chunk, 1024, fp ); - else - nBytesRead = g_pFileSystem->Read( chunk, nSize, fp ); - - // If any data was received, CRC it. - if ( nBytesRead > 0 ) - { - nSize -= nBytesRead; - CRC32_ProcessBuffer( crcvalue, chunk, nBytesRead ); - } - else - { - g_pFileSystem->Close( fp ); - return false; - } - } - } - - g_pFileSystem->Close( fp ); - return true; -} - - -void SetHDRMode( bool bHDR ) -{ - g_bHDR = bHDR; - if ( bHDR ) - { - pdlightdata = &dlightdataHDR; - g_pLeafAmbientLighting = &g_LeafAmbientLightingHDR; - g_pLeafAmbientIndex = &g_LeafAmbientIndexHDR; - pNumworldlights = &numworldlightsHDR; - dworldlights = dworldlightsHDR; -#ifdef VRAD - extern void VRadDetailProps_SetHDRMode( bool bHDR ); - VRadDetailProps_SetHDRMode( bHDR ); -#endif - } - else - { - pdlightdata = &dlightdataLDR; - g_pLeafAmbientLighting = &g_LeafAmbientLightingLDR; - g_pLeafAmbientIndex = &g_LeafAmbientIndexLDR; - pNumworldlights = &numworldlightsLDR; - dworldlights = dworldlightsLDR; -#ifdef VRAD - extern void VRadDetailProps_SetHDRMode( bool bHDR ); - VRadDetailProps_SetHDRMode( bHDR ); -#endif - } -} - -bool SwapVHV( void *pDestBase, void *pSrcBase ) -{ - byte *pDest = (byte*)pDestBase; - byte *pSrc = (byte*)pSrcBase; - - HardwareVerts::FileHeader_t *pHdr = (HardwareVerts::FileHeader_t*)( g_bSwapOnLoad ? pDest : pSrc ); - g_Swap.SwapFieldsToTargetEndian( (HardwareVerts::FileHeader_t*)pDest, (HardwareVerts::FileHeader_t*)pSrc ); - pSrc += sizeof(HardwareVerts::FileHeader_t); - pDest += sizeof(HardwareVerts::FileHeader_t); - - // This swap is pretty format specific - Assert( pHdr->m_nVersion == VHV_VERSION ); - if ( pHdr->m_nVersion != VHV_VERSION ) - return false; - - HardwareVerts::MeshHeader_t *pSrcMesh = (HardwareVerts::MeshHeader_t*)pSrc; - HardwareVerts::MeshHeader_t *pDestMesh = (HardwareVerts::MeshHeader_t*)pDest; - HardwareVerts::MeshHeader_t *pMesh = (HardwareVerts::MeshHeader_t*)( g_bSwapOnLoad ? pDest : pSrc ); - for ( int i = 0; i < pHdr->m_nMeshes; ++i, ++pMesh, ++pSrcMesh, ++pDestMesh ) - { - g_Swap.SwapFieldsToTargetEndian( pDestMesh, pSrcMesh ); - - pSrc = (byte*)pSrcBase + pMesh->m_nOffset; - pDest = (byte*)pDestBase + pMesh->m_nOffset; - - // Swap as a buffer of integers - // (source is bgra for an Intel swap to argb. PowerPC won't swap, so we need argb source. - g_Swap.SwapBufferToTargetEndian( (int*)pDest, (int*)pSrc, pMesh->m_nVertexes ); - } - return true; -} - -const char *ResolveStaticPropToModel( const char *pPropName ) -{ - // resolve back to static prop - int iProp = -1; - - // filename should be sp_???.vhv or sp_hdr_???.vhv - if ( V_strnicmp( pPropName, "sp_", 3 ) ) - { - return NULL; - } - const char *pPropNumber = V_strrchr( pPropName, '_' ); - if ( pPropNumber ) - { - sscanf( pPropNumber+1, "%d.vhv", &iProp ); - } - else - { - return NULL; - } - - // look up the prop to get to the actual model - if ( iProp < 0 || iProp >= g_StaticPropInstances.Count() ) - { - // prop out of range - return NULL; - } - int iModel = g_StaticPropInstances[iProp]; - if ( iModel < 0 || iModel >= g_StaticPropNames.Count() ) - { - // model out of range - return NULL; - } - - return g_StaticPropNames[iModel].String(); -} - -//----------------------------------------------------------------------------- -// Iterate files in pak file, distribute to converters -// pak file will be ready for serialization upon completion -//----------------------------------------------------------------------------- -void ConvertPakFileContents( const char *pInFilename ) -{ - IZip *newPakFile = IZip::CreateZip( NULL ); - - CUtlBuffer sourceBuf; - CUtlBuffer targetBuf; - bool bConverted; - CUtlVector< CUtlString > hdrFiles; - - int id = -1; - int fileSize; - while ( 1 ) - { - char relativeName[MAX_PATH]; - id = GetNextFilename( GetPakFile(), id, relativeName, sizeof( relativeName ), fileSize ); - if ( id == -1) - break; - - bConverted = false; - sourceBuf.Purge(); - targetBuf.Purge(); - - const char* pExtension = V_GetFileExtension( relativeName ); - const char* pExt = 0; - - bool bOK = ReadFileFromPak( GetPakFile(), relativeName, false, sourceBuf ); - if ( !bOK ) - { - Warning( "Failed to load '%s' from lump pak for conversion or copy in '%s'.\n", relativeName, pInFilename ); - continue; - } - - if ( pExtension && !V_stricmp( pExtension, "vtf" ) ) - { - bOK = g_pVTFConvertFunc( relativeName, sourceBuf, targetBuf, g_pCompressFunc ); - if ( !bOK ) - { - Warning( "Failed to convert '%s' in '%s'.\n", relativeName, pInFilename ); - continue; - } - - bConverted = true; - pExt = ".vtf"; - } - else if ( pExtension && !V_stricmp( pExtension, "vhv" ) ) - { - CUtlBuffer tempBuffer; - if ( g_pVHVFixupFunc ) - { - // caller supplied a fixup - const char *pModelName = ResolveStaticPropToModel( relativeName ); - if ( !pModelName ) - { - Warning( "Static Prop '%s' failed to resolve actual model in '%s'.\n", relativeName, pInFilename ); - continue; - } - - // output temp buffer may shrink, must use TellPut() to determine size - bOK = g_pVHVFixupFunc( relativeName, pModelName, sourceBuf, tempBuffer ); - if ( !bOK ) - { - Warning( "Failed to convert '%s' in '%s'.\n", relativeName, pInFilename ); - continue; - } - } - else - { - // use the source buffer as-is - tempBuffer.EnsureCapacity( sourceBuf.TellMaxPut() ); - tempBuffer.Put( sourceBuf.Base(), sourceBuf.TellMaxPut() ); - } - - // swap the VHV - targetBuf.EnsureCapacity( tempBuffer.TellPut() ); - bOK = SwapVHV( targetBuf.Base(), tempBuffer.Base() ); - if ( !bOK ) - { - Warning( "Failed to swap '%s' in '%s'.\n", relativeName, pInFilename ); - continue; - } - targetBuf.SeekPut( CUtlBuffer::SEEK_HEAD, tempBuffer.TellPut() ); - - if ( g_pCompressFunc ) - { - CUtlBuffer compressedBuffer; - targetBuf.SeekGet( CUtlBuffer::SEEK_HEAD, sizeof( HardwareVerts::FileHeader_t ) ); - bool bCompressed = g_pCompressFunc( targetBuf, compressedBuffer ); - if ( bCompressed ) - { - // copy all the header data off - CUtlBuffer headerBuffer; - headerBuffer.EnsureCapacity( sizeof( HardwareVerts::FileHeader_t ) ); - headerBuffer.Put( targetBuf.Base(), sizeof( HardwareVerts::FileHeader_t ) ); - - // reform the target with the header and then the compressed data - targetBuf.Clear(); - targetBuf.Put( headerBuffer.Base(), sizeof( HardwareVerts::FileHeader_t ) ); - targetBuf.Put( compressedBuffer.Base(), compressedBuffer.TellPut() ); - } - - targetBuf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); - } - - bConverted = true; - pExt = ".vhv"; - } - - if ( !bConverted ) - { - // straight copy - AddBufferToPak( newPakFile, relativeName, sourceBuf.Base(), sourceBuf.TellMaxPut(), false ); - } - else - { - // converted filename - V_StripExtension( relativeName, relativeName, sizeof( relativeName ) ); - V_strcat( relativeName, ".360", sizeof( relativeName ) ); - V_strcat( relativeName, pExt, sizeof( relativeName ) ); - AddBufferToPak( newPakFile, relativeName, targetBuf.Base(), targetBuf.TellMaxPut(), false ); - } - - if ( V_stristr( relativeName, ".hdr" ) || V_stristr( relativeName, "_hdr" ) ) - { - hdrFiles.AddToTail( relativeName ); - } - - DevMsg( "Created '%s' in lump pak in '%s'.\n", relativeName, pInFilename ); - } - - // strip ldr version of hdr files - for ( int i=0; iRemoveFileFromZip( ldrFileName ); - } - } - - // discard old pak in favor of new pak - IZip::ReleaseZip( s_pakFile ); - s_pakFile = newPakFile; -} - -void SetAlignedLumpPosition( int lumpnum, int alignment = LUMP_ALIGNMENT ) -{ - g_pBSPHeader->lumps[lumpnum].fileofs = AlignFilePosition( g_hBSPFile, alignment ); -} - -template< class T > -int SwapLumpToDisk( int fieldType, int lumpnum ) -{ - if ( g_pBSPHeader->lumps[lumpnum].filelen == 0 ) - return 0; - - DevMsg( "Swapping %s\n", GetLumpName( lumpnum ) ); - - // lump swap may expand, allocate enough expansion room - void *pBuffer = malloc( 2*g_pBSPHeader->lumps[lumpnum].filelen ); - - // CopyLumpInternal will handle the swap on load case - unsigned int fieldSize = ( fieldType == FIELD_VECTOR ) ? sizeof(Vector) : sizeof(T); - unsigned int count = CopyLumpInternal( fieldType, lumpnum, (T*)pBuffer, g_pBSPHeader->lumps[lumpnum].version ); - g_pBSPHeader->lumps[lumpnum].filelen = count * fieldSize; - - if ( g_bSwapOnWrite ) - { - // Swap the lump in place before writing - switch( lumpnum ) - { - case LUMP_VISIBILITY: - SwapVisibilityLump( (byte*)pBuffer, (byte*)pBuffer, count ); - break; - - case LUMP_PHYSCOLLIDE: - // SwapPhyscollideLump may change size - SwapPhyscollideLump( (byte*)pBuffer, (byte*)pBuffer, count ); - g_pBSPHeader->lumps[lumpnum].filelen = count; - break; - - case LUMP_PHYSDISP: - SwapPhysdispLump( (byte*)pBuffer, (byte*)pBuffer, count ); - break; - - default: - g_Swap.SwapBufferToTargetEndian( (T*)pBuffer, (T*)pBuffer, g_pBSPHeader->lumps[lumpnum].filelen / sizeof(T) ); - break; - } - } - - SetAlignedLumpPosition( lumpnum ); - SafeWrite( g_hBSPFile, pBuffer, g_pBSPHeader->lumps[lumpnum].filelen ); - - free( pBuffer ); - - return g_pBSPHeader->lumps[lumpnum].filelen; -} - -template< class T > -int SwapLumpToDisk( int lumpnum ) -{ - if ( g_pBSPHeader->lumps[lumpnum].filelen == 0 || g_Lumps.bLumpParsed[lumpnum] ) - return 0; - - DevMsg( "Swapping %s\n", GetLumpName( lumpnum ) ); - - // lump swap may expand, allocate enough room - void *pBuffer = malloc( 2*g_pBSPHeader->lumps[lumpnum].filelen ); - - // CopyLumpInternal will handle the swap on load case - int count = CopyLumpInternal( lumpnum, (T*)pBuffer, g_pBSPHeader->lumps[lumpnum].version ); - g_pBSPHeader->lumps[lumpnum].filelen = count * sizeof(T); - - if ( g_bSwapOnWrite ) - { - // Swap the lump in place before writing - g_Swap.SwapFieldsToTargetEndian( (T*)pBuffer, (T*)pBuffer, count ); - } - - SetAlignedLumpPosition( lumpnum ); - SafeWrite( g_hBSPFile, pBuffer, g_pBSPHeader->lumps[lumpnum].filelen ); - free( pBuffer ); - - return g_pBSPHeader->lumps[lumpnum].filelen; -} - -void SwapLeafAmbientLightingLumpToDisk() -{ - if ( HasLump( LUMP_LEAF_AMBIENT_INDEX ) || HasLump( LUMP_LEAF_AMBIENT_INDEX_HDR ) ) - { - // current version, swap in place - if ( HasLump( LUMP_LEAF_AMBIENT_INDEX_HDR ) ) - { - // write HDR - SwapLumpToDisk< dleafambientlighting_t >( LUMP_LEAF_AMBIENT_LIGHTING_HDR ); - SwapLumpToDisk< dleafambientindex_t >( LUMP_LEAF_AMBIENT_INDEX_HDR ); - - // cull LDR - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen = 0; - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen = 0; - } - else - { - // no HDR, keep LDR version - SwapLumpToDisk< dleafambientlighting_t >( LUMP_LEAF_AMBIENT_LIGHTING ); - SwapLumpToDisk< dleafambientindex_t >( LUMP_LEAF_AMBIENT_INDEX ); - } - } - else - { - // older ambient lighting version (before index) - // load older ambient lighting into memory and build ambient/index - // an older leaf version would have already built the new LDR leaf ambient/index - int numLeafs = g_pBSPHeader->lumps[LUMP_LEAFS].filelen / sizeof( dleaf_t ); - LoadLeafAmbientLighting( numLeafs ); - - if ( HasLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) ) - { - DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) ); - DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_INDEX_HDR ) ); - - // write HDR - if ( g_bSwapOnWrite ) - { - g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientLightingHDR.Base(), g_LeafAmbientLightingHDR.Count() ); - g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientIndexHDR.Base(), g_LeafAmbientIndexHDR.Count() ); - } - - SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_LIGHTING_HDR ); - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].version = LUMP_LEAF_AMBIENT_LIGHTING_VERSION; - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].filelen = g_LeafAmbientLightingHDR.Count() * sizeof( dleafambientlighting_t ); - SafeWrite( g_hBSPFile, g_LeafAmbientLightingHDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].filelen ); - - SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_INDEX_HDR ); - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX_HDR].filelen = g_LeafAmbientIndexHDR.Count() * sizeof( dleafambientindex_t ); - SafeWrite( g_hBSPFile, g_LeafAmbientIndexHDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX_HDR].filelen ); - - // mark as processed - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING_HDR] = true; - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX_HDR] = true; - - // cull LDR - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen = 0; - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen = 0; - } - else - { - // no HDR, keep LDR version - DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_LIGHTING ) ); - DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_INDEX ) ); - - if ( g_bSwapOnWrite ) - { - g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientLightingLDR.Base(), g_LeafAmbientLightingLDR.Count() ); - g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientIndexLDR.Base(), g_LeafAmbientIndexLDR.Count() ); - } - - SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_LIGHTING ); - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].version = LUMP_LEAF_AMBIENT_LIGHTING_VERSION; - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen = g_LeafAmbientLightingLDR.Count() * sizeof( dleafambientlighting_t ); - SafeWrite( g_hBSPFile, g_LeafAmbientLightingLDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen ); - - SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_INDEX ); - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen = g_LeafAmbientIndexLDR.Count() * sizeof( dleafambientindex_t ); - SafeWrite( g_hBSPFile, g_LeafAmbientIndexLDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen ); - - // mark as processed - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING] = true; - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX] = true; - } - - g_LeafAmbientLightingLDR.Purge(); - g_LeafAmbientIndexLDR.Purge(); - g_LeafAmbientLightingHDR.Purge(); - g_LeafAmbientIndexHDR.Purge(); - } -} - -void SwapLeafLumpToDisk( void ) -{ - DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAFS ) ); - - // load the leafs - int count = LoadLeafs(); - if ( g_bSwapOnWrite ) - { - g_Swap.SwapFieldsToTargetEndian( dleafs, count ); - } - - bool bOldLeafVersion = ( LumpVersion( LUMP_LEAFS ) == 0 ); - if ( bOldLeafVersion ) - { - // version has been converted in the load process - // not updating the version ye, SwapLeafAmbientLightingLumpToDisk() can detect - g_pBSPHeader->lumps[LUMP_LEAFS].filelen = count * sizeof( dleaf_t ); - } - - SetAlignedLumpPosition( LUMP_LEAFS ); - SafeWrite( g_hBSPFile, dleafs, g_pBSPHeader->lumps[LUMP_LEAFS].filelen ); - - SwapLeafAmbientLightingLumpToDisk(); - - if ( bOldLeafVersion ) - { - // version has been converted in the load process - // can now safely change - g_pBSPHeader->lumps[LUMP_LEAFS].version = 1; - } - -#if defined( BSP_USE_LESS_MEMORY ) - if ( dleafs ) - { - free( dleafs ); - dleafs = NULL; - } -#endif -} - -void SwapOcclusionLumpToDisk( void ) -{ - DevMsg( "Swapping %s\n", GetLumpName( LUMP_OCCLUSION ) ); - - LoadOcclusionLump(); - SetAlignedLumpPosition( LUMP_OCCLUSION ); - AddOcclusionLump(); -} - -void SwapPakfileLumpToDisk( const char *pInFilename ) -{ - DevMsg( "Swapping %s\n", GetLumpName( LUMP_PAKFILE ) ); - - byte *pakbuffer = NULL; - int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer ); - if ( paksize > 0 ) - { - GetPakFile()->ActivateByteSwapping( IsX360() ); - GetPakFile()->ParseFromBuffer( pakbuffer, paksize ); - - ConvertPakFileContents( pInFilename ); - } - free( pakbuffer ); - - SetAlignedLumpPosition( LUMP_PAKFILE, XBOX_DVD_SECTORSIZE ); - WritePakFileLump(); - - ReleasePakFileLumps(); -} - -void SwapGameLumpsToDisk( void ) -{ - DevMsg( "Swapping %s\n", GetLumpName( LUMP_GAME_LUMP ) ); - - g_GameLumps.ParseGameLump( g_pBSPHeader ); - SetAlignedLumpPosition( LUMP_GAME_LUMP ); - AddGameLumps(); -} - -//----------------------------------------------------------------------------- -// Generate a table of all static props, used for resolving static prop lighting -// files back to their actual mdl. -//----------------------------------------------------------------------------- -void BuildStaticPropNameTable() -{ - g_StaticPropNames.Purge(); - g_StaticPropInstances.Purge(); - - g_GameLumps.ParseGameLump( g_pBSPHeader ); - - GameLumpHandle_t hGameLump = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); - if ( hGameLump != g_GameLumps.InvalidGameLump() ) - { - int nVersion = g_GameLumps.GetGameLumpVersion( hGameLump ); - if ( nVersion < 4 ) - { - // old unsupported version - return; - } - - if ( nVersion != 4 && nVersion != 5 && nVersion != 6 ) - { - Error( "Unknown Static Prop Lump version %d!\n", nVersion ); - } - - byte *pGameLumpData = (byte *)g_GameLumps.GetGameLump( hGameLump ); - if ( pGameLumpData && g_GameLumps.GameLumpSize( hGameLump ) ) - { - // get the model dictionary - int count = ((int *)pGameLumpData)[0]; - pGameLumpData += sizeof( int ); - StaticPropDictLump_t *pStaticPropDictLump = (StaticPropDictLump_t *)pGameLumpData; - for ( int i = 0; i < count; i++ ) - { - g_StaticPropNames.AddToTail( pStaticPropDictLump[i].m_Name ); - } - pGameLumpData += count * sizeof( StaticPropDictLump_t ); - - // skip the leaf list - count = ((int *)pGameLumpData)[0]; - pGameLumpData += sizeof( int ); - pGameLumpData += count * sizeof( StaticPropLeafLump_t ); - - // get the instances - count = ((int *)pGameLumpData)[0]; - pGameLumpData += sizeof( int ); - for ( int i = 0; i < count; i++ ) - { - int propType; - if ( nVersion == 4 ) - { - propType = ((StaticPropLumpV4_t *)pGameLumpData)->m_PropType; - pGameLumpData += sizeof( StaticPropLumpV4_t ); - } - else if ( nVersion == 5 ) - { - propType = ((StaticPropLumpV5_t *)pGameLumpData)->m_PropType; - pGameLumpData += sizeof( StaticPropLumpV5_t ); - } - else - { - propType = ((StaticPropLump_t *)pGameLumpData)->m_PropType; - pGameLumpData += sizeof( StaticPropLump_t ); - } - g_StaticPropInstances.AddToTail( propType ); - } - } - } - - g_GameLumps.DestroyAllGameLumps(); -} - -int AlignBuffer( CUtlBuffer &buffer, int alignment ) -{ - unsigned int newPosition = AlignValue( buffer.TellPut(), alignment ); - int padLength = newPosition - buffer.TellPut(); - for ( int i = 0; ipLump->fileofs; - int fileOffsetB = pSortedLumpB->pLump->fileofs; - - int fileSizeA = pSortedLumpA->pLump->filelen; - int fileSizeB = pSortedLumpB->pLump->filelen; - - // invalid or empty lumps get sorted together - if ( !fileSizeA ) - { - fileOffsetA = 0; - } - if ( !fileSizeB ) - { - fileOffsetB = 0; - } - - // compare by offset, want ascending - if ( fileOffsetA < fileOffsetB ) - { - return -1; - } - else if ( fileOffsetA > fileOffsetB ) - { - return 1; - } - - return 0; -} - -bool CompressGameLump( dheader_t *pInBSPHeader, dheader_t *pOutBSPHeader, CUtlBuffer &outputBuffer, CompressFunc_t pCompressFunc ) -{ - CByteswap byteSwap; - - dgamelumpheader_t* pInGameLumpHeader = (dgamelumpheader_t*)(((byte *)pInBSPHeader) + pInBSPHeader->lumps[LUMP_GAME_LUMP].fileofs); - dgamelump_t* pInGameLump = (dgamelump_t*)(pInGameLumpHeader + 1); - - byteSwap.ActivateByteSwapping( true ); - byteSwap.SwapFieldsToTargetEndian( pInGameLumpHeader ); - byteSwap.SwapFieldsToTargetEndian( pInGameLump, pInGameLumpHeader->lumpCount ); - - unsigned int newOffset = outputBuffer.TellPut(); - outputBuffer.Put( pInGameLumpHeader, sizeof( dgamelumpheader_t ) ); - outputBuffer.Put( pInGameLump, pInGameLumpHeader->lumpCount * sizeof( dgamelump_t ) ); - - dgamelumpheader_t* pOutGameLumpHeader = (dgamelumpheader_t*)((byte *)outputBuffer.Base() + newOffset); - dgamelump_t* pOutGameLump = (dgamelump_t*)(pOutGameLumpHeader + 1); - - // add a dummy terminal gamelump - // purposely NOT updating the .filelen to reflect the compressed size, but leaving as original size - // callers use the next entry offset to determine compressed size - pOutGameLumpHeader->lumpCount++; - dgamelump_t dummyLump = { 0 }; - outputBuffer.Put( &dummyLump, sizeof( dgamelump_t ) ); - - for ( int i = 0; i < pInGameLumpHeader->lumpCount; i++ ) - { - CUtlBuffer inputBuffer; - CUtlBuffer compressedBuffer; - - pOutGameLump[i].fileofs = AlignBuffer( outputBuffer, 4 ); - - if ( pInGameLump[i].filelen ) - { - inputBuffer.SetExternalBuffer( ((byte *)pInBSPHeader) + pInGameLump[i].fileofs, pInGameLump[i].filelen, pInGameLump[i].filelen ); - - bool bCompressed = pCompressFunc( inputBuffer, compressedBuffer ); - if ( bCompressed ) - { - pOutGameLump[i].flags |= GAMELUMPFLAG_COMPRESSED; - - outputBuffer.Put( compressedBuffer.Base(), compressedBuffer.TellPut() ); - compressedBuffer.Purge(); - } - else - { - // as is - outputBuffer.Put( inputBuffer.Base(), inputBuffer.TellPut() ); - } - } - } - - // fix the dummy terminal lump - int lastLump = pOutGameLumpHeader->lumpCount-1; - pOutGameLump[lastLump].fileofs = outputBuffer.TellPut(); - - // fix the output for 360, swapping it back - byteSwap.SwapFieldsToTargetEndian( pOutGameLump, pOutGameLumpHeader->lumpCount ); - byteSwap.SwapFieldsToTargetEndian( pOutGameLumpHeader ); - - pOutBSPHeader->lumps[LUMP_GAME_LUMP].fileofs = newOffset; - pOutBSPHeader->lumps[LUMP_GAME_LUMP].filelen = outputBuffer.TellPut() - newOffset; - - return true; -} - -bool CompressBSP( CUtlBuffer &inputBuffer, CUtlBuffer &outputBuffer, CompressFunc_t pCompressFunc ) -{ - CByteswap byteSwap; - - dheader_t *pInBSPHeader = (dheader_t *)inputBuffer.Base(); - if ( pInBSPHeader->ident != BigLong( IDBSPHEADER ) || !pCompressFunc ) - { - // only compress 360 bsp's - return false; - } - - // bsp is 360, swap the header back - byteSwap.ActivateByteSwapping( true ); - byteSwap.SwapFieldsToTargetEndian( pInBSPHeader ); - - // output will be smaller, use input size as upper bound - outputBuffer.EnsureCapacity( inputBuffer.TellMaxPut() ); - outputBuffer.Put( pInBSPHeader, sizeof( dheader_t ) ); - - dheader_t *pOutBSPHeader = (dheader_t *)outputBuffer.Base(); - - // must adhere to input lump's offset order and process according to that, NOT lump num - // sort by offset order - CUtlVector< SortedLump_t > sortedLumps; - for ( int i = 0; i < HEADER_LUMPS; i++ ) - { - int iIndex = sortedLumps.AddToTail(); - sortedLumps[iIndex].lumpNum = i; - sortedLumps[iIndex].pLump = &pInBSPHeader->lumps[i]; - } - sortedLumps.Sort( SortLumpsByOffset ); - - // iterate in sorted order - for ( int i = 0; i < HEADER_LUMPS; ++i ) - { - SortedLump_t *pSortedLump = &sortedLumps[i]; - int lumpNum = pSortedLump->lumpNum; - - if ( !pSortedLump->pLump->filelen ) - { - // degenerate - pOutBSPHeader->lumps[lumpNum].fileofs = 0; - } - else - { - int alignment = 4; - if ( lumpNum == LUMP_PAKFILE ) - { - alignment = 2048; - } - unsigned int newOffset = AlignBuffer( outputBuffer, alignment ); - - // only set by compressed lumps, hides the uncompressed size - *((unsigned int *)pOutBSPHeader->lumps[lumpNum].fourCC) = 0; - - CUtlBuffer inputBuffer; - inputBuffer.SetExternalBuffer( ((byte *)pInBSPHeader) + pSortedLump->pLump->fileofs, pSortedLump->pLump->filelen, pSortedLump->pLump->filelen ); - - if ( lumpNum == LUMP_GAME_LUMP ) - { - // the game lump has to have each of its components individually compressed - CompressGameLump( pInBSPHeader, pOutBSPHeader, outputBuffer, pCompressFunc ); - } - else if ( lumpNum == LUMP_PAKFILE ) - { - // add as is - pOutBSPHeader->lumps[lumpNum].fileofs = newOffset; - outputBuffer.Put( inputBuffer.Base(), inputBuffer.TellPut() ); - } - else - { - CUtlBuffer compressedBuffer; - bool bCompressed = pCompressFunc( inputBuffer, compressedBuffer ); - if ( bCompressed ) - { - // placing the uncompressed size in the unused fourCC, will decode at runtime - *((unsigned int *)pOutBSPHeader->lumps[lumpNum].fourCC) = BigLong( inputBuffer.TellPut() ); - pOutBSPHeader->lumps[lumpNum].filelen = compressedBuffer.TellPut(); - pOutBSPHeader->lumps[lumpNum].fileofs = newOffset; - outputBuffer.Put( compressedBuffer.Base(), compressedBuffer.TellPut() ); - compressedBuffer.Purge(); - } - else - { - // add as is - pOutBSPHeader->lumps[lumpNum].fileofs = newOffset; - outputBuffer.Put( inputBuffer.Base(), inputBuffer.TellPut() ); - } - } - } - } - - // fix the output for 360, swapping it back - byteSwap.SetTargetBigEndian( true ); - byteSwap.SwapFieldsToTargetEndian( pOutBSPHeader ); - - return true; -} - -//----------------------------------------------------------------------------- -// For all lumps in a bsp: Loads the lump from file A, swaps it, writes it to file B. -// This limits the memory used for the swap process which helps the Xbox 360. -// -// NOTE: These lumps will be written to the file in exactly the order they appear here, -// so they can be shifted around if desired for file access optimization. -//----------------------------------------------------------------------------- -bool SwapBSPFile( const char *pInFilename, const char *pOutFilename, bool bSwapOnLoad, VTFConvertFunc_t pVTFConvertFunc, VHVFixupFunc_t pVHVFixupFunc, CompressFunc_t pCompressFunc ) -{ - DevMsg( "Creating %s\n", pOutFilename ); - - if ( !g_pFileSystem->FileExists( pInFilename ) ) - { - Warning( "Error! Couldn't open input file %s - BSP swap failed!\n", pInFilename ); - return false; - } - - g_hBSPFile = SafeOpenWrite( pOutFilename ); - if ( !g_hBSPFile ) - { - Warning( "Error! Couldn't open output file %s - BSP swap failed!\n", pOutFilename ); - return false; - } - - if ( !pVTFConvertFunc ) - { - Warning( "Error! Missing VTF Conversion function\n" ); - return false; - } - g_pVTFConvertFunc = pVTFConvertFunc; - - // optional VHV fixup - g_pVHVFixupFunc = pVHVFixupFunc; - - // optional compression callback - g_pCompressFunc = pCompressFunc; - - // These must be mutually exclusive - g_bSwapOnLoad = bSwapOnLoad; - g_bSwapOnWrite = !bSwapOnLoad; - - g_Swap.ActivateByteSwapping( true ); - - OpenBSPFile( pInFilename ); - - // CRC the bsp first - CRC32_t mapCRC; - CRC32_Init(&mapCRC); - if ( !CRC_MapFile( &mapCRC, pInFilename ) ) - { - Warning( "Failed to CRC the bsp\n" ); - return false; - } - - // hold a dictionary of all the static prop names - // this is needed to properly convert any VHV files inside the pak lump - BuildStaticPropNameTable(); - - // Set the output file pointer after the header - dheader_t dummyHeader = { 0 }; - SafeWrite( g_hBSPFile, &dummyHeader, sizeof( dheader_t ) ); - - // To allow for alignment fixups, the lumps will be written to the - // output file in the order they appear in this function. - - // NOTE: Flags for 360 !!!MUST!!! be first - SwapLumpToDisk< dflagslump_t >( LUMP_MAP_FLAGS ); - - // complex lump swaps first or for later contingent data - SwapLeafLumpToDisk(); - SwapOcclusionLumpToDisk(); - SwapGameLumpsToDisk(); - - // Strip dead or non relevant lumps - g_pBSPHeader->lumps[LUMP_DISP_LIGHTMAP_ALPHAS].filelen = 0; - g_pBSPHeader->lumps[LUMP_FACEIDS].filelen = 0; - - // Strip obsolete LDR in favor of HDR - if ( SwapLumpToDisk( LUMP_FACES_HDR ) ) - { - g_pBSPHeader->lumps[LUMP_FACES].filelen = 0; - } - else - { - // no HDR, keep LDR version - SwapLumpToDisk( LUMP_FACES ); - } - - if ( SwapLumpToDisk( LUMP_WORLDLIGHTS_HDR ) ) - { - g_pBSPHeader->lumps[LUMP_WORLDLIGHTS].filelen = 0; - } - else - { - // no HDR, keep LDR version - SwapLumpToDisk( LUMP_WORLDLIGHTS ); - } - - // Simple lump swaps - SwapLumpToDisk( FIELD_CHARACTER, LUMP_PHYSDISP ); - SwapLumpToDisk( FIELD_CHARACTER, LUMP_PHYSCOLLIDE ); - SwapLumpToDisk( FIELD_CHARACTER, LUMP_VISIBILITY ); - SwapLumpToDisk( LUMP_MODELS ); - SwapLumpToDisk( LUMP_VERTEXES ); - SwapLumpToDisk( LUMP_PLANES ); - SwapLumpToDisk( LUMP_NODES ); - SwapLumpToDisk( LUMP_TEXINFO ); - SwapLumpToDisk( LUMP_TEXDATA ); - SwapLumpToDisk( LUMP_DISPINFO ); - SwapLumpToDisk( LUMP_DISP_VERTS ); - SwapLumpToDisk( LUMP_DISP_TRIS ); - SwapLumpToDisk( FIELD_CHARACTER, LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS ); - SwapLumpToDisk( LUMP_FACE_MACRO_TEXTURE_INFO ); - SwapLumpToDisk( LUMP_PRIMITIVES ); - SwapLumpToDisk( LUMP_PRIMVERTS ); - SwapLumpToDisk( FIELD_SHORT, LUMP_PRIMINDICES ); - SwapLumpToDisk( LUMP_ORIGINALFACES ); - SwapLumpToDisk( FIELD_SHORT, LUMP_LEAFFACES ); - SwapLumpToDisk( FIELD_SHORT, LUMP_LEAFBRUSHES ); - SwapLumpToDisk( FIELD_INTEGER, LUMP_SURFEDGES ); - SwapLumpToDisk( LUMP_EDGES ); - SwapLumpToDisk( LUMP_BRUSHES ); - SwapLumpToDisk( LUMP_BRUSHSIDES ); - SwapLumpToDisk( LUMP_AREAS ); - SwapLumpToDisk( LUMP_AREAPORTALS ); - SwapLumpToDisk( FIELD_CHARACTER, LUMP_ENTITIES ); - SwapLumpToDisk( LUMP_LEAFWATERDATA ); - SwapLumpToDisk( FIELD_VECTOR, LUMP_VERTNORMALS ); - SwapLumpToDisk( FIELD_SHORT, LUMP_VERTNORMALINDICES ); - SwapLumpToDisk( FIELD_VECTOR, LUMP_CLIPPORTALVERTS ); - SwapLumpToDisk( LUMP_CUBEMAPS ); - SwapLumpToDisk( FIELD_CHARACTER, LUMP_TEXDATA_STRING_DATA ); - SwapLumpToDisk( FIELD_INTEGER, LUMP_TEXDATA_STRING_TABLE ); - SwapLumpToDisk( LUMP_OVERLAYS ); - SwapLumpToDisk( LUMP_WATEROVERLAYS ); - SwapLumpToDisk( FIELD_SHORT, LUMP_LEAFMINDISTTOWATER ); - SwapLumpToDisk( LUMP_OVERLAY_FADES ); - - - // NOTE: this data placed at the end for the sake of 360: - { - // NOTE: lighting must be the penultimate lump - // (allows 360 to free this memory part-way through map loading) - if ( SwapLumpToDisk( FIELD_CHARACTER, LUMP_LIGHTING_HDR ) ) - { - g_pBSPHeader->lumps[LUMP_LIGHTING].filelen = 0; - } - else - { - // no HDR, keep LDR version - SwapLumpToDisk( FIELD_CHARACTER, LUMP_LIGHTING ); - } - // NOTE: Pakfile for 360 !!!MUST!!! be last - SwapPakfileLumpToDisk( pInFilename ); - } - - - // Store the crc in the flags lump version field - g_pBSPHeader->lumps[LUMP_MAP_FLAGS].version = mapCRC; - - // Pad out the end of the file to a sector boundary for optimal IO - AlignFilePosition( g_hBSPFile, XBOX_DVD_SECTORSIZE ); - - // Warn of any lumps that didn't get swapped - for ( int i = 0; i < HEADER_LUMPS; ++i ) - { - if ( HasLump( i ) && !g_Lumps.bLumpParsed[i] ) - { - // a new lump got added that needs to have a swap function - Warning( "BSP: '%s', %s has no swap or copy function. Discarding!\n", pInFilename, GetLumpName(i) ); - - // the data didn't get copied, so don't reference garbage - g_pBSPHeader->lumps[i].filelen = 0; - } - } - - // Write the updated header - g_pFileSystem->Seek( g_hBSPFile, 0, FILESYSTEM_SEEK_HEAD ); - WriteData( g_pBSPHeader ); - g_pFileSystem->Close( g_hBSPFile ); - g_hBSPFile = 0; - - // Cleanup - g_Swap.ActivateByteSwapping( false ); - - CloseBSPFile(); - - g_StaticPropNames.Purge(); - g_StaticPropInstances.Purge(); - - DevMsg( "Finished BSP Swap\n" ); - - // caller provided compress func will further compress compatible lumps - if ( pCompressFunc ) - { - CUtlBuffer inputBuffer; - if ( !g_pFileSystem->ReadFile( pOutFilename, NULL, inputBuffer ) ) - { - Warning( "Error! Couldn't read file %s - final BSP compression failed!\n", pOutFilename ); - return false; - } - - CUtlBuffer outputBuffer; - if ( !CompressBSP( inputBuffer, outputBuffer, pCompressFunc ) ) - { - Warning( "Error! Failed to compress BSP '%s'!\n", pOutFilename ); - return false; - } - - g_hBSPFile = SafeOpenWrite( pOutFilename ); - if ( !g_hBSPFile ) - { - Warning( "Error! Couldn't open output file %s - BSP swap failed!\n", pOutFilename ); - return false; - } - SafeWrite( g_hBSPFile, outputBuffer.Base(), outputBuffer.TellPut() ); - g_pFileSystem->Close( g_hBSPFile ); - g_hBSPFile = 0; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Get the pak lump from a BSP -//----------------------------------------------------------------------------- -bool GetPakFileLump( const char *pBSPFilename, void **pPakData, int *pPakSize ) -{ - *pPakData = NULL; - *pPakSize = 0; - - if ( !g_pFileSystem->FileExists( pBSPFilename ) ) - { - Warning( "Error! Couldn't open file %s!\n", pBSPFilename ); - return false; - } - - // determine endian nature - dheader_t *pHeader; - LoadFile( pBSPFilename, (void **)&pHeader ); - bool bSwap = ( pHeader->ident == BigLong( IDBSPHEADER ) ); - free( pHeader ); - - g_bSwapOnLoad = bSwap; - g_bSwapOnWrite = !bSwap; - - OpenBSPFile( pBSPFilename ); - - if ( g_pBSPHeader->lumps[LUMP_PAKFILE].filelen ) - { - *pPakSize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, pPakData ); - } - - CloseBSPFile(); - - return true; -} - -// compare function for qsort below -static int LumpOffsetCompare( const void *pElem1, const void *pElem2 ) -{ - int lump1 = *(byte *)pElem1; - int lump2 = *(byte *)pElem2; - - if ( lump1 != lump2 ) - { - // force LUMP_MAP_FLAGS to be first, always - if ( lump1 == LUMP_MAP_FLAGS ) - { - return -1; - } - else if ( lump2 == LUMP_MAP_FLAGS ) - { - return 1; - } - - // force LUMP_PAKFILE to be last, always - if ( lump1 == LUMP_PAKFILE ) - { - return 1; - } - else if ( lump2 == LUMP_PAKFILE ) - { - return -1; - } - } - - int fileOffset1 = g_pBSPHeader->lumps[lump1].fileofs; - int fileOffset2 = g_pBSPHeader->lumps[lump2].fileofs; - - // invalid or empty lumps will get sorted together - if ( !g_pBSPHeader->lumps[lump1].filelen ) - { - fileOffset1 = 0; - } - - if ( !g_pBSPHeader->lumps[lump2].filelen ) - { - fileOffset2 = 0; - } - - // compare by offset - if ( fileOffset1 < fileOffset2 ) - { - return -1; - } - else if ( fileOffset1 > fileOffset2 ) - { - return 1; - } - return 0; -} - -//----------------------------------------------------------------------------- -// Replace the pak lump in a BSP -//----------------------------------------------------------------------------- -bool SetPakFileLump( const char *pBSPFilename, const char *pNewFilename, void *pPakData, int pakSize ) -{ - if ( !g_pFileSystem->FileExists( pBSPFilename ) ) - { - Warning( "Error! Couldn't open file %s!\n", pBSPFilename ); - return false; - } - - // determine endian nature - dheader_t *pHeader; - LoadFile( pBSPFilename, (void **)&pHeader ); - bool bSwap = ( pHeader->ident == BigLong( IDBSPHEADER ) ); - free( pHeader ); - - g_bSwapOnLoad = bSwap; - g_bSwapOnWrite = bSwap; - - OpenBSPFile( pBSPFilename ); - - // save a copy of the old header - // generating a new bsp is a destructive operation - dheader_t oldHeader; - oldHeader = *g_pBSPHeader; - - g_hBSPFile = SafeOpenWrite( pNewFilename ); - if ( !g_hBSPFile ) - { - return false; - } - - // placeholder only, reset at conclusion - WriteData( &oldHeader ); - - // lumps must be reserialized in same relative offset order - // build sorted order table - int readOrder[HEADER_LUMPS]; - for ( int i=0; ilumps[lump].filelen; - if ( length ) - { - // save the lump data - int offset = g_pBSPHeader->lumps[lump].fileofs; - SetAlignedLumpPosition( lump ); - SafeWrite( g_hBSPFile, (byte *)g_pBSPHeader + offset, length ); - } - else - { - g_pBSPHeader->lumps[lump].fileofs = 0; - } - } - - // Always write the pak file at the end - // Pad out the end of the file to a sector boundary for optimal IO - g_pBSPHeader->lumps[LUMP_PAKFILE].fileofs = AlignFilePosition( g_hBSPFile, XBOX_DVD_SECTORSIZE ); - g_pBSPHeader->lumps[LUMP_PAKFILE].filelen = pakSize; - SafeWrite( g_hBSPFile, pPakData, pakSize ); - - // Pad out the end of the file to a sector boundary for optimal IO - AlignFilePosition( g_hBSPFile, XBOX_DVD_SECTORSIZE ); - - // Write the updated header - g_pFileSystem->Seek( g_hBSPFile, 0, FILESYSTEM_SEEK_HEAD ); - WriteData( g_pBSPHeader ); - g_pFileSystem->Close( g_hBSPFile ); - - CloseBSPFile(); - - return true; -} - -//----------------------------------------------------------------------------- -// Build a list of files that BSP owns, world/cubemap materials, static props, etc. -//----------------------------------------------------------------------------- -bool GetBSPDependants( const char *pBSPFilename, CUtlVector< CUtlString > *pList ) -{ - if ( !g_pFileSystem->FileExists( pBSPFilename ) ) - { - Warning( "Error! Couldn't open file %s!\n", pBSPFilename ); - return false; - } - - // must be set, but exact hdr not critical for dependant traversal - SetHDRMode( false ); - - LoadBSPFile( pBSPFilename ); - - char szBspName[MAX_PATH]; - V_FileBase( pBSPFilename, szBspName, sizeof( szBspName ) ); - V_SetExtension( szBspName, ".bsp", sizeof( szBspName ) ); - - // get embedded pak files, and internals - char szFilename[MAX_PATH]; - int fileSize; - int fileId = -1; - for ( ;; ) - { - fileId = GetPakFile()->GetNextFilename( fileId, szFilename, sizeof( szFilename ), fileSize ); - if ( fileId == -1 ) - { - break; - } - pList->AddToTail( szFilename ); - } - - // get all the world materials - for ( int i=0; iAddToTail( szFilename ); - } - - // get all the static props - GameLumpHandle_t hGameLump = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); - if ( hGameLump != g_GameLumps.InvalidGameLump() ) - { - byte *pGameLumpData = (byte *)g_GameLumps.GetGameLump( hGameLump ); - if ( pGameLumpData && g_GameLumps.GameLumpSize( hGameLump ) ) - { - int count = ((int *)pGameLumpData)[0]; - pGameLumpData += sizeof( int ); - - StaticPropDictLump_t *pStaticPropDictLump = (StaticPropDictLump_t *)pGameLumpData; - for ( int i=0; iAddToTail( pStaticPropDictLump[i].m_Name ); - } - } - } - - // get all the detail props - hGameLump = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROPS ); - if ( hGameLump != g_GameLumps.InvalidGameLump() ) - { - byte *pGameLumpData = (byte *)g_GameLumps.GetGameLump( hGameLump ); - if ( pGameLumpData && g_GameLumps.GameLumpSize( hGameLump ) ) - { - int count = ((int *)pGameLumpData)[0]; - pGameLumpData += sizeof( int ); - - DetailObjectDictLump_t *pDetailObjectDictLump = (DetailObjectDictLump_t *)pGameLumpData; - for ( int i=0; iAddToTail( pDetailObjectDictLump[i].m_Name ); - } - pGameLumpData += count * sizeof( DetailObjectDictLump_t ); - - if ( g_GameLumps.GetGameLumpVersion( hGameLump ) == 4 ) - { - count = ((int *)pGameLumpData)[0]; - pGameLumpData += sizeof( int ); - if ( count ) - { - // All detail prop sprites must lie in the material detail/detailsprites - pList->AddToTail( "materials/detail/detailsprites.vmt" ); - } - } - } - } - - UnloadBSPFile(); - - return true; -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#include "cmdlib.h" +#include "mathlib/mathlib.h" +#include "bsplib.h" +#include "zip_utils.h" +#include "scriplib.h" +#include "utllinkedlist.h" +#include "bsptreedata.h" +#include "cmodel.h" +#include "gamebspfile.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/hardwareverts.h" +#include "utlbuffer.h" +#include "utlrbtree.h" +#include "utlsymbol.h" +#include "utlstring.h" +#include "checksum_crc.h" +#include "physdll.h" +#include "tier0/dbg.h" +#include "lumpfiles.h" +#include "vtf/vtf.h" + +//============================================================================= + +// Boundary each lump should be aligned to +#define LUMP_ALIGNMENT 4 + +// Data descriptions for byte swapping - only needed +// for structures that are written to file for use by the game. +BEGIN_BYTESWAP_DATADESC( dheader_t ) + DEFINE_FIELD( ident, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_EMBEDDED_ARRAY( lumps, HEADER_LUMPS ), + DEFINE_FIELD( mapRevision, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( lump_t ) + DEFINE_FIELD( fileofs, FIELD_INTEGER ), + DEFINE_FIELD( filelen, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_ARRAY( fourCC, FIELD_CHARACTER, 4 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dflagslump_t ) + DEFINE_FIELD( m_LevelFlags, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dplane_t ) + DEFINE_FIELD( normal, FIELD_VECTOR ), + DEFINE_FIELD( dist, FIELD_FLOAT ), + DEFINE_FIELD( type, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dleaf_version_0_t ) + DEFINE_FIELD( contents, FIELD_INTEGER ), + DEFINE_FIELD( cluster, FIELD_SHORT ), + DEFINE_BITFIELD( bf, FIELD_SHORT, 16 ), + DEFINE_ARRAY( mins, FIELD_SHORT, 3 ), + DEFINE_ARRAY( maxs, FIELD_SHORT, 3 ), + DEFINE_FIELD( firstleafface, FIELD_SHORT ), + DEFINE_FIELD( numleaffaces, FIELD_SHORT ), + DEFINE_FIELD( firstleafbrush, FIELD_SHORT ), + DEFINE_FIELD( numleafbrushes, FIELD_SHORT ), + DEFINE_FIELD( leafWaterDataID, FIELD_SHORT ), + DEFINE_EMBEDDED( m_AmbientLighting ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dleaf_t ) + DEFINE_FIELD( contents, FIELD_INTEGER ), + DEFINE_FIELD( cluster, FIELD_SHORT ), + DEFINE_BITFIELD( bf, FIELD_SHORT, 16 ), + DEFINE_ARRAY( mins, FIELD_SHORT, 3 ), + DEFINE_ARRAY( maxs, FIELD_SHORT, 3 ), + DEFINE_FIELD( firstleafface, FIELD_SHORT ), + DEFINE_FIELD( numleaffaces, FIELD_SHORT ), + DEFINE_FIELD( firstleafbrush, FIELD_SHORT ), + DEFINE_FIELD( numleafbrushes, FIELD_SHORT ), + DEFINE_FIELD( leafWaterDataID, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CompressedLightCube ) // array of 6 ColorRGBExp32 (3 bytes and 1 char) + DEFINE_ARRAY( m_Color, FIELD_CHARACTER, 6 * sizeof(ColorRGBExp32) ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dleafambientindex_t ) + DEFINE_FIELD( ambientSampleCount, FIELD_SHORT ), + DEFINE_FIELD( firstAmbientSample, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dleafambientlighting_t ) // array of 6 ColorRGBExp32 (3 bytes and 1 char) + DEFINE_EMBEDDED( cube ), + DEFINE_FIELD( x, FIELD_CHARACTER ), + DEFINE_FIELD( y, FIELD_CHARACTER ), + DEFINE_FIELD( z, FIELD_CHARACTER ), + DEFINE_FIELD( pad, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dvertex_t ) + DEFINE_FIELD( point, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dnode_t ) + DEFINE_FIELD( planenum, FIELD_INTEGER ), + DEFINE_ARRAY( children, FIELD_INTEGER, 2 ), + DEFINE_ARRAY( mins, FIELD_SHORT, 3 ), + DEFINE_ARRAY( maxs, FIELD_SHORT, 3 ), + DEFINE_FIELD( firstface, FIELD_SHORT ), + DEFINE_FIELD( numfaces, FIELD_SHORT ), + DEFINE_FIELD( area, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( texinfo_t ) + DEFINE_ARRAY( textureVecsTexelsPerWorldUnits, FIELD_FLOAT, 2 * 4 ), + DEFINE_ARRAY( lightmapVecsLuxelsPerWorldUnits, FIELD_FLOAT, 2 * 4 ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( texdata, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dtexdata_t ) + DEFINE_FIELD( reflectivity, FIELD_VECTOR ), + DEFINE_FIELD( nameStringTableID, FIELD_INTEGER ), + DEFINE_FIELD( width, FIELD_INTEGER ), + DEFINE_FIELD( height, FIELD_INTEGER ), + DEFINE_FIELD( view_width, FIELD_INTEGER ), + DEFINE_FIELD( view_height, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( ddispinfo_t ) + DEFINE_FIELD( startPosition, FIELD_VECTOR ), + DEFINE_FIELD( m_iDispVertStart, FIELD_INTEGER ), + DEFINE_FIELD( m_iDispTriStart, FIELD_INTEGER ), + DEFINE_FIELD( power, FIELD_INTEGER ), + DEFINE_FIELD( minTess, FIELD_INTEGER ), + DEFINE_FIELD( smoothingAngle, FIELD_FLOAT ), + DEFINE_FIELD( contents, FIELD_INTEGER ), + DEFINE_FIELD( m_iMapFace, FIELD_SHORT ), + DEFINE_FIELD( m_iLightmapAlphaStart, FIELD_INTEGER ), + DEFINE_FIELD( m_iLightmapSamplePositionStart, FIELD_INTEGER ), + DEFINE_EMBEDDED_ARRAY( m_EdgeNeighbors, 4 ), + DEFINE_EMBEDDED_ARRAY( m_CornerNeighbors, 4 ), + DEFINE_ARRAY( m_AllowedVerts, FIELD_INTEGER, ddispinfo_t::ALLOWEDVERTS_SIZE ), // unsigned long +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CDispNeighbor ) + DEFINE_EMBEDDED_ARRAY( m_SubNeighbors, 2 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CDispCornerNeighbors ) + DEFINE_ARRAY( m_Neighbors, FIELD_SHORT, MAX_DISP_CORNER_NEIGHBORS ), + DEFINE_FIELD( m_nNeighbors, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CDispSubNeighbor ) + DEFINE_FIELD( m_iNeighbor, FIELD_SHORT ), + DEFINE_FIELD( m_NeighborOrientation, FIELD_CHARACTER ), + DEFINE_FIELD( m_Span, FIELD_CHARACTER ), + DEFINE_FIELD( m_NeighborSpan, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CDispVert ) + DEFINE_FIELD( m_vVector, FIELD_VECTOR ), + DEFINE_FIELD( m_flDist, FIELD_FLOAT ), + DEFINE_FIELD( m_flAlpha, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CDispTri ) + DEFINE_FIELD( m_uiTags, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CFaceMacroTextureInfo ) + DEFINE_FIELD( m_MacroTextureNameID, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dprimitive_t ) + DEFINE_FIELD( type, FIELD_CHARACTER ), + DEFINE_FIELD( firstIndex, FIELD_SHORT ), + DEFINE_FIELD( indexCount, FIELD_SHORT ), + DEFINE_FIELD( firstVert, FIELD_SHORT ), + DEFINE_FIELD( vertCount, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dprimvert_t ) + DEFINE_FIELD( pos, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dface_t ) + DEFINE_FIELD( planenum, FIELD_SHORT ), + DEFINE_FIELD( side, FIELD_CHARACTER ), + DEFINE_FIELD( onNode, FIELD_CHARACTER ), + DEFINE_FIELD( firstedge, FIELD_INTEGER ), + DEFINE_FIELD( numedges, FIELD_SHORT ), + DEFINE_FIELD( texinfo, FIELD_SHORT ), + DEFINE_FIELD( dispinfo, FIELD_SHORT ), + DEFINE_FIELD( surfaceFogVolumeID, FIELD_SHORT ), + DEFINE_ARRAY( styles, FIELD_CHARACTER, MAXLIGHTMAPS ), + DEFINE_FIELD( lightofs, FIELD_INTEGER ), + DEFINE_FIELD( area, FIELD_FLOAT ), + DEFINE_ARRAY( m_LightmapTextureMinsInLuxels, FIELD_INTEGER, 2 ), + DEFINE_ARRAY( m_LightmapTextureSizeInLuxels, FIELD_INTEGER, 2 ), + DEFINE_FIELD( origFace, FIELD_INTEGER ), + DEFINE_FIELD( m_NumPrims, FIELD_SHORT ), + DEFINE_FIELD( firstPrimID, FIELD_SHORT ), + DEFINE_FIELD( smoothingGroups, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dfaceid_t ) + DEFINE_FIELD( hammerfaceid, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dbrush_t ) + DEFINE_FIELD( firstside, FIELD_INTEGER ), + DEFINE_FIELD( numsides, FIELD_INTEGER ), + DEFINE_FIELD( contents, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dbrushside_t ) + DEFINE_FIELD( planenum, FIELD_SHORT ), + DEFINE_FIELD( texinfo, FIELD_SHORT ), + DEFINE_FIELD( dispinfo, FIELD_SHORT ), + DEFINE_FIELD( bevel, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dedge_t ) + DEFINE_ARRAY( v, FIELD_SHORT, 2 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dmodel_t ) + DEFINE_FIELD( mins, FIELD_VECTOR ), + DEFINE_FIELD( maxs, FIELD_VECTOR ), + DEFINE_FIELD( origin, FIELD_VECTOR ), + DEFINE_FIELD( headnode, FIELD_INTEGER ), + DEFINE_FIELD( firstface, FIELD_INTEGER ), + DEFINE_FIELD( numfaces, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dphysmodel_t ) + DEFINE_FIELD( modelIndex, FIELD_INTEGER ), + DEFINE_FIELD( dataSize, FIELD_INTEGER ), + DEFINE_FIELD( keydataSize, FIELD_INTEGER ), + DEFINE_FIELD( solidCount, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dphysdisp_t ) + DEFINE_FIELD( numDisplacements, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( darea_t ) + DEFINE_FIELD( numareaportals, FIELD_INTEGER ), + DEFINE_FIELD( firstareaportal, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dareaportal_t ) + DEFINE_FIELD( m_PortalKey, FIELD_SHORT ), + DEFINE_FIELD( otherarea, FIELD_SHORT ), + DEFINE_FIELD( m_FirstClipPortalVert, FIELD_SHORT ), + DEFINE_FIELD( m_nClipPortalVerts, FIELD_SHORT ), + DEFINE_FIELD( planenum, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dworldlight_t ) + DEFINE_FIELD( origin, FIELD_VECTOR ), + DEFINE_FIELD( intensity, FIELD_VECTOR ), + DEFINE_FIELD( normal, FIELD_VECTOR ), + DEFINE_FIELD( cluster, FIELD_INTEGER ), + DEFINE_FIELD( type, FIELD_INTEGER ), // enumeration + DEFINE_FIELD( style, FIELD_INTEGER ), + DEFINE_FIELD( stopdot, FIELD_FLOAT ), + DEFINE_FIELD( stopdot2, FIELD_FLOAT ), + DEFINE_FIELD( exponent, FIELD_FLOAT ), + DEFINE_FIELD( radius, FIELD_FLOAT ), + DEFINE_FIELD( constant_attn, FIELD_FLOAT ), + DEFINE_FIELD( linear_attn, FIELD_FLOAT ), + DEFINE_FIELD( quadratic_attn, FIELD_FLOAT ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( texinfo, FIELD_INTEGER ), + DEFINE_FIELD( owner, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dleafwaterdata_t ) + DEFINE_FIELD( surfaceZ, FIELD_FLOAT ), + DEFINE_FIELD( minZ, FIELD_FLOAT ), + DEFINE_FIELD( surfaceTexInfoID, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( doccluderdata_t ) + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( firstpoly, FIELD_INTEGER ), + DEFINE_FIELD( polycount, FIELD_INTEGER ), + DEFINE_FIELD( mins, FIELD_VECTOR ), + DEFINE_FIELD( maxs, FIELD_VECTOR ), + DEFINE_FIELD( area, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( doccluderpolydata_t ) + DEFINE_FIELD( firstvertexindex, FIELD_INTEGER ), + DEFINE_FIELD( vertexcount, FIELD_INTEGER ), + DEFINE_FIELD( planenum, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dcubemapsample_t ) + DEFINE_ARRAY( origin, FIELD_INTEGER, 3 ), + DEFINE_FIELD( size, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( doverlay_t ) + DEFINE_FIELD( nId, FIELD_INTEGER ), + DEFINE_FIELD( nTexInfo, FIELD_SHORT ), + DEFINE_FIELD( m_nFaceCountAndRenderOrder, FIELD_SHORT ), + DEFINE_ARRAY( aFaces, FIELD_INTEGER, OVERLAY_BSP_FACE_COUNT ), + DEFINE_ARRAY( flU, FIELD_FLOAT, 2 ), + DEFINE_ARRAY( flV, FIELD_FLOAT, 2 ), + DEFINE_ARRAY( vecUVPoints, FIELD_VECTOR, 4 ), + DEFINE_FIELD( vecOrigin, FIELD_VECTOR ), + DEFINE_FIELD( vecBasisNormal, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dwateroverlay_t ) + DEFINE_FIELD( nId, FIELD_INTEGER ), + DEFINE_FIELD( nTexInfo, FIELD_SHORT ), + DEFINE_FIELD( m_nFaceCountAndRenderOrder, FIELD_SHORT ), + DEFINE_ARRAY( aFaces, FIELD_INTEGER, WATEROVERLAY_BSP_FACE_COUNT ), + DEFINE_ARRAY( flU, FIELD_FLOAT, 2 ), + DEFINE_ARRAY( flV, FIELD_FLOAT, 2 ), + DEFINE_ARRAY( vecUVPoints, FIELD_VECTOR, 4 ), + DEFINE_FIELD( vecOrigin, FIELD_VECTOR ), + DEFINE_FIELD( vecBasisNormal, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( doverlayfade_t ) + DEFINE_FIELD( flFadeDistMinSq, FIELD_FLOAT ), + DEFINE_FIELD( flFadeDistMaxSq, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dgamelumpheader_t ) + DEFINE_FIELD( lumpCount, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dgamelump_t ) + DEFINE_FIELD( id, FIELD_INTEGER ), // GameLumpId_t + DEFINE_FIELD( flags, FIELD_SHORT ), + DEFINE_FIELD( version, FIELD_SHORT ), + DEFINE_FIELD( fileofs, FIELD_INTEGER ), + DEFINE_FIELD( filelen, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +// From gamebspfile.h +BEGIN_BYTESWAP_DATADESC( StaticPropDictLump_t ) + DEFINE_ARRAY( m_Name, FIELD_CHARACTER, STATIC_PROP_NAME_LENGTH ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( StaticPropLump_t ) + DEFINE_FIELD( m_Origin, FIELD_VECTOR ), + DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle + DEFINE_FIELD( m_PropType, FIELD_SHORT ), + DEFINE_FIELD( m_FirstLeaf, FIELD_SHORT ), + DEFINE_FIELD( m_LeafCount, FIELD_SHORT ), + DEFINE_FIELD( m_Solid, FIELD_CHARACTER ), + DEFINE_FIELD( m_Flags, FIELD_CHARACTER ), + DEFINE_FIELD( m_Skin, FIELD_INTEGER ), + DEFINE_FIELD( m_FadeMinDist, FIELD_FLOAT ), + DEFINE_FIELD( m_FadeMaxDist, FIELD_FLOAT ), + DEFINE_FIELD( m_LightingOrigin, FIELD_VECTOR ), + DEFINE_FIELD( m_flForcedFadeScale, FIELD_FLOAT ), + DEFINE_FIELD( m_nMinDXLevel, FIELD_SHORT ), + DEFINE_FIELD( m_nMaxDXLevel, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( StaticPropLumpV4_t ) + DEFINE_FIELD( m_Origin, FIELD_VECTOR ), + DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle + DEFINE_FIELD( m_PropType, FIELD_SHORT ), + DEFINE_FIELD( m_FirstLeaf, FIELD_SHORT ), + DEFINE_FIELD( m_LeafCount, FIELD_SHORT ), + DEFINE_FIELD( m_Solid, FIELD_CHARACTER ), + DEFINE_FIELD( m_Flags, FIELD_CHARACTER ), + DEFINE_FIELD( m_Skin, FIELD_INTEGER ), + DEFINE_FIELD( m_FadeMinDist, FIELD_FLOAT ), + DEFINE_FIELD( m_FadeMaxDist, FIELD_FLOAT ), + DEFINE_FIELD( m_LightingOrigin, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( StaticPropLumpV5_t ) + DEFINE_FIELD( m_Origin, FIELD_VECTOR ), + DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle + DEFINE_FIELD( m_PropType, FIELD_SHORT ), + DEFINE_FIELD( m_FirstLeaf, FIELD_SHORT ), + DEFINE_FIELD( m_LeafCount, FIELD_SHORT ), + DEFINE_FIELD( m_Solid, FIELD_CHARACTER ), + DEFINE_FIELD( m_Flags, FIELD_CHARACTER ), + DEFINE_FIELD( m_Skin, FIELD_INTEGER ), + DEFINE_FIELD( m_FadeMinDist, FIELD_FLOAT ), + DEFINE_FIELD( m_FadeMaxDist, FIELD_FLOAT ), + DEFINE_FIELD( m_LightingOrigin, FIELD_VECTOR ), + DEFINE_FIELD( m_flForcedFadeScale, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( StaticPropLeafLump_t ) + DEFINE_FIELD( m_Leaf, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( DetailObjectDictLump_t ) + DEFINE_ARRAY( m_Name, FIELD_CHARACTER, DETAIL_NAME_LENGTH ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( DetailObjectLump_t ) + DEFINE_FIELD( m_Origin, FIELD_VECTOR ), + DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle + DEFINE_FIELD( m_DetailModel, FIELD_SHORT ), + DEFINE_FIELD( m_Leaf, FIELD_SHORT ), + DEFINE_ARRAY( m_Lighting, FIELD_CHARACTER, 4 ), // ColorRGBExp32 + DEFINE_FIELD( m_LightStyles, FIELD_INTEGER ), + DEFINE_FIELD( m_LightStyleCount, FIELD_CHARACTER ), + DEFINE_FIELD( m_SwayAmount, FIELD_CHARACTER ), + DEFINE_FIELD( m_ShapeAngle, FIELD_CHARACTER ), + DEFINE_FIELD( m_ShapeSize, FIELD_CHARACTER ), + DEFINE_FIELD( m_Orientation, FIELD_CHARACTER ), + DEFINE_ARRAY( m_Padding2, FIELD_CHARACTER, 3 ), + DEFINE_FIELD( m_Type, FIELD_CHARACTER ), + DEFINE_ARRAY( m_Padding3, FIELD_CHARACTER, 3 ), + DEFINE_FIELD( m_flScale, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( DetailSpriteDictLump_t ) + DEFINE_FIELD( m_UL, FIELD_VECTOR2D ), + DEFINE_FIELD( m_LR, FIELD_VECTOR2D ), + DEFINE_FIELD( m_TexUL, FIELD_VECTOR2D ), + DEFINE_FIELD( m_TexLR, FIELD_VECTOR2D ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( DetailPropLightstylesLump_t ) + DEFINE_ARRAY( m_Lighting, FIELD_CHARACTER, 4 ), // ColorRGBExp32 + DEFINE_FIELD( m_Style, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +// From vradstaticprops.h +namespace HardwareVerts +{ +BEGIN_BYTESWAP_DATADESC( MeshHeader_t ) + DEFINE_FIELD( m_nLod, FIELD_INTEGER ), + DEFINE_FIELD( m_nVertexes, FIELD_INTEGER ), + DEFINE_FIELD( m_nOffset, FIELD_INTEGER ), + DEFINE_ARRAY( m_nUnused, FIELD_INTEGER, 4 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( FileHeader_t ) + DEFINE_FIELD( m_nVersion, FIELD_INTEGER ), + DEFINE_FIELD( m_nChecksum, FIELD_INTEGER ), + DEFINE_FIELD( m_nVertexFlags, FIELD_INTEGER ), + DEFINE_FIELD( m_nVertexSize, FIELD_INTEGER ), + DEFINE_FIELD( m_nVertexes, FIELD_INTEGER ), + DEFINE_FIELD( m_nMeshes, FIELD_INTEGER ), + DEFINE_ARRAY( m_nUnused, FIELD_INTEGER, 4 ), +END_BYTESWAP_DATADESC() +} // end namespace + +static const char *s_LumpNames[] = { + "LUMP_ENTITIES", // 0 + "LUMP_PLANES", // 1 + "LUMP_TEXDATA", // 2 + "LUMP_VERTEXES", // 3 + "LUMP_VISIBILITY", // 4 + "LUMP_NODES", // 5 + "LUMP_TEXINFO", // 6 + "LUMP_FACES", // 7 + "LUMP_LIGHTING", // 8 + "LUMP_OCCLUSION", // 9 + "LUMP_LEAFS", // 10 + "LUMP_FACEIDS", // 11 + "LUMP_EDGES", // 12 + "LUMP_SURFEDGES", // 13 + "LUMP_MODELS", // 14 + "LUMP_WORLDLIGHTS", // 15 + "LUMP_LEAFFACES", // 16 + "LUMP_LEAFBRUSHES", // 17 + "LUMP_BRUSHES", // 18 + "LUMP_BRUSHSIDES", // 19 + "LUMP_AREAS", // 20 + "LUMP_AREAPORTALS", // 21 + "LUMP_UNUSED0", // 22 + "LUMP_UNUSED1", // 23 + "LUMP_UNUSED2", // 24 + "LUMP_UNUSED3", // 25 + "LUMP_DISPINFO", // 26 + "LUMP_ORIGINALFACES", // 27 + "LUMP_PHYSDISP", // 28 + "LUMP_PHYSCOLLIDE", // 29 + "LUMP_VERTNORMALS", // 30 + "LUMP_VERTNORMALINDICES", // 31 + "LUMP_DISP_LIGHTMAP_ALPHAS", // 32 + "LUMP_DISP_VERTS", // 33 + "LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS", // 34 + "LUMP_GAME_LUMP", // 35 + "LUMP_LEAFWATERDATA", // 36 + "LUMP_PRIMITIVES", // 37 + "LUMP_PRIMVERTS", // 38 + "LUMP_PRIMINDICES", // 39 + "LUMP_PAKFILE", // 40 + "LUMP_CLIPPORTALVERTS", // 41 + "LUMP_CUBEMAPS", // 42 + "LUMP_TEXDATA_STRING_DATA", // 43 + "LUMP_TEXDATA_STRING_TABLE", // 44 + "LUMP_OVERLAYS", // 45 + "LUMP_LEAFMINDISTTOWATER", // 46 + "LUMP_FACE_MACRO_TEXTURE_INFO", // 47 + "LUMP_DISP_TRIS", // 48 + "LUMP_PHYSCOLLIDESURFACE", // 49 + "LUMP_WATEROVERLAYS", // 50 + "LUMP_LEAF_AMBIENT_INDEX_HDR", // 51 + "LUMP_LEAF_AMBIENT_INDEX", // 52 + "LUMP_LIGHTING_HDR", // 53 + "LUMP_WORLDLIGHTS_HDR", // 54 + "LUMP_LEAF_AMBIENT_LIGHTING_HDR", // 55 + "LUMP_LEAF_AMBIENT_LIGHTING", // 56 + "LUMP_XZIPPAKFILE", // 57 + "LUMP_FACES_HDR", // 58 + "LUMP_MAP_FLAGS", // 59 + "LUMP_OVERLAY_FADES", // 60 +}; + +const char *GetLumpName( unsigned int lumpnum ) +{ + if ( lumpnum >= ARRAYSIZE( s_LumpNames ) ) + { + return "UNKNOWN"; + } + return s_LumpNames[lumpnum]; +} + +// "-hdr" tells us to use the HDR fields (if present) on the light sources. Also, tells us to write +// out the HDR lumps for lightmaps, ambient leaves, and lights sources. +bool g_bHDR = false; + +// Set to true to generate Xbox360 native output files +static bool g_bSwapOnLoad = false; +static bool g_bSwapOnWrite = false; + +VTFConvertFunc_t g_pVTFConvertFunc; +VHVFixupFunc_t g_pVHVFixupFunc; +CompressFunc_t g_pCompressFunc; + +CUtlVector< CUtlString > g_StaticPropNames; +CUtlVector< int > g_StaticPropInstances; + +CByteswap g_Swap; + +uint32 g_LevelFlags = 0; + +int nummodels; +dmodel_t dmodels[MAX_MAP_MODELS]; + +int visdatasize; +byte dvisdata[MAX_MAP_VISIBILITY]; +dvis_t *dvis = (dvis_t *)dvisdata; + +CUtlVector dlightdataHDR; +CUtlVector dlightdataLDR; +CUtlVector *pdlightdata = &dlightdataLDR; + +CUtlVector dentdata; + +int numleafs; +#if !defined( BSP_USE_LESS_MEMORY ) +dleaf_t dleafs[MAX_MAP_LEAFS]; +#else +dleaf_t *dleafs; +#endif + +CUtlVector g_LeafAmbientIndexLDR; +CUtlVector g_LeafAmbientIndexHDR; +CUtlVector *g_pLeafAmbientIndex = NULL; +CUtlVector g_LeafAmbientLightingLDR; +CUtlVector g_LeafAmbientLightingHDR; +CUtlVector *g_pLeafAmbientLighting = NULL; + +unsigned short g_LeafMinDistToWater[MAX_MAP_LEAFS]; + +int numplanes; +dplane_t dplanes[MAX_MAP_PLANES]; + +int numvertexes; +dvertex_t dvertexes[MAX_MAP_VERTS]; + +int g_numvertnormalindices; // dfaces reference these. These index g_vertnormals. +unsigned short g_vertnormalindices[MAX_MAP_VERTNORMALS]; + +int g_numvertnormals; +Vector g_vertnormals[MAX_MAP_VERTNORMALS]; + +int numnodes; +dnode_t dnodes[MAX_MAP_NODES]; + +CUtlVector texinfo( MAX_MAP_TEXINFO ); + +int numtexdata; +dtexdata_t dtexdata[MAX_MAP_TEXDATA]; + +// +// displacement map bsp file info: dispinfo +// +CUtlVector g_dispinfo; +CUtlVector g_DispVerts; +CUtlVector g_DispTris; +CUtlVector g_DispLightmapSamplePositions; // LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS + +int numorigfaces; +dface_t dorigfaces[MAX_MAP_FACES]; + +int g_numprimitives = 0; +dprimitive_t g_primitives[MAX_MAP_PRIMITIVES]; + +int g_numprimverts = 0; +dprimvert_t g_primverts[MAX_MAP_PRIMVERTS]; + +int g_numprimindices = 0; +unsigned short g_primindices[MAX_MAP_PRIMINDICES]; + +int numfaces; +dface_t dfaces[MAX_MAP_FACES]; + +int numfaceids; +CUtlVector dfaceids; + +int numfaces_hdr; +dface_t dfaces_hdr[MAX_MAP_FACES]; + +int numedges; +dedge_t dedges[MAX_MAP_EDGES]; + +int numleaffaces; +unsigned short dleaffaces[MAX_MAP_LEAFFACES]; + +int numleafbrushes; +unsigned short dleafbrushes[MAX_MAP_LEAFBRUSHES]; + +int numsurfedges; +int dsurfedges[MAX_MAP_SURFEDGES]; + +int numbrushes; +dbrush_t dbrushes[MAX_MAP_BRUSHES]; + +int numbrushsides; +dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; + +int numareas; +darea_t dareas[MAX_MAP_AREAS]; + +int numareaportals; +dareaportal_t dareaportals[MAX_MAP_AREAPORTALS]; + +int numworldlightsLDR; +dworldlight_t dworldlightsLDR[MAX_MAP_WORLDLIGHTS]; + +int numworldlightsHDR; +dworldlight_t dworldlightsHDR[MAX_MAP_WORLDLIGHTS]; + +int *pNumworldlights = &numworldlightsLDR; +dworldlight_t *dworldlights = dworldlightsLDR; + +int numleafwaterdata = 0; +dleafwaterdata_t dleafwaterdata[MAX_MAP_LEAFWATERDATA]; + +CUtlVector g_FaceMacroTextureInfos; + +Vector g_ClipPortalVerts[MAX_MAP_PORTALVERTS]; +int g_nClipPortalVerts; + +dcubemapsample_t g_CubemapSamples[MAX_MAP_CUBEMAPSAMPLES]; +int g_nCubemapSamples = 0; + +int g_nOverlayCount; +doverlay_t g_Overlays[MAX_MAP_OVERLAYS]; +doverlayfade_t g_OverlayFades[MAX_MAP_OVERLAYS]; + +int g_nWaterOverlayCount; +dwateroverlay_t g_WaterOverlays[MAX_MAP_WATEROVERLAYS]; + +CUtlVector g_TexDataStringData; +CUtlVector g_TexDataStringTable; + +byte *g_pPhysCollide = NULL; +int g_PhysCollideSize = 0; +int g_MapRevision = 0; + +byte *g_pPhysDisp = NULL; +int g_PhysDispSize = 0; + +CUtlVector g_OccluderData( 256, 256 ); +CUtlVector g_OccluderPolyData( 1024, 1024 ); +CUtlVector g_OccluderVertexIndices( 2048, 2048 ); + +template static void WriteData( T *pData, int count = 1 ); +template static void WriteData( int fieldType, T *pData, int count = 1 ); +template< class T > static void AddLump( int lumpnum, T *pData, int count, int version = 0 ); +template< class T > static void AddLump( int lumpnum, CUtlVector &data, int version = 0 ); + +dheader_t *g_pBSPHeader; +FileHandle_t g_hBSPFile; + +struct Lump_t +{ + void *pLumps[HEADER_LUMPS]; + int size[HEADER_LUMPS]; + bool bLumpParsed[HEADER_LUMPS]; +} g_Lumps; + +CGameLump g_GameLumps; + +static IZip *s_pakFile = 0; + +//----------------------------------------------------------------------------- +// Keep the file position aligned to an arbitrary boundary. +// Returns updated file position. +//----------------------------------------------------------------------------- +static unsigned int AlignFilePosition( FileHandle_t hFile, int alignment ) +{ + unsigned int currPosition = g_pFileSystem->Tell( hFile ); + + if ( alignment >= 2 ) + { + unsigned int newPosition = AlignValue( currPosition, alignment ); + unsigned int count = newPosition - currPosition; + if ( count ) + { + char *pBuffer; + char smallBuffer[4096]; + if ( count > sizeof( smallBuffer ) ) + { + pBuffer = (char *)malloc( count ); + } + else + { + pBuffer = smallBuffer; + } + + memset( pBuffer, 0, count ); + SafeWrite( hFile, pBuffer, count ); + + if ( pBuffer != smallBuffer ) + { + free( pBuffer ); + } + + currPosition = newPosition; + } + } + + return currPosition; +} + +//----------------------------------------------------------------------------- +// Purpose: // Get a pakfile instance +// Output : IZip* +//----------------------------------------------------------------------------- +IZip* GetPakFile( void ) +{ + if ( !s_pakFile ) + { + s_pakFile = IZip::CreateZip(); + } + return s_pakFile; +} + +//----------------------------------------------------------------------------- +// Purpose: Free the pak files +//----------------------------------------------------------------------------- +void ReleasePakFileLumps( void ) +{ + // Release the pak files + IZip::ReleaseZip( s_pakFile ); + s_pakFile = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the sector alignment for all subsequent zip operations +//----------------------------------------------------------------------------- +void ForceAlignment( IZip *pak, bool bAlign, bool bCompatibleFormat, unsigned int alignmentSize ) +{ + pak->ForceAlignment( bAlign, bCompatibleFormat, alignmentSize ); +} + +//----------------------------------------------------------------------------- +// Purpose: Store data back out to .bsp file +//----------------------------------------------------------------------------- +static void WritePakFileLump( void ) +{ + CUtlBuffer buf( 0, 0 ); + GetPakFile()->ActivateByteSwapping( IsX360() ); + GetPakFile()->SaveToBuffer( buf ); + + // must respect pak file alignment + // pad up and ensure lump starts on same aligned boundary + AlignFilePosition( g_hBSPFile, GetPakFile()->GetAlignment() ); + + // Now store final buffers out to file + AddLump( LUMP_PAKFILE, (byte*)buf.Base(), buf.TellPut() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all entries +//----------------------------------------------------------------------------- +void ClearPakFile( IZip *pak ) +{ + pak->Reset(); +} + +//----------------------------------------------------------------------------- +// Purpose: Add file from disk to .bsp PAK lump +// Input : *relativename - +// *fullpath - +//----------------------------------------------------------------------------- +void AddFileToPak( IZip *pak, const char *relativename, const char *fullpath ) +{ + pak->AddFileToZip( relativename, fullpath ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add buffer to .bsp PAK lump as named file +// Input : *relativename - +// *data - +// length - +//----------------------------------------------------------------------------- +void AddBufferToPak( IZip *pak, const char *pRelativeName, void *data, int length, bool bTextMode ) +{ + pak->AddBufferToZip( pRelativeName, data, length, bTextMode ); +} + +//----------------------------------------------------------------------------- +// Purpose: Check if a file already exists in the pack file. +// Input : *relativename - +//----------------------------------------------------------------------------- +bool FileExistsInPak( IZip *pak, const char *pRelativeName ) +{ + return pak->FileExistsInZip( pRelativeName ); +} + + +//----------------------------------------------------------------------------- +// Read a file from the pack file +//----------------------------------------------------------------------------- +bool ReadFileFromPak( IZip *pak, const char *pRelativeName, bool bTextMode, CUtlBuffer &buf ) +{ + return pak->ReadFileFromZip( pRelativeName, bTextMode, buf ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Remove file from .bsp PAK lump +// Input : *relativename - +//----------------------------------------------------------------------------- +void RemoveFileFromPak( IZip *pak, const char *relativename ) +{ + pak->RemoveFileFromZip( relativename ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Get next filename in directory +// Input : id, -1 to start, returns next id, or -1 at list conclusion +//----------------------------------------------------------------------------- +int GetNextFilename( IZip *pak, int id, char *pBuffer, int bufferSize, int &fileSize ) +{ + return pak->GetNextFilename( id, pBuffer, bufferSize, fileSize ); +} + +//----------------------------------------------------------------------------- +// Convert four-CC code to a handle + back +//----------------------------------------------------------------------------- +GameLumpHandle_t CGameLump::GetGameLumpHandle( GameLumpId_t id ) +{ + // NOTE: I'm also expecting game lump id's to be four-CC codes + Assert( id > HEADER_LUMPS ); + + FOR_EACH_LL(m_GameLumps, i) + { + if (m_GameLumps[i].m_Id == id) + return i; + } + + return InvalidGameLump(); +} + +GameLumpId_t CGameLump::GetGameLumpId( GameLumpHandle_t handle ) +{ + return m_GameLumps[handle].m_Id; +} + +int CGameLump::GetGameLumpFlags( GameLumpHandle_t handle ) +{ + return m_GameLumps[handle].m_Flags; +} + +int CGameLump::GetGameLumpVersion( GameLumpHandle_t handle ) +{ + return m_GameLumps[handle].m_Version; +} + + +//----------------------------------------------------------------------------- +// Game lump accessor methods +//----------------------------------------------------------------------------- + +void* CGameLump::GetGameLump( GameLumpHandle_t id ) +{ + return m_GameLumps[id].m_Memory.Base(); +} + +int CGameLump::GameLumpSize( GameLumpHandle_t id ) +{ + return m_GameLumps[id].m_Memory.NumAllocated(); +} + + +//----------------------------------------------------------------------------- +// Game lump iteration methods +//----------------------------------------------------------------------------- + +GameLumpHandle_t CGameLump::FirstGameLump() +{ + return (m_GameLumps.Count()) ? m_GameLumps.Head() : InvalidGameLump(); +} + +GameLumpHandle_t CGameLump::NextGameLump( GameLumpHandle_t handle ) +{ + + return (m_GameLumps.IsValidIndex(handle)) ? m_GameLumps.Next(handle) : InvalidGameLump(); +} + +GameLumpHandle_t CGameLump::InvalidGameLump() +{ + return 0xFFFF; +} + + +//----------------------------------------------------------------------------- +// Game lump creation/destruction method +//----------------------------------------------------------------------------- + +GameLumpHandle_t CGameLump::CreateGameLump( GameLumpId_t id, int size, int flags, int version ) +{ + Assert( GetGameLumpHandle(id) == InvalidGameLump() ); + GameLumpHandle_t handle = m_GameLumps.AddToTail(); + m_GameLumps[handle].m_Id = id; + m_GameLumps[handle].m_Flags = flags; + m_GameLumps[handle].m_Version = version; + m_GameLumps[handle].m_Memory.EnsureCapacity( size ); + return handle; +} + +void CGameLump::DestroyGameLump( GameLumpHandle_t handle ) +{ + m_GameLumps.Remove( handle ); +} + +void CGameLump::DestroyAllGameLumps() +{ + m_GameLumps.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Compute file size and clump count +//----------------------------------------------------------------------------- + +void CGameLump::ComputeGameLumpSizeAndCount( int& size, int& clumpCount ) +{ + // Figure out total size of the client lumps + size = 0; + clumpCount = 0; + GameLumpHandle_t h; + for( h = FirstGameLump(); h != InvalidGameLump(); h = NextGameLump( h ) ) + { + ++clumpCount; + size += GameLumpSize( h ); + } + + // Add on headers + size += sizeof( dgamelumpheader_t ) + clumpCount * sizeof( dgamelump_t ); +} + + +void CGameLump::SwapGameLump( GameLumpId_t id, int version, byte *dest, byte *src, int length ) +{ + int count = 0; + switch( id ) + { + case GAMELUMP_STATIC_PROPS: + // Swap the static prop model dict + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + g_Swap.SwapFieldsToTargetEndian( (StaticPropDictLump_t*)dest, (StaticPropDictLump_t*)src, count ); + src += sizeof(StaticPropDictLump_t) * count; + dest += sizeof(StaticPropDictLump_t) * count; + + // Swap the leaf list + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + g_Swap.SwapFieldsToTargetEndian( (StaticPropLeafLump_t*)dest, (StaticPropLeafLump_t*)src, count ); + src += sizeof(StaticPropLeafLump_t) * count; + dest += sizeof(StaticPropLeafLump_t) * count; + + // Swap the models + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + // The one-at-a-time swap is to compensate for these structures + // possibly being misaligned, which crashes the Xbox 360. + if ( version == 4 ) + { + StaticPropLumpV4_t lump; + for ( int i = 0; i < count; ++i ) + { + Q_memcpy( &lump, src, sizeof(StaticPropLumpV4_t) ); + g_Swap.SwapFieldsToTargetEndian( &lump, &lump ); + Q_memcpy( dest, &lump, sizeof(StaticPropLumpV4_t) ); + src += sizeof( StaticPropLumpV4_t ); + dest += sizeof( StaticPropLumpV4_t ); + } + } + else if ( version == 5 ) + { + StaticPropLumpV5_t lump; + for ( int i = 0; i < count; ++i ) + { + Q_memcpy( &lump, src, sizeof(StaticPropLumpV5_t) ); + g_Swap.SwapFieldsToTargetEndian( &lump, &lump ); + Q_memcpy( dest, &lump, sizeof(StaticPropLumpV5_t) ); + src += sizeof( StaticPropLumpV5_t ); + dest += sizeof( StaticPropLumpV5_t ); + } + } + else + { + if ( version != 6 ) + { + Error( "Unknown Static Prop Lump version %d didn't get swapped!\n", version ); + } + + StaticPropLump_t lump; + for ( int i = 0; i < count; ++i ) + { + Q_memcpy( &lump, src, sizeof(StaticPropLump_t) ); + g_Swap.SwapFieldsToTargetEndian( &lump, &lump ); + Q_memcpy( dest, &lump, sizeof(StaticPropLump_t) ); + src += sizeof( StaticPropLump_t ); + dest += sizeof( StaticPropLump_t ); + } + } + break; + + case GAMELUMP_DETAIL_PROPS: + // Swap the detail prop model dict + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + g_Swap.SwapFieldsToTargetEndian( (DetailObjectDictLump_t*)dest, (DetailObjectDictLump_t*)src, count ); + src += sizeof(DetailObjectDictLump_t) * count; + dest += sizeof(DetailObjectDictLump_t) * count; + + if ( version == 4 ) + { + // Swap the detail sprite dict + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + DetailSpriteDictLump_t spritelump; + for ( int i = 0; i < count; ++i ) + { + Q_memcpy( &spritelump, src, sizeof(DetailSpriteDictLump_t) ); + g_Swap.SwapFieldsToTargetEndian( &spritelump, &spritelump ); + Q_memcpy( dest, &spritelump, sizeof(DetailSpriteDictLump_t) ); + src += sizeof(DetailSpriteDictLump_t); + dest += sizeof(DetailSpriteDictLump_t); + } + + // Swap the models + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + DetailObjectLump_t objectlump; + for ( int i = 0; i < count; ++i ) + { + Q_memcpy( &objectlump, src, sizeof(DetailObjectLump_t) ); + g_Swap.SwapFieldsToTargetEndian( &objectlump, &objectlump ); + Q_memcpy( dest, &objectlump, sizeof(DetailObjectLump_t) ); + src += sizeof(DetailObjectLump_t); + dest += sizeof(DetailObjectLump_t); + } + } + break; + + case GAMELUMP_DETAIL_PROP_LIGHTING: + // Swap the LDR light styles + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + g_Swap.SwapFieldsToTargetEndian( (DetailPropLightstylesLump_t*)dest, (DetailPropLightstylesLump_t*)src, count ); + src += sizeof(DetailObjectDictLump_t) * count; + dest += sizeof(DetailObjectDictLump_t) * count; + break; + + case GAMELUMP_DETAIL_PROP_LIGHTING_HDR: + // Swap the HDR light styles + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + g_Swap.SwapFieldsToTargetEndian( (DetailPropLightstylesLump_t*)dest, (DetailPropLightstylesLump_t*)src, count ); + src += sizeof(DetailObjectDictLump_t) * count; + dest += sizeof(DetailObjectDictLump_t) * count; + break; + + default: + char idchars[5] = {0}; + Q_memcpy( idchars, &id, 4 ); + Warning( "Unknown game lump '%s' didn't get swapped!\n", idchars ); + memcpy ( dest, src, length); + break; + } +} + +//----------------------------------------------------------------------------- +// Game lump file I/O +//----------------------------------------------------------------------------- +void CGameLump::ParseGameLump( dheader_t* pHeader ) +{ + g_GameLumps.DestroyAllGameLumps(); + + g_Lumps.bLumpParsed[LUMP_GAME_LUMP] = true; + + int length = pHeader->lumps[LUMP_GAME_LUMP].filelen; + int ofs = pHeader->lumps[LUMP_GAME_LUMP].fileofs; + + if (length > 0) + { + // Read dictionary... + dgamelumpheader_t* pGameLumpHeader = (dgamelumpheader_t*)((byte *)pHeader + ofs); + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( pGameLumpHeader ); + } + dgamelump_t* pGameLump = (dgamelump_t*)(pGameLumpHeader + 1); + for (int i = 0; i < pGameLumpHeader->lumpCount; ++i ) + { + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( &pGameLump[i] ); + } + + int length = pGameLump[i].filelen; + GameLumpHandle_t lump = g_GameLumps.CreateGameLump( pGameLump[i].id, length, pGameLump[i].flags, pGameLump[i].version ); + if ( g_bSwapOnLoad ) + { + SwapGameLump( pGameLump[i].id, pGameLump[i].version, (byte*)g_GameLumps.GetGameLump(lump), (byte *)pHeader + pGameLump[i].fileofs, length ); + } + else + { + memcpy( g_GameLumps.GetGameLump(lump), (byte *)pHeader + pGameLump[i].fileofs, length ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// String table methods +//----------------------------------------------------------------------------- +const char *TexDataStringTable_GetString( int stringID ) +{ + return &g_TexDataStringData[g_TexDataStringTable[stringID]]; +} + +int TexDataStringTable_AddOrFindString( const char *pString ) +{ + int i; + // garymcthack: Make this use an RBTree! + for( i = 0; i < g_TexDataStringTable.Count(); i++ ) + { + if( stricmp( pString, &g_TexDataStringData[g_TexDataStringTable[i]] ) == 0 ) + { + return i; + } + } + + int len = strlen( pString ); + int outOffset = g_TexDataStringData.AddMultipleToTail( len+1, pString ); + int outIndex = g_TexDataStringTable.AddToTail( outOffset ); + return outIndex; +} + +//----------------------------------------------------------------------------- +// Adds all game lumps into one big block +//----------------------------------------------------------------------------- + +static void AddGameLumps( ) +{ + // Figure out total size of the client lumps + int size, clumpCount; + g_GameLumps.ComputeGameLumpSizeAndCount( size, clumpCount ); + + // Set up the main lump dictionary entry + g_Lumps.size[LUMP_GAME_LUMP] = 0; // mark it written + + lump_t* lump = &g_pBSPHeader->lumps[LUMP_GAME_LUMP]; + + lump->fileofs = g_pFileSystem->Tell( g_hBSPFile ); + lump->filelen = size; + + // write header + dgamelumpheader_t header; + header.lumpCount = clumpCount; + WriteData( &header ); + + // write dictionary + dgamelump_t dict; + int offset = lump->fileofs + sizeof(header) + clumpCount * sizeof(dgamelump_t); + GameLumpHandle_t h; + for( h = g_GameLumps.FirstGameLump(); h != g_GameLumps.InvalidGameLump(); h = g_GameLumps.NextGameLump( h ) ) + { + dict.id = g_GameLumps.GetGameLumpId(h); + dict.version = g_GameLumps.GetGameLumpVersion(h); + dict.flags = g_GameLumps.GetGameLumpFlags(h); + dict.fileofs = offset; + dict.filelen = g_GameLumps.GameLumpSize( h ); + offset += dict.filelen; + + WriteData( &dict ); + } + + // write lumps.. + for( h = g_GameLumps.FirstGameLump(); h != g_GameLumps.InvalidGameLump(); h = g_GameLumps.NextGameLump( h ) ) + { + unsigned int lumpsize = g_GameLumps.GameLumpSize(h); + if ( g_bSwapOnWrite ) + { + g_GameLumps.SwapGameLump( g_GameLumps.GetGameLumpId(h), g_GameLumps.GetGameLumpVersion(h), (byte*)g_GameLumps.GetGameLump(h), (byte*)g_GameLumps.GetGameLump(h), lumpsize ); + } + SafeWrite( g_hBSPFile, g_GameLumps.GetGameLump(h), lumpsize ); + } + + // align to doubleword + AlignFilePosition( g_hBSPFile, 4 ); +} + + +//----------------------------------------------------------------------------- +// Adds the occluder lump... +//----------------------------------------------------------------------------- +static void AddOcclusionLump( ) +{ + g_Lumps.size[LUMP_OCCLUSION] = 0; // mark it written + + int nOccluderCount = g_OccluderData.Count(); + int nOccluderPolyDataCount = g_OccluderPolyData.Count(); + int nOccluderVertexIndices = g_OccluderVertexIndices.Count(); + + int nLumpLength = nOccluderCount * sizeof(doccluderdata_t) + + nOccluderPolyDataCount * sizeof(doccluderpolydata_t) + + nOccluderVertexIndices * sizeof(int) + + 3 * sizeof(int); + + lump_t *lump = &g_pBSPHeader->lumps[LUMP_OCCLUSION]; + + lump->fileofs = g_pFileSystem->Tell( g_hBSPFile ); + lump->filelen = nLumpLength; + lump->version = LUMP_OCCLUSION_VERSION; + lump->fourCC[0] = ( char )0; + lump->fourCC[1] = ( char )0; + lump->fourCC[2] = ( char )0; + lump->fourCC[3] = ( char )0; + + // Data is swapped in place, so the 'Count' variables aren't safe to use after they're written + WriteData( FIELD_INTEGER, &nOccluderCount ); + WriteData( (doccluderdata_t*)g_OccluderData.Base(), g_OccluderData.Count() ); + WriteData( FIELD_INTEGER, &nOccluderPolyDataCount ); + WriteData( (doccluderpolydata_t*)g_OccluderPolyData.Base(), g_OccluderPolyData.Count() ); + WriteData( FIELD_INTEGER, &nOccluderVertexIndices ); + WriteData( FIELD_INTEGER, (int*)g_OccluderVertexIndices.Base(), g_OccluderVertexIndices.Count() ); +} + + +//----------------------------------------------------------------------------- +// Loads the occluder lump... +//----------------------------------------------------------------------------- +static void UnserializeOcclusionLumpV2( CUtlBuffer &buf ) +{ + int nCount = buf.GetInt(); + if ( nCount ) + { + g_OccluderData.SetCount( nCount ); + buf.GetObjects( g_OccluderData.Base(), nCount ); + } + + nCount = buf.GetInt(); + if ( nCount ) + { + g_OccluderPolyData.SetCount( nCount ); + buf.GetObjects( g_OccluderPolyData.Base(), nCount ); + } + + nCount = buf.GetInt(); + if ( nCount ) + { + if ( g_bSwapOnLoad ) + { + g_Swap.SwapBufferToTargetEndian( (int*)buf.PeekGet(), (int*)buf.PeekGet(), nCount ); + } + g_OccluderVertexIndices.SetCount( nCount ); + buf.Get( g_OccluderVertexIndices.Base(), nCount * sizeof(g_OccluderVertexIndices[0]) ); + } +} + + +static void LoadOcclusionLump() +{ + g_OccluderData.RemoveAll(); + g_OccluderPolyData.RemoveAll(); + g_OccluderVertexIndices.RemoveAll(); + + int length, ofs; + + g_Lumps.bLumpParsed[LUMP_OCCLUSION] = true; + + length = g_pBSPHeader->lumps[LUMP_OCCLUSION].filelen; + ofs = g_pBSPHeader->lumps[LUMP_OCCLUSION].fileofs; + + CUtlBuffer buf( (byte *)g_pBSPHeader + ofs, length, CUtlBuffer::READ_ONLY ); + buf.ActivateByteSwapping( g_bSwapOnLoad ); + switch ( g_pBSPHeader->lumps[LUMP_OCCLUSION].version ) + { + case 2: + UnserializeOcclusionLumpV2( buf ); + break; + + case 0: + break; + + default: + Error("Unknown occlusion lump version!\n"); + break; + } +} + + +/* +=============== +CompressVis + +=============== +*/ +int CompressVis (byte *vis, byte *dest) +{ + int j; + int rep; + int visrow; + byte *dest_p; + + dest_p = dest; +// visrow = (r_numvisleafs + 7)>>3; + visrow = (dvis->numclusters + 7)>>3; + + for (j=0 ; j>3; + row = (dvis->numclusters+7)>>3; + out = decompressed; + + do + { + if (*in) + { + *out++ = *in++; + continue; + } + + c = in[1]; + if (!c) + Error ("DecompressVis: 0 repeat"); + in += 2; + if ((out - decompressed) + c > row) + { + c = row - (out - decompressed); + Warning( "warning: Vis decompression overrun\n" ); + } + while (c) + { + *out++ = 0; + c--; + } + } while (out - decompressed < row); +} + +//----------------------------------------------------------------------------- +// Lump-specific swap functions +//----------------------------------------------------------------------------- +struct swapcollideheader_t +{ + DECLARE_BYTESWAP_DATADESC(); + int size; + int vphysicsID; + short version; + short modelType; +}; + +struct swapcompactsurfaceheader_t : swapcollideheader_t +{ + DECLARE_BYTESWAP_DATADESC(); + int surfaceSize; + Vector dragAxisAreas; + int axisMapSize; +}; + +struct swapmoppsurfaceheader_t : swapcollideheader_t +{ + DECLARE_BYTESWAP_DATADESC(); + int moppSize; +}; + +BEGIN_BYTESWAP_DATADESC( swapcollideheader_t ) + DEFINE_FIELD( size, FIELD_INTEGER ), + DEFINE_FIELD( vphysicsID, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_SHORT ), + DEFINE_FIELD( modelType, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC_( swapcompactsurfaceheader_t, swapcollideheader_t ) + DEFINE_FIELD( surfaceSize, FIELD_INTEGER ), + DEFINE_FIELD( dragAxisAreas, FIELD_VECTOR ), + DEFINE_FIELD( axisMapSize, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC_( swapmoppsurfaceheader_t, swapcollideheader_t ) + DEFINE_FIELD( moppSize, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + + +static void SwapPhyscollideLump( byte *pDestBase, byte *pSrcBase, unsigned int &count ) +{ + IPhysicsCollision *physcollision = NULL; + CSysModule *pPhysicsModule = g_pFullFileSystem->LoadModule( "vphysics.dll" ); + if ( pPhysicsModule ) + { + CreateInterfaceFn physicsFactory = Sys_GetFactory( pPhysicsModule ); + if ( physicsFactory ) + { + physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); + } + } + + if ( !physcollision ) + { + Warning("!!! WARNING: Can't swap the physcollide lump!\n" ); + return; + } + + // physics data is variable length. The last physmodel is a NULL pointer + // with modelIndex -1, dataSize -1 + dphysmodel_t *pPhysModel; + byte *pSrc = pSrcBase; + + // first the src chunks have to be aligned properly + // swap increases size, allocate enough expansion room + byte *pSrcAlignedBase = (byte*)malloc( 2*count ); + byte *basePtr = pSrcAlignedBase; + byte *pSrcAligned = pSrcAlignedBase; + + do + { + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( (dphysmodel_t*)pSrcAligned, (dphysmodel_t*)pSrc ); + } + else + { + Q_memcpy( pSrcAligned, pSrc, sizeof(dphysmodel_t) ); + } + pPhysModel = (dphysmodel_t*)pSrcAligned; + + pSrc += sizeof(dphysmodel_t); + pSrcAligned += sizeof(dphysmodel_t); + + if ( pPhysModel->dataSize > 0 ) + { + // Align the collide headers + for ( int i = 0; i < pPhysModel->solidCount; ++i ) + { + // Get data size + int size; + Q_memcpy( &size, pSrc, sizeof(int) ); + if ( g_bSwapOnLoad ) + size = SwapLong( size ); + + // Fixup size + int padBytes = 0; + if ( size % 4 != 0 ) + { + padBytes = ( 4 - size % 4 ); + count += padBytes; + pPhysModel->dataSize += padBytes; + } + + // Copy data and size into alligned buffer + int newsize = size + padBytes; + if ( g_bSwapOnLoad ) + newsize = SwapLong( newsize ); + + Q_memcpy( pSrcAligned, &newsize, sizeof(int) ); + Q_memcpy( pSrcAligned + sizeof(int), pSrc + sizeof(int), size ); + pSrcAligned += size + padBytes + sizeof(int); + pSrc += size + sizeof(int); + } + + int padBytes = 0; + int dataSize = pPhysModel->dataSize + pPhysModel->keydataSize; + Q_memcpy( pSrcAligned, pSrc, pPhysModel->keydataSize ); + pSrc += pPhysModel->keydataSize; + pSrcAligned += pPhysModel->keydataSize; + if ( dataSize % 4 != 0 ) + { + // Next chunk will be unaligned + padBytes = ( 4 - dataSize % 4 ); + pPhysModel->keydataSize += padBytes; + count += padBytes; + Q_memset( pSrcAligned, 0, padBytes ); + pSrcAligned += padBytes; + } + } + } while ( pPhysModel->dataSize > 0 ); + + // Now the data can be swapped properly + pSrcBase = pSrcAlignedBase; + pSrc = pSrcBase; + byte *pDest = pDestBase; + + do + { + // src headers are in native format + pPhysModel = (dphysmodel_t*)pSrc; + if ( g_bSwapOnWrite ) + { + g_Swap.SwapFieldsToTargetEndian( (dphysmodel_t*)pDest, (dphysmodel_t*)pSrc ); + } + else + { + Q_memcpy( pDest, pSrc, sizeof(dphysmodel_t) ); + } + + pSrc += sizeof(dphysmodel_t); + pDest += sizeof(dphysmodel_t); + + pSrcBase = pSrc; + pDestBase = pDest; + + if ( pPhysModel->dataSize > 0 ) + { + vcollide_t collide = {0}; + int dataSize = pPhysModel->dataSize + pPhysModel->keydataSize; + + if ( g_bSwapOnWrite ) + { + // Load the collide data + physcollision->VCollideLoad( &collide, pPhysModel->solidCount, (const char *)pSrc, dataSize, false ); + } + + int *offsets = new int[ pPhysModel->solidCount ]; + + // Swap the collision data headers + for ( int i = 0; i < pPhysModel->solidCount; ++i ) + { + int headerSize = 0; + swapcollideheader_t *baseHdr = (swapcollideheader_t*)pSrc; + short modelType = baseHdr->modelType; + if ( g_bSwapOnLoad ) + { + g_Swap.SwapBufferToTargetEndian( &modelType ); + } + + if ( modelType == 0 ) // COLLIDE_POLY + { + headerSize = sizeof(swapcompactsurfaceheader_t); + swapcompactsurfaceheader_t swapHdr; + Q_memcpy( &swapHdr, pSrc, headerSize ); + g_Swap.SwapFieldsToTargetEndian( &swapHdr, &swapHdr ); + Q_memcpy( pDest, &swapHdr, headerSize ); + } + else if ( modelType == 1 ) // COLLIDE_MOPP + { + // The PC still unserializes these, but we don't support them + if ( g_bSwapOnWrite ) + { + collide.solids[i] = NULL; + } + + headerSize = sizeof(swapmoppsurfaceheader_t); + swapmoppsurfaceheader_t swapHdr; + Q_memcpy( &swapHdr, pSrc, headerSize ); + g_Swap.SwapFieldsToTargetEndian( &swapHdr, &swapHdr ); + Q_memcpy( pDest, &swapHdr, headerSize ); + + } + else + { + // Shouldn't happen + Assert( 0 ); + } + + if ( g_bSwapOnLoad ) + { + // src needs the native header data to load the vcollides + Q_memcpy( pSrc, pDest, headerSize ); + } + // HACK: Need either surfaceSize or moppSize - both sit at the same offset in the structure + swapmoppsurfaceheader_t *hdr = (swapmoppsurfaceheader_t*)pSrc; + pSrc += hdr->size + sizeof(int); + pDest += hdr->size + sizeof(int); + offsets[i] = hdr->size; + } + + pSrc = pSrcBase; + pDest = pDestBase; + if ( g_bSwapOnLoad ) + { + physcollision->VCollideLoad( &collide, pPhysModel->solidCount, (const char *)pSrc, dataSize, true ); + } + + // Write out the ledge tree data + for ( int i = 0; i < pPhysModel->solidCount; ++i ) + { + if ( collide.solids[i] ) + { + // skip over the size member + pSrc += sizeof(int); + pDest += sizeof(int); + int offset = physcollision->CollideWrite( (char*)pDest, collide.solids[i], g_bSwapOnWrite ); + pSrc += offset; + pDest += offset; + } + else + { + pSrc += offsets[i] + sizeof(int); + pDest += offsets[i] + sizeof(int); + } + } + + // copy the keyvalues data + Q_memcpy( pDest, pSrc, pPhysModel->keydataSize ); + pDest += pPhysModel->keydataSize; + pSrc += pPhysModel->keydataSize; + + // Free the memory + physcollision->VCollideUnload( &collide ); + delete [] offsets; + } + + // avoid infinite loop on badly formed file + if ( (pSrc - basePtr) > count ) + break; + + } while ( pPhysModel->dataSize > 0 ); + + free( pSrcAlignedBase ); +} + + +// UNDONE: This code is not yet tested. +static void SwapPhysdispLump( byte *pDest, byte *pSrc, int count ) +{ + // the format of this lump is one unsigned short dispCount, then dispCount unsigned shorts of sizes + // followed by an array of variable length (each element is the length of the corresponding entry in the + // previous table) byte-stream data structure of the displacement collision models + // these byte-stream structs are endian-neutral because each element is byte-sized + unsigned short dispCount = *(unsigned short*)pSrc; + if ( g_bSwapOnLoad ) + { + g_Swap.SwapBufferToTargetEndian( &dispCount ); + } + g_Swap.SwapBufferToTargetEndian( (unsigned short*)pDest, (unsigned short*)pSrc, dispCount + 1 ); + + const int nBytes = (dispCount + 1) * sizeof( unsigned short ); + pSrc += nBytes; + pDest += nBytes; + count -= nBytes; + + g_Swap.SwapBufferToTargetEndian( pDest, pSrc, count ); +} + + +static void SwapVisibilityLump( byte *pDest, byte *pSrc, int count ) +{ + int firstInt = *(int*)pSrc; + if ( g_bSwapOnLoad ) + { + g_Swap.SwapBufferToTargetEndian( &firstInt ); + } + int intCt = firstInt * 2 + 1; + const int hdrSize = intCt * sizeof(int); + g_Swap.SwapBufferToTargetEndian( (int*)pDest, (int*)pSrc, intCt ); + g_Swap.SwapBufferToTargetEndian( pDest + hdrSize, pSrc + hdrSize, count - hdrSize ); +} + +//============================================================================= +void Lumps_Init( void ) +{ + memset( &g_Lumps, 0, sizeof(g_Lumps) ); +} + +int LumpVersion( int lump ) +{ + return g_pBSPHeader->lumps[lump].version; +} + +bool HasLump( int lump ) +{ + return g_pBSPHeader->lumps[lump].filelen > 0; +} + +void ValidateLump( int lump, int length, int size, int forceVersion ) +{ + if ( length % size ) + { + Error( "ValidateLump: odd size for lump %d", lump ); + } + + if ( forceVersion >= 0 && forceVersion != g_pBSPHeader->lumps[lump].version ) + { + Error( "ValidateLump: old version for lump %d in map!", lump ); + } +} + +//----------------------------------------------------------------------------- +// Add Lumps of integral types without datadescs +//----------------------------------------------------------------------------- +template< class T > +int CopyLumpInternal( int fieldType, int lump, T *dest, int forceVersion ) +{ + g_Lumps.bLumpParsed[lump] = true; + + // Vectors are passed in as floats + int fieldSize = ( fieldType == FIELD_VECTOR ) ? sizeof(Vector) : sizeof(T); + unsigned int length = g_pBSPHeader->lumps[lump].filelen; + unsigned int ofs = g_pBSPHeader->lumps[lump].fileofs; + + // count must be of the integral type + unsigned int count = length / sizeof(T); + + ValidateLump( lump, length, fieldSize, forceVersion ); + + if ( g_bSwapOnLoad ) + { + switch( lump ) + { + case LUMP_VISIBILITY: + SwapVisibilityLump( (byte*)dest, ((byte*)g_pBSPHeader + ofs), count ); + break; + + case LUMP_PHYSCOLLIDE: + // SwapPhyscollideLump may change size + SwapPhyscollideLump( (byte*)dest, ((byte*)g_pBSPHeader + ofs), count ); + length = count; + break; + + case LUMP_PHYSDISP: + SwapPhysdispLump( (byte*)dest, ((byte*)g_pBSPHeader + ofs), count ); + break; + + default: + g_Swap.SwapBufferToTargetEndian( dest, (T*)((byte*)g_pBSPHeader + ofs), count ); + break; + } + } + else + { + memcpy( dest, (byte*)g_pBSPHeader + ofs, length ); + } + + // Return actual count of elements + return length / fieldSize; +} + +template< class T > +int CopyLump( int fieldType, int lump, T *dest, int forceVersion = -1 ) +{ + return CopyLumpInternal( fieldType, lump, dest, forceVersion ); +} + +template< class T > +void CopyLump( int fieldType, int lump, CUtlVector &dest, int forceVersion = -1 ) +{ + Assert( fieldType != FIELD_VECTOR ); // TODO: Support this if necessary + dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); + CopyLumpInternal( fieldType, lump, dest.Base(), forceVersion ); +} + +template< class T > +void CopyOptionalLump( int fieldType, int lump, CUtlVector &dest, int forceVersion = -1 ) +{ + // not fatal if not present + if ( !HasLump( lump ) ) + return; + + dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); + CopyLumpInternal( fieldType, lump, dest.Base(), forceVersion ); +} + +template< class T > +int CopyVariableLump( int fieldType, int lump, void **dest, int forceVersion = -1 ) +{ + int length = g_pBSPHeader->lumps[lump].filelen; + *dest = malloc( length ); + + return CopyLumpInternal( fieldType, lump, (T*)*dest, forceVersion ); +} + +//----------------------------------------------------------------------------- +// Add Lumps of object types with datadescs +//----------------------------------------------------------------------------- +template< class T > +int CopyLumpInternal( int lump, T *dest, int forceVersion ) +{ + g_Lumps.bLumpParsed[lump] = true; + + unsigned int length = g_pBSPHeader->lumps[lump].filelen; + unsigned int ofs = g_pBSPHeader->lumps[lump].fileofs; + unsigned int count = length / sizeof(T); + + ValidateLump( lump, length, sizeof(T), forceVersion ); + + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( dest, (T*)((byte*)g_pBSPHeader + ofs), count ); + } + else + { + memcpy( dest, (byte*)g_pBSPHeader + ofs, length ); + } + + return count; +} + +template< class T > +int CopyLump( int lump, T *dest, int forceVersion = -1 ) +{ + return CopyLumpInternal( lump, dest, forceVersion ); +} + +template< class T > +void CopyLump( int lump, CUtlVector &dest, int forceVersion = -1 ) +{ + dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); + CopyLumpInternal( lump, dest.Base(), forceVersion ); +} + +template< class T > +void CopyOptionalLump( int lump, CUtlVector &dest, int forceVersion = -1 ) +{ + // not fatal if not present + if ( !HasLump( lump ) ) + return; + + dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); + CopyLumpInternal( lump, dest.Base(), forceVersion ); +} + +template< class T > +int CopyVariableLump( int lump, void **dest, int forceVersion = -1 ) +{ + int length = g_pBSPHeader->lumps[lump].filelen; + *dest = malloc( length ); + + return CopyLumpInternal( lump, (T*)*dest, forceVersion ); +} + +//----------------------------------------------------------------------------- +// Add/Write unknown lumps +//----------------------------------------------------------------------------- +void Lumps_Parse( void ) +{ + int i; + + for ( i = 0; i < HEADER_LUMPS; i++ ) + { + if ( !g_Lumps.bLumpParsed[i] && g_pBSPHeader->lumps[i].filelen ) + { + g_Lumps.size[i] = CopyVariableLump( FIELD_CHARACTER, i, &g_Lumps.pLumps[i], -1 ); + Msg( "Reading unknown lump #%d (%d bytes)\n", i, g_Lumps.size[i] ); + } + } +} + +void Lumps_Write( void ) +{ + int i; + + for ( i = 0; i < HEADER_LUMPS; i++ ) + { + if ( g_Lumps.size[i] ) + { + Msg( "Writing unknown lump #%d (%d bytes)\n", i, g_Lumps.size[i] ); + AddLump( i, (byte*)g_Lumps.pLumps[i], g_Lumps.size[i] ); + } + if ( g_Lumps.pLumps[i] ) + { + free( g_Lumps.pLumps[i] ); + g_Lumps.pLumps[i] = NULL; + } + } +} + +int LoadLeafs( void ) +{ +#if defined( BSP_USE_LESS_MEMORY ) + dleafs = (dleaf_t*)malloc( g_pBSPHeader->lumps[LUMP_LEAFS].filelen ); +#endif + + switch ( LumpVersion( LUMP_LEAFS ) ) + { + case 0: + { + g_Lumps.bLumpParsed[LUMP_LEAFS] = true; + int length = g_pBSPHeader->lumps[LUMP_LEAFS].filelen; + int size = sizeof( dleaf_version_0_t ); + if ( length % size ) + { + Error( "odd size for LUMP_LEAFS\n" ); + } + int count = length / size; + + void *pSrcBase = ( ( byte * )g_pBSPHeader + g_pBSPHeader->lumps[LUMP_LEAFS].fileofs ); + dleaf_version_0_t *pSrc = (dleaf_version_0_t *)pSrcBase; + dleaf_t *pDst = dleafs; + + // version 0 predates HDR, build the LDR + g_LeafAmbientLightingLDR.SetCount( count ); + g_LeafAmbientIndexLDR.SetCount( count ); + + dleafambientlighting_t *pDstLeafAmbientLighting = &g_LeafAmbientLightingLDR[0]; + for ( int i = 0; i < count; i++ ) + { + g_LeafAmbientIndexLDR[i].ambientSampleCount = 1; + g_LeafAmbientIndexLDR[i].firstAmbientSample = i; + + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( pSrc ); + } + // pDst is a subset of pSrc; + *pDst = *( ( dleaf_t * )( void * )pSrc ); + pDstLeafAmbientLighting->cube = pSrc->m_AmbientLighting; + pDstLeafAmbientLighting->x = pDstLeafAmbientLighting->y = pDstLeafAmbientLighting->z = pDstLeafAmbientLighting->pad = 0; + pDst++; + pSrc++; + pDstLeafAmbientLighting++; + } + return count; + } + + case 1: + return CopyLump( LUMP_LEAFS, dleafs ); + + default: + Assert( 0 ); + Error( "Unknown LUMP_LEAFS version\n" ); + return 0; + } +} + +void LoadLeafAmbientLighting( int numLeafs ) +{ + if ( LumpVersion( LUMP_LEAFS ) == 0 ) + { + // an older leaf version already built the LDR ambient lighting on load + return; + } + + // old BSP with ambient, or new BSP with no lighting, convert ambient light to new format or create dummy ambient + if ( !HasLump( LUMP_LEAF_AMBIENT_INDEX ) ) + { + // a bunch of legacy maps, have these lumps with garbage versions + // expect them to be NOT the current version + if ( HasLump(LUMP_LEAF_AMBIENT_LIGHTING) ) + { + Assert( LumpVersion( LUMP_LEAF_AMBIENT_LIGHTING ) != LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); + } + if ( HasLump(LUMP_LEAF_AMBIENT_LIGHTING_HDR) ) + { + Assert( LumpVersion( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) != LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); + } + + void *pSrcBase = ( ( byte * )g_pBSPHeader + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].fileofs ); + CompressedLightCube *pSrc = NULL; + if ( HasLump( LUMP_LEAF_AMBIENT_LIGHTING ) ) + { + pSrc = (CompressedLightCube*)pSrcBase; + } + g_LeafAmbientIndexLDR.SetCount( numLeafs ); + g_LeafAmbientLightingLDR.SetCount( numLeafs ); + + void *pSrcBaseHDR = ( ( byte * )g_pBSPHeader + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].fileofs ); + CompressedLightCube *pSrcHDR = NULL; + if ( HasLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) ) + { + pSrcHDR = (CompressedLightCube*)pSrcBaseHDR; + } + g_LeafAmbientIndexHDR.SetCount( numLeafs ); + g_LeafAmbientLightingHDR.SetCount( numLeafs ); + + for ( int i = 0; i < numLeafs; i++ ) + { + g_LeafAmbientIndexLDR[i].ambientSampleCount = 1; + g_LeafAmbientIndexLDR[i].firstAmbientSample = i; + g_LeafAmbientIndexHDR[i].ambientSampleCount = 1; + g_LeafAmbientIndexHDR[i].firstAmbientSample = i; + + Q_memset( &g_LeafAmbientLightingLDR[i], 0, sizeof(g_LeafAmbientLightingLDR[i]) ); + Q_memset( &g_LeafAmbientLightingHDR[i], 0, sizeof(g_LeafAmbientLightingHDR[i]) ); + + if ( pSrc ) + { + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( &pSrc[i] ); + } + g_LeafAmbientLightingLDR[i].cube = pSrc[i]; + } + if ( pSrcHDR ) + { + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( &pSrcHDR[i] ); + } + g_LeafAmbientLightingHDR[i].cube = pSrcHDR[i]; + } + } + + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING] = true; + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX] = true; + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING_HDR] = true; + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX_HDR] = true; + } + else + { + CopyOptionalLump( LUMP_LEAF_AMBIENT_LIGHTING, g_LeafAmbientLightingLDR ); + CopyOptionalLump( LUMP_LEAF_AMBIENT_INDEX, g_LeafAmbientIndexLDR ); + CopyOptionalLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR, g_LeafAmbientLightingHDR ); + CopyOptionalLump( LUMP_LEAF_AMBIENT_INDEX_HDR, g_LeafAmbientIndexHDR ); + } +} + +void ValidateHeader( const char *filename, const dheader_t *pHeader ) +{ + if ( pHeader->ident != IDBSPHEADER ) + { + Error ("%s is not a IBSP file", filename); + } + if ( pHeader->version < MINBSPVERSION || pHeader->version > BSPVERSION ) + { + Error ("%s is version %i, not %i", filename, pHeader->version, BSPVERSION); + } +} + +//----------------------------------------------------------------------------- +// Low level BSP opener for external parsing. Parses headers, but nothing else. +// You must close the BSP, via CloseBSPFile(). +//----------------------------------------------------------------------------- +void OpenBSPFile( const char *filename ) +{ + Lumps_Init(); + + // load the file header + LoadFile( filename, (void **)&g_pBSPHeader ); + + if ( g_bSwapOnLoad ) + { + g_Swap.ActivateByteSwapping( true ); + g_Swap.SwapFieldsToTargetEndian( g_pBSPHeader ); + } + + ValidateHeader( filename, g_pBSPHeader ); + + g_MapRevision = g_pBSPHeader->mapRevision; +} + +//----------------------------------------------------------------------------- +// CloseBSPFile +//----------------------------------------------------------------------------- +void CloseBSPFile( void ) +{ + free( g_pBSPHeader ); + g_pBSPHeader = NULL; +} + +//----------------------------------------------------------------------------- +// LoadBSPFile +//----------------------------------------------------------------------------- +void LoadBSPFile( const char *filename ) +{ + OpenBSPFile( filename ); + + nummodels = CopyLump( LUMP_MODELS, dmodels ); + numvertexes = CopyLump( LUMP_VERTEXES, dvertexes ); + numplanes = CopyLump( LUMP_PLANES, dplanes ); + numleafs = LoadLeafs(); + numnodes = CopyLump( LUMP_NODES, dnodes ); + CopyLump( LUMP_TEXINFO, texinfo ); + numtexdata = CopyLump( LUMP_TEXDATA, dtexdata ); + + CopyLump( LUMP_DISPINFO, g_dispinfo ); + CopyLump( LUMP_DISP_VERTS, g_DispVerts ); + CopyLump( LUMP_DISP_TRIS, g_DispTris ); + CopyLump( FIELD_CHARACTER, LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS, g_DispLightmapSamplePositions ); + CopyLump( LUMP_FACE_MACRO_TEXTURE_INFO, g_FaceMacroTextureInfos ); + + numfaces = CopyLump(LUMP_FACES, dfaces, LUMP_FACES_VERSION); + if ( HasLump( LUMP_FACES_HDR ) ) + numfaces_hdr = CopyLump( LUMP_FACES_HDR, dfaces_hdr, LUMP_FACES_VERSION ); + else + numfaces_hdr = 0; + + CopyOptionalLump( LUMP_FACEIDS, dfaceids ); + + g_numprimitives = CopyLump( LUMP_PRIMITIVES, g_primitives ); + g_numprimverts = CopyLump( LUMP_PRIMVERTS, g_primverts ); + g_numprimindices = CopyLump( FIELD_SHORT, LUMP_PRIMINDICES, g_primindices ); + numorigfaces = CopyLump( LUMP_ORIGINALFACES, dorigfaces ); // original faces + numleaffaces = CopyLump( FIELD_SHORT, LUMP_LEAFFACES, dleaffaces ); + numleafbrushes = CopyLump( FIELD_SHORT, LUMP_LEAFBRUSHES, dleafbrushes ); + numsurfedges = CopyLump( FIELD_INTEGER, LUMP_SURFEDGES, dsurfedges ); + numedges = CopyLump( LUMP_EDGES, dedges ); + numbrushes = CopyLump( LUMP_BRUSHES, dbrushes ); + numbrushsides = CopyLump( LUMP_BRUSHSIDES, dbrushsides ); + numareas = CopyLump( LUMP_AREAS, dareas ); + numareaportals = CopyLump( LUMP_AREAPORTALS, dareaportals ); + + visdatasize = CopyLump ( FIELD_CHARACTER, LUMP_VISIBILITY, dvisdata ); + CopyOptionalLump( FIELD_CHARACTER, LUMP_LIGHTING, dlightdataLDR, LUMP_LIGHTING_VERSION ); + CopyOptionalLump( FIELD_CHARACTER, LUMP_LIGHTING_HDR, dlightdataHDR, LUMP_LIGHTING_VERSION ); + + LoadLeafAmbientLighting( numleafs ); + + CopyLump( FIELD_CHARACTER, LUMP_ENTITIES, dentdata ); + numworldlightsLDR = CopyLump( LUMP_WORLDLIGHTS, dworldlightsLDR ); + numworldlightsHDR = CopyLump( LUMP_WORLDLIGHTS_HDR, dworldlightsHDR ); + + numleafwaterdata = CopyLump( LUMP_LEAFWATERDATA, dleafwaterdata ); + g_PhysCollideSize = CopyVariableLump( FIELD_CHARACTER, LUMP_PHYSCOLLIDE, (void**)&g_pPhysCollide ); + g_PhysDispSize = CopyVariableLump( FIELD_CHARACTER, LUMP_PHYSDISP, (void**)&g_pPhysDisp ); + + g_numvertnormals = CopyLump( FIELD_VECTOR, LUMP_VERTNORMALS, (float*)g_vertnormals ); + g_numvertnormalindices = CopyLump( FIELD_SHORT, LUMP_VERTNORMALINDICES, g_vertnormalindices ); + + g_nClipPortalVerts = CopyLump( FIELD_VECTOR, LUMP_CLIPPORTALVERTS, (float*)g_ClipPortalVerts ); + g_nCubemapSamples = CopyLump( LUMP_CUBEMAPS, g_CubemapSamples ); + + CopyLump( FIELD_CHARACTER, LUMP_TEXDATA_STRING_DATA, g_TexDataStringData ); + CopyLump( FIELD_INTEGER, LUMP_TEXDATA_STRING_TABLE, g_TexDataStringTable ); + + g_nOverlayCount = CopyLump( LUMP_OVERLAYS, g_Overlays ); + g_nWaterOverlayCount = CopyLump( LUMP_WATEROVERLAYS, g_WaterOverlays ); + CopyLump( LUMP_OVERLAY_FADES, g_OverlayFades ); + + dflagslump_t flags_lump; + + if ( HasLump( LUMP_MAP_FLAGS ) ) + CopyLump ( LUMP_MAP_FLAGS, &flags_lump ); + else + memset( &flags_lump, 0, sizeof( flags_lump ) ); // default flags to 0 + + g_LevelFlags = flags_lump.m_LevelFlags; + + LoadOcclusionLump(); + + CopyLump( FIELD_SHORT, LUMP_LEAFMINDISTTOWATER, g_LeafMinDistToWater ); + + /* + int crap; + for( crap = 0; crap < g_nBSPStringTable; crap++ ) + { + Msg( "stringtable %d", ( int )crap ); + Msg( " %d:", ( int )g_BSPStringTable[crap] ); + puts( &g_BSPStringData[g_BSPStringTable[crap]] ); + puts( "\n" ); + } + */ + + // Load PAK file lump into appropriate data structure + byte *pakbuffer = NULL; + int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer ); + if ( paksize > 0 ) + { + GetPakFile()->ActivateByteSwapping( IsX360() ); + GetPakFile()->ParseFromBuffer( pakbuffer, paksize ); + } + else + { + GetPakFile()->Reset(); + } + + free( pakbuffer ); + + g_GameLumps.ParseGameLump( g_pBSPHeader ); + + // NOTE: Do NOT call CopyLump after Lumps_Parse() it parses all un-Copied lumps + // parse any additional lumps + Lumps_Parse(); + + // everything has been copied out + CloseBSPFile(); + + g_Swap.ActivateByteSwapping( false ); +} + +//----------------------------------------------------------------------------- +// Reset any state. +//----------------------------------------------------------------------------- +void UnloadBSPFile() +{ + nummodels = 0; + numvertexes = 0; + numplanes = 0; + + numleafs = 0; +#if defined( BSP_USE_LESS_MEMORY ) + if ( dleafs ) + { + free( dleafs ); + dleafs = NULL; + } +#endif + + numnodes = 0; + texinfo.Purge(); + numtexdata = 0; + + g_dispinfo.Purge(); + g_DispVerts.Purge(); + g_DispTris.Purge(); + + g_DispLightmapSamplePositions.Purge(); + g_FaceMacroTextureInfos.Purge(); + + numfaces = 0; + numfaces_hdr = 0; + + dfaceids.Purge(); + + g_numprimitives = 0; + g_numprimverts = 0; + g_numprimindices = 0; + numorigfaces = 0; + numleaffaces = 0; + numleafbrushes = 0; + numsurfedges = 0; + numedges = 0; + numbrushes = 0; + numbrushsides = 0; + numareas = 0; + numareaportals = 0; + + visdatasize = 0; + dlightdataLDR.Purge(); + dlightdataHDR.Purge(); + + g_LeafAmbientLightingLDR.Purge(); + g_LeafAmbientLightingHDR.Purge(); + g_LeafAmbientIndexHDR.Purge(); + g_LeafAmbientIndexLDR.Purge(); + + dentdata.Purge(); + numworldlightsLDR = 0; + numworldlightsHDR = 0; + + numleafwaterdata = 0; + + if ( g_pPhysCollide ) + { + free( g_pPhysCollide ); + g_pPhysCollide = NULL; + } + g_PhysCollideSize = 0; + + if ( g_pPhysDisp ) + { + free( g_pPhysDisp ); + g_pPhysDisp = NULL; + } + g_PhysDispSize = 0; + + g_numvertnormals = 0; + g_numvertnormalindices = 0; + + g_nClipPortalVerts = 0; + g_nCubemapSamples = 0; + + g_TexDataStringData.Purge(); + g_TexDataStringTable.Purge(); + + g_nOverlayCount = 0; + g_nWaterOverlayCount = 0; + + g_LevelFlags = 0; + + g_OccluderData.Purge(); + g_OccluderPolyData.Purge(); + g_OccluderVertexIndices.Purge(); + + g_GameLumps.DestroyAllGameLumps(); + + for ( int i = 0; i < HEADER_LUMPS; i++ ) + { + if ( g_Lumps.pLumps[i] ) + { + free( g_Lumps.pLumps[i] ); + g_Lumps.pLumps[i] = NULL; + } + } + + ReleasePakFileLumps(); +} + +//----------------------------------------------------------------------------- +// LoadBSPFileFilesystemOnly +//----------------------------------------------------------------------------- +void LoadBSPFile_FileSystemOnly( const char *filename ) +{ + Lumps_Init(); + + // + // load the file header + // + LoadFile( filename, (void **)&g_pBSPHeader ); + + ValidateHeader( filename, g_pBSPHeader ); + + // Load PAK file lump into appropriate data structure + byte *pakbuffer = NULL; + int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer, 1 ); + if ( paksize > 0 ) + { + GetPakFile()->ParseFromBuffer( pakbuffer, paksize ); + } + else + { + GetPakFile()->Reset(); + } + + free( pakbuffer ); + + // everything has been copied out + free( g_pBSPHeader ); + g_pBSPHeader = NULL; +} + +void ExtractZipFileFromBSP( char *pBSPFileName, char *pZipFileName ) +{ + Lumps_Init(); + + // + // load the file header + // + LoadFile( pBSPFileName, (void **)&g_pBSPHeader); + + ValidateHeader( pBSPFileName, g_pBSPHeader ); + + byte *pakbuffer = NULL; + int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer ); + if ( paksize > 0 ) + { + FILE *fp; + fp = fopen( pZipFileName, "wb" ); + if( !fp ) + { + fprintf( stderr, "can't open %s\n", pZipFileName ); + return; + } + + fwrite( pakbuffer, paksize, 1, fp ); + fclose( fp ); + } + else + { + fprintf( stderr, "zip file is zero length!\n" ); + } +} + +/* +============= +LoadBSPFileTexinfo + +Only loads the texinfo lump, so qdata can scan for textures +============= +*/ +void LoadBSPFileTexinfo( const char *filename ) +{ + FILE *f; + int length, ofs; + + g_pBSPHeader = (dheader_t*)malloc( sizeof(dheader_t) ); + + f = fopen( filename, "rb" ); + fread( g_pBSPHeader, sizeof(dheader_t), 1, f); + + ValidateHeader( filename, g_pBSPHeader ); + + length = g_pBSPHeader->lumps[LUMP_TEXINFO].filelen; + ofs = g_pBSPHeader->lumps[LUMP_TEXINFO].fileofs; + + int nCount = length / sizeof(texinfo_t); + + texinfo.Purge(); + texinfo.AddMultipleToTail( nCount ); + + fseek( f, ofs, SEEK_SET ); + fread( texinfo.Base(), length, 1, f ); + fclose( f ); + + // everything has been copied out + free( g_pBSPHeader ); + g_pBSPHeader = NULL; +} + +static void AddLumpInternal( int lumpnum, void *data, int len, int version ) +{ + lump_t *lump; + + g_Lumps.size[lumpnum] = 0; // mark it written + + lump = &g_pBSPHeader->lumps[lumpnum]; + + lump->fileofs = g_pFileSystem->Tell( g_hBSPFile ); + lump->filelen = len; + lump->version = version; + lump->fourCC[0] = ( char )0; + lump->fourCC[1] = ( char )0; + lump->fourCC[2] = ( char )0; + lump->fourCC[3] = ( char )0; + + SafeWrite( g_hBSPFile, data, len ); + + // pad out to the next dword + AlignFilePosition( g_hBSPFile, 4 ); +} + +template< class T > +static void SwapInPlace( T *pData, int count ) +{ + if ( !pData ) + return; + + // use the datadesc to swap the fields in place + g_Swap.SwapFieldsToTargetEndian( (T*)pData, pData, count ); +} + +template< class T > +static void SwapInPlace( int fieldType, T *pData, int count ) +{ + if ( !pData ) + return; + + // swap the data in place + g_Swap.SwapBufferToTargetEndian( (T*)pData, (T*)pData, count ); +} + +//----------------------------------------------------------------------------- +// Add raw data chunk to file (not a lump) +//----------------------------------------------------------------------------- +template< class T > +static void WriteData( int fieldType, T *pData, int count ) +{ + if ( g_bSwapOnWrite ) + { + SwapInPlace( fieldType, pData, count ); + } + SafeWrite( g_hBSPFile, pData, count * sizeof(T) ); +} + +template< class T > +static void WriteData( T *pData, int count ) +{ + if ( g_bSwapOnWrite ) + { + SwapInPlace( pData, count ); + } + SafeWrite( g_hBSPFile, pData, count * sizeof(T) ); +} + +//----------------------------------------------------------------------------- +// Add Lump of object types with datadescs +//----------------------------------------------------------------------------- +template< class T > +static void AddLump( int lumpnum, T *pData, int count, int version ) +{ + AddLumpInternal( lumpnum, pData, count * sizeof(T), version ); +} + +template< class T > +static void AddLump( int lumpnum, CUtlVector &data, int version ) +{ + AddLumpInternal( lumpnum, data.Base(), data.Count() * sizeof(T), version ); +} + +/* +============= +WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void WriteBSPFile( const char *filename, char *pUnused ) +{ + if ( texinfo.Count() > MAX_MAP_TEXINFO ) + { + Error( "Map has too many texinfos (has %d, can have at most %d)\n", texinfo.Count(), MAX_MAP_TEXINFO ); + return; + } + + dheader_t outHeader; + g_pBSPHeader = &outHeader; + memset( g_pBSPHeader, 0, sizeof( dheader_t ) ); + + g_pBSPHeader->ident = IDBSPHEADER; + g_pBSPHeader->version = BSPVERSION; + g_pBSPHeader->mapRevision = g_MapRevision; + + g_hBSPFile = SafeOpenWrite( filename ); + WriteData( g_pBSPHeader ); // overwritten later + + AddLump( LUMP_PLANES, dplanes, numplanes ); + AddLump( LUMP_LEAFS, dleafs, numleafs, LUMP_LEAFS_VERSION ); + AddLump( LUMP_LEAF_AMBIENT_LIGHTING, g_LeafAmbientLightingLDR, LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); + AddLump( LUMP_LEAF_AMBIENT_INDEX, g_LeafAmbientIndexLDR ); + AddLump( LUMP_LEAF_AMBIENT_INDEX_HDR, g_LeafAmbientIndexHDR ); + AddLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR, g_LeafAmbientLightingHDR, LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); + + AddLump( LUMP_VERTEXES, dvertexes, numvertexes ); + AddLump( LUMP_NODES, dnodes, numnodes ); + AddLump( LUMP_TEXINFO, texinfo ); + AddLump( LUMP_TEXDATA, dtexdata, numtexdata ); + + AddLump( LUMP_DISPINFO, g_dispinfo ); + AddLump( LUMP_DISP_VERTS, g_DispVerts ); + AddLump( LUMP_DISP_TRIS, g_DispTris ); + AddLump( LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS, g_DispLightmapSamplePositions ); + AddLump( LUMP_FACE_MACRO_TEXTURE_INFO, g_FaceMacroTextureInfos ); + + AddLump( LUMP_PRIMITIVES, g_primitives, g_numprimitives ); + AddLump( LUMP_PRIMVERTS, g_primverts, g_numprimverts ); + AddLump( LUMP_PRIMINDICES, g_primindices, g_numprimindices ); + AddLump( LUMP_FACES, dfaces, numfaces, LUMP_FACES_VERSION ); + if (numfaces_hdr) + AddLump( LUMP_FACES_HDR, dfaces_hdr, numfaces_hdr, LUMP_FACES_VERSION ); + AddLump ( LUMP_FACEIDS, dfaceids, numfaceids ); + + AddLump( LUMP_ORIGINALFACES, dorigfaces, numorigfaces ); // original faces lump + AddLump( LUMP_BRUSHES, dbrushes, numbrushes ); + AddLump( LUMP_BRUSHSIDES, dbrushsides, numbrushsides ); + AddLump( LUMP_LEAFFACES, dleaffaces, numleaffaces ); + AddLump( LUMP_LEAFBRUSHES, dleafbrushes, numleafbrushes ); + AddLump( LUMP_SURFEDGES, dsurfedges, numsurfedges ); + AddLump( LUMP_EDGES, dedges, numedges ); + AddLump( LUMP_MODELS, dmodels, nummodels ); + AddLump( LUMP_AREAS, dareas, numareas ); + AddLump( LUMP_AREAPORTALS, dareaportals, numareaportals ); + + AddLump( LUMP_LIGHTING, dlightdataLDR, LUMP_LIGHTING_VERSION ); + AddLump( LUMP_LIGHTING_HDR, dlightdataHDR, LUMP_LIGHTING_VERSION ); + AddLump( LUMP_VISIBILITY, dvisdata, visdatasize ); + AddLump( LUMP_ENTITIES, dentdata ); + AddLump( LUMP_WORLDLIGHTS, dworldlightsLDR, numworldlightsLDR ); + AddLump( LUMP_WORLDLIGHTS_HDR, dworldlightsHDR, numworldlightsHDR ); + AddLump( LUMP_LEAFWATERDATA, dleafwaterdata, numleafwaterdata ); + + AddOcclusionLump(); + + dflagslump_t flags_lump; + flags_lump.m_LevelFlags = g_LevelFlags; + AddLump( LUMP_MAP_FLAGS, &flags_lump, 1 ); + + // NOTE: This is just for debugging, so it is disabled in release maps +#if 0 + // add the vis portals to the BSP for visualization + AddLump( LUMP_PORTALS, dportals, numportals ); + AddLump( LUMP_CLUSTERS, dclusters, numclusters ); + AddLump( LUMP_PORTALVERTS, dportalverts, numportalverts ); + AddLump( LUMP_CLUSTERPORTALS, dclusterportals, numclusterportals ); +#endif + + AddLump( LUMP_CLIPPORTALVERTS, (float*)g_ClipPortalVerts, g_nClipPortalVerts * 3 ); + AddLump( LUMP_CUBEMAPS, g_CubemapSamples, g_nCubemapSamples ); + AddLump( LUMP_TEXDATA_STRING_DATA, g_TexDataStringData ); + AddLump( LUMP_TEXDATA_STRING_TABLE, g_TexDataStringTable ); + AddLump( LUMP_OVERLAYS, g_Overlays, g_nOverlayCount ); + AddLump( LUMP_WATEROVERLAYS, g_WaterOverlays, g_nWaterOverlayCount ); + AddLump( LUMP_OVERLAY_FADES, g_OverlayFades, g_nOverlayCount ); + + if ( g_pPhysCollide ) + { + AddLump( LUMP_PHYSCOLLIDE, g_pPhysCollide, g_PhysCollideSize ); + } + + if ( g_pPhysDisp ) + { + AddLump ( LUMP_PHYSDISP, g_pPhysDisp, g_PhysDispSize ); + } + + AddLump( LUMP_VERTNORMALS, (float*)g_vertnormals, g_numvertnormals * 3 ); + AddLump( LUMP_VERTNORMALINDICES, g_vertnormalindices, g_numvertnormalindices ); + + AddLump( LUMP_LEAFMINDISTTOWATER, g_LeafMinDistToWater, numleafs ); + + AddGameLumps(); + + // Write pakfile lump to disk + WritePakFileLump(); + + // NOTE: Do NOT call AddLump after Lumps_Write() it writes all un-Added lumps + // write any additional lumps + Lumps_Write(); + + g_pFileSystem->Seek( g_hBSPFile, 0, FILESYSTEM_SEEK_HEAD ); + WriteData( g_pBSPHeader ); + g_pFileSystem->Close( g_hBSPFile ); +} + +// Generate the next clear lump filename for the bsp file +bool GenerateNextLumpFileName( const char *bspfilename, char *lumpfilename, int buffsize ) +{ + for (int i = 0; i < MAX_LUMPFILES; i++) + { + GenerateLumpFileName( bspfilename, lumpfilename, buffsize, i ); + + if ( !g_pFileSystem->FileExists( lumpfilename ) ) + return true; + } + + return false; +} + +void WriteLumpToFile( char *filename, int lump ) +{ + if ( !HasLump(lump) ) + return; + + char lumppre[MAX_PATH]; + if ( !GenerateNextLumpFileName( filename, lumppre, MAX_PATH ) ) + { + Warning( "Failed to find valid lump filename for bsp %s.\n", filename ); + return; + } + + // Open the file + FileHandle_t lumpfile = g_pFileSystem->Open(lumppre, "wb"); + if ( !lumpfile ) + { + Error ("Error opening %s! (Check for write enable)\n",filename); + return; + } + + int ofs = g_pBSPHeader->lumps[lump].fileofs; + int length = g_pBSPHeader->lumps[lump].filelen; + + // Write the header + lumpfileheader_t lumpHeader; + lumpHeader.lumpID = lump; + lumpHeader.lumpVersion = LumpVersion(lump); + lumpHeader.lumpLength = length; + lumpHeader.mapRevision = LittleLong( g_MapRevision ); + lumpHeader.lumpOffset = sizeof(lumpfileheader_t); // Lump starts after the header + SafeWrite (lumpfile, &lumpHeader, sizeof(lumpfileheader_t)); + + // Write the lump + SafeWrite (lumpfile, (byte *)g_pBSPHeader + ofs, length); +} + +void WriteLumpToFile( char *filename, int lump, int nLumpVersion, void *pBuffer, size_t nBufLen ) +{ + char lumppre[MAX_PATH]; + if ( !GenerateNextLumpFileName( filename, lumppre, MAX_PATH ) ) + { + Warning( "Failed to find valid lump filename for bsp %s.\n", filename ); + return; + } + + // Open the file + FileHandle_t lumpfile = g_pFileSystem->Open(lumppre, "wb"); + if ( !lumpfile ) + { + Error ("Error opening %s! (Check for write enable)\n",filename); + return; + } + + // Write the header + lumpfileheader_t lumpHeader; + lumpHeader.lumpID = lump; + lumpHeader.lumpVersion = nLumpVersion; + lumpHeader.lumpLength = nBufLen; + lumpHeader.mapRevision = LittleLong( g_MapRevision ); + lumpHeader.lumpOffset = sizeof(lumpfileheader_t); // Lump starts after the header + SafeWrite( lumpfile, &lumpHeader, sizeof(lumpfileheader_t)); + + // Write the lump + SafeWrite( lumpfile, pBuffer, nBufLen ); + + g_pFileSystem->Close( lumpfile ); +} + + +//============================================================================ +#define ENTRIES(a) (sizeof(a)/sizeof(*(a))) +#define ENTRYSIZE(a) (sizeof(*(a))) + +int ArrayUsage( const char *szItem, int items, int maxitems, int itemsize ) +{ + float percentage = maxitems ? items * 100.0 / maxitems : 0.0; + + Msg("%-17.17s %8i/%-8i %8i/%-8i (%4.1f%%) ", + szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage ); + if ( percentage > 80.0 ) + Msg( "VERY FULL!\n" ); + else if ( percentage > 95.0 ) + Msg( "SIZE DANGER!\n" ); + else if ( percentage > 99.9 ) + Msg( "SIZE OVERFLOW!!!\n" ); + else + Msg( "\n" ); + return items * itemsize; +} + +int GlobUsage( const char *szItem, int itemstorage, int maxstorage ) +{ + float percentage = maxstorage ? itemstorage * 100.0 / maxstorage : 0.0; + Msg("%-17.17s [variable] %8i/%-8i (%4.1f%%) ", + szItem, itemstorage, maxstorage, percentage ); + if ( percentage > 80.0 ) + Msg( "VERY FULL!\n" ); + else if ( percentage > 95.0 ) + Msg( "SIZE DANGER!\n" ); + else if ( percentage > 99.9 ) + Msg( "SIZE OVERFLOW!!!\n" ); + else + Msg( "\n" ); + return itemstorage; +} + +/* +============= +PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void PrintBSPFileSizes (void) +{ + int totalmemory = 0; + +// if (!num_entities) +// ParseEntities (); + + Msg("\n"); + Msg( "%-17s %16s %16s %9s \n", "Object names", "Objects/Maxobjs", "Memory / Maxmem", "Fullness" ); + Msg( "%-17s %16s %16s %9s \n", "------------", "---------------", "---------------", "--------" ); + + totalmemory += ArrayUsage( "models", nummodels, ENTRIES(dmodels), ENTRYSIZE(dmodels) ); + totalmemory += ArrayUsage( "brushes", numbrushes, ENTRIES(dbrushes), ENTRYSIZE(dbrushes) ); + totalmemory += ArrayUsage( "brushsides", numbrushsides, ENTRIES(dbrushsides), ENTRYSIZE(dbrushsides) ); + totalmemory += ArrayUsage( "planes", numplanes, ENTRIES(dplanes), ENTRYSIZE(dplanes) ); + totalmemory += ArrayUsage( "vertexes", numvertexes, ENTRIES(dvertexes), ENTRYSIZE(dvertexes) ); + totalmemory += ArrayUsage( "nodes", numnodes, ENTRIES(dnodes), ENTRYSIZE(dnodes) ); + totalmemory += ArrayUsage( "texinfos", texinfo.Count(),MAX_MAP_TEXINFO, sizeof(texinfo_t) ); + totalmemory += ArrayUsage( "texdata", numtexdata, ENTRIES(dtexdata), ENTRYSIZE(dtexdata) ); + + totalmemory += ArrayUsage( "dispinfos", g_dispinfo.Count(), 0, sizeof( ddispinfo_t ) ); + totalmemory += ArrayUsage( "disp_verts", g_DispVerts.Count(), 0, sizeof( g_DispVerts[0] ) ); + totalmemory += ArrayUsage( "disp_tris", g_DispTris.Count(), 0, sizeof( g_DispTris[0] ) ); + totalmemory += ArrayUsage( "disp_lmsamples",g_DispLightmapSamplePositions.Count(),0,sizeof( g_DispLightmapSamplePositions[0] ) ); + + totalmemory += ArrayUsage( "faces", numfaces, ENTRIES(dfaces), ENTRYSIZE(dfaces) ); + totalmemory += ArrayUsage( "hdr faces", numfaces_hdr, ENTRIES(dfaces_hdr), ENTRYSIZE(dfaces_hdr) ); + totalmemory += ArrayUsage( "origfaces", numorigfaces, ENTRIES(dorigfaces), ENTRYSIZE(dorigfaces) ); // original faces + totalmemory += ArrayUsage( "leaves", numleafs, ENTRIES(dleafs), ENTRYSIZE(dleafs) ); + totalmemory += ArrayUsage( "leaffaces", numleaffaces, ENTRIES(dleaffaces), ENTRYSIZE(dleaffaces) ); + totalmemory += ArrayUsage( "leafbrushes", numleafbrushes, ENTRIES(dleafbrushes), ENTRYSIZE(dleafbrushes) ); + totalmemory += ArrayUsage( "areas", numareas, ENTRIES(dareas), ENTRYSIZE(dareas) ); + totalmemory += ArrayUsage( "surfedges", numsurfedges, ENTRIES(dsurfedges), ENTRYSIZE(dsurfedges) ); + totalmemory += ArrayUsage( "edges", numedges, ENTRIES(dedges), ENTRYSIZE(dedges) ); + totalmemory += ArrayUsage( "LDR worldlights", numworldlightsLDR, ENTRIES(dworldlightsLDR), ENTRYSIZE(dworldlightsLDR) ); + totalmemory += ArrayUsage( "HDR worldlights", numworldlightsHDR, ENTRIES(dworldlightsHDR), ENTRYSIZE(dworldlightsHDR) ); + + totalmemory += ArrayUsage( "leafwaterdata", numleafwaterdata,ENTRIES(dleafwaterdata), ENTRYSIZE(dleafwaterdata) ); + totalmemory += ArrayUsage( "waterstrips", g_numprimitives,ENTRIES(g_primitives), ENTRYSIZE(g_primitives) ); + totalmemory += ArrayUsage( "waterverts", g_numprimverts, ENTRIES(g_primverts), ENTRYSIZE(g_primverts) ); + totalmemory += ArrayUsage( "waterindices", g_numprimindices,ENTRIES(g_primindices),ENTRYSIZE(g_primindices) ); + totalmemory += ArrayUsage( "cubemapsamples", g_nCubemapSamples,ENTRIES(g_CubemapSamples),ENTRYSIZE(g_CubemapSamples) ); + totalmemory += ArrayUsage( "overlays", g_nOverlayCount, ENTRIES(g_Overlays), ENTRYSIZE(g_Overlays) ); + + totalmemory += GlobUsage( "LDR lightdata", dlightdataLDR.Count(), 0 ); + totalmemory += GlobUsage( "HDR lightdata", dlightdataHDR.Count(), 0 ); + totalmemory += GlobUsage( "visdata", visdatasize, sizeof(dvisdata) ); + totalmemory += GlobUsage( "entdata", dentdata.Count(), 384*1024 ); // goal is <384K + + totalmemory += ArrayUsage( "LDR ambient table", g_LeafAmbientIndexLDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientIndexLDR[0] ) ); + totalmemory += ArrayUsage( "HDR ambient table", g_LeafAmbientIndexHDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientIndexHDR[0] ) ); + totalmemory += ArrayUsage( "LDR leaf ambient lighting", g_LeafAmbientLightingLDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientLightingLDR[0] ) ); + totalmemory += ArrayUsage( "HDR leaf ambient lighting", g_LeafAmbientLightingHDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientLightingHDR[0] ) ); + + totalmemory += ArrayUsage( "occluders", g_OccluderData.Count(), 0, sizeof( g_OccluderData[0] ) ); + totalmemory += ArrayUsage( "occluder polygons", g_OccluderPolyData.Count(), 0, sizeof( g_OccluderPolyData[0] ) ); + totalmemory += ArrayUsage( "occluder vert ind",g_OccluderVertexIndices.Count(),0, sizeof( g_OccluderVertexIndices[0] ) ); + + GameLumpHandle_t h = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROPS ); + if (h != g_GameLumps.InvalidGameLump()) + totalmemory += GlobUsage( "detail props", 1, g_GameLumps.GameLumpSize(h) ); + h = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROP_LIGHTING ); + if (h != g_GameLumps.InvalidGameLump()) + totalmemory += GlobUsage( "dtl prp lght", 1, g_GameLumps.GameLumpSize(h) ); + h = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROP_LIGHTING_HDR ); + if (h != g_GameLumps.InvalidGameLump()) + totalmemory += GlobUsage( "HDR dtl prp lght", 1, g_GameLumps.GameLumpSize(h) ); + h = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); + if (h != g_GameLumps.InvalidGameLump()) + totalmemory += GlobUsage( "static props", 1, g_GameLumps.GameLumpSize(h) ); + + totalmemory += GlobUsage( "pakfile", GetPakFile()->EstimateSize(), 0 ); + // HACKHACK: Set physics limit at 4MB, in reality this is totally dynamic + totalmemory += GlobUsage( "physics", g_PhysCollideSize, 4*1024*1024 ); + totalmemory += GlobUsage( "physics terrain", g_PhysDispSize, 1*1024*1024 ); + + Msg( "\nLevel flags = %x\n", g_LevelFlags ); + + Msg( "\n" ); + + int triangleCount = 0; + + for ( int i = 0; i < numfaces; i++ ) + { + // face tris = numedges - 2 + triangleCount += dfaces[i].numedges - 2; + } + Msg("Total triangle count: %d\n", triangleCount ); + + // UNDONE: + // areaportals, portals, texdata, clusters, worldlights, portalverts +} + +/* +============= +PrintBSPPackDirectory + +Dumps a list of files stored in the bsp pack. +============= +*/ +void PrintBSPPackDirectory( void ) +{ + GetPakFile()->PrintDirectory(); +} + + +//============================================ + +int num_entities; +entity_t entities[MAX_MAP_ENTITIES]; + +void StripTrailing (char *e) +{ + char *s; + + s = e + strlen(e)-1; + while (s >= e && *s <= 32) + { + *s = 0; + s--; + } +} + +/* +================= +ParseEpair +================= +*/ +epair_t *ParseEpair (void) +{ + epair_t *e; + + e = (epair_t*)malloc (sizeof(epair_t)); + memset (e, 0, sizeof(epair_t)); + + if (strlen(token) >= MAX_KEY-1) + Error ("ParseEpar: token too long"); + e->key = copystring(token); + + GetToken (false); + if (strlen(token) >= MAX_VALUE-1) + Error ("ParseEpar: token too long"); + e->value = copystring(token); + + // strip trailing spaces + StripTrailing (e->key); + StripTrailing (e->value); + + return e; +} + + +/* +================ +ParseEntity +================ +*/ +qboolean ParseEntity (void) +{ + epair_t *e; + entity_t *mapent; + + if (!GetToken (true)) + return false; + + if (Q_stricmp (token, "{") ) + Error ("ParseEntity: { not found"); + + if (num_entities == MAX_MAP_ENTITIES) + Error ("num_entities == MAX_MAP_ENTITIES"); + + mapent = &entities[num_entities]; + num_entities++; + + do + { + if (!GetToken (true)) + Error ("ParseEntity: EOF without closing brace"); + if (!Q_stricmp (token, "}") ) + break; + e = ParseEpair (); + e->next = mapent->epairs; + mapent->epairs = e; + } while (1); + + return true; +} + +/* +================ +ParseEntities + +Parses the dentdata string into entities +================ +*/ +void ParseEntities (void) +{ + num_entities = 0; + ParseFromMemory (dentdata.Base(), dentdata.Count()); + + while (ParseEntity ()) + { + } +} + + +/* +================ +UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void UnparseEntities (void) +{ + epair_t *ep; + char line[2048]; + int i; + char key[1024], value[1024]; + + CUtlBuffer buffer( 0, 0, CUtlBuffer::TEXT_BUFFER ); + buffer.EnsureCapacity( 256 * 1024 ); + + for (i=0 ; inext) + { + strcpy (key, ep->key); + StripTrailing (key); + strcpy (value, ep->value); + StripTrailing (value); + + sprintf(line, "\"%s\" \"%s\"\n", key, value); + buffer.PutString( line ); + } + buffer.PutString("}\n"); + } + int entdatasize = buffer.TellPut()+1; + + dentdata.SetSize( entdatasize ); + memcpy( dentdata.Base(), buffer.Base(), entdatasize-1 ); + dentdata[entdatasize-1] = 0; +} + +void PrintEntity (entity_t *ent) +{ + epair_t *ep; + + Msg ("------- entity %p -------\n", ent); + for (ep=ent->epairs ; ep ; ep=ep->next) + { + Msg ("%s = %s\n", ep->key, ep->value); + } + +} + +void SetKeyValue(entity_t *ent, const char *key, const char *value) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!Q_stricmp (ep->key, key) ) + { + free (ep->value); + ep->value = copystring(value); + return; + } + ep = (epair_t*)malloc (sizeof(*ep)); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring(key); + ep->value = copystring(value); +} + +char *ValueForKey (entity_t *ent, char *key) +{ + for (epair_t *ep=ent->epairs ; ep ; ep=ep->next) + if (!Q_stricmp (ep->key, key) ) + return ep->value; + return ""; +} + +vec_t FloatForKey (entity_t *ent, char *key) +{ + char *k = ValueForKey (ent, key); + return atof(k); +} + +vec_t FloatForKeyWithDefault (entity_t *ent, char *key, float default_value) +{ + for (epair_t *ep=ent->epairs ; ep ; ep=ep->next) + if (!Q_stricmp (ep->key, key) ) + return atof( ep->value ); + return default_value; +} + + + +int IntForKey (entity_t *ent, char *key) +{ + char *k = ValueForKey (ent, key); + return atol(k); +} + +int IntForKeyWithDefault(entity_t *ent, char *key, int nDefault ) +{ + char *k = ValueForKey (ent, key); + if ( !k[0] ) + return nDefault; + return atol(k); +} + +void GetVectorForKey (entity_t *ent, char *key, Vector& vec) +{ + + char *k = ValueForKey (ent, key); +// scanf into doubles, then assign, so it is vec_t size independent + double v1, v2, v3; + v1 = v2 = v3 = 0; + sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; +} + +void GetVector2DForKey (entity_t *ent, char *key, Vector2D& vec) +{ + double v1, v2; + + char *k = ValueForKey (ent, key); +// scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = 0; + sscanf (k, "%lf %lf", &v1, &v2); + vec[0] = v1; + vec[1] = v2; +} + +void GetAnglesForKey (entity_t *ent, char *key, QAngle& angle) +{ + char *k; + double v1, v2, v3; + + k = ValueForKey (ent, key); +// scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); + angle[0] = v1; + angle[1] = v2; + angle[2] = v3; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void BuildFaceCalcWindingData( dface_t *pFace, int *points ) +{ + for( int i = 0; i < pFace->numedges; i++ ) + { + int eIndex = dsurfedges[pFace->firstedge+i]; + if( eIndex < 0 ) + { + points[i] = dedges[-eIndex].v[1]; + } + else + { + points[i] = dedges[eIndex].v[0]; + } + } +} + + +void TriStripToTriList( + unsigned short const *pTriStripIndices, + int nTriStripIndices, + unsigned short **pTriListIndices, + int *pnTriListIndices ) +{ + int nMaxTriListIndices = (nTriStripIndices - 2) * 3; + *pTriListIndices = new unsigned short[ nMaxTriListIndices ]; + *pnTriListIndices = 0; + + for( int i=0; i < nTriStripIndices - 2; i++ ) + { + if( pTriStripIndices[i] == pTriStripIndices[i+1] || + pTriStripIndices[i] == pTriStripIndices[i+2] || + pTriStripIndices[i+1] == pTriStripIndices[i+2] ) + { + } + else + { + // Flip odd numbered tris.. + if( i & 1 ) + { + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+2]; + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+1]; + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i]; + } + else + { + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i]; + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+1]; + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+2]; + } + } + } +} + + +void CalcTextureCoordsAtPoints( + float const texelsPerWorldUnits[2][4], + int const subtractOffset[2], + Vector const *pPoints, + int const nPoints, + Vector2D *pCoords ) +{ + for( int i=0; i < nPoints; i++ ) + { + for( int iCoord=0; iCoord < 2; iCoord++ ) + { + float *pDestCoord = &pCoords[i][iCoord]; + + *pDestCoord = 0; + for( int iDot=0; iDot < 3; iDot++ ) + *pDestCoord += pPoints[i][iDot] * texelsPerWorldUnits[iCoord][iDot]; + + *pDestCoord += texelsPerWorldUnits[iCoord][3]; + *pDestCoord -= subtractOffset[iCoord]; + } + } +} + + +/* +================ +CalcFaceExtents + +Fills in s->texmins[] and s->texsize[] +================ +*/ +void CalcFaceExtents(dface_t *s, int lightmapTextureMinsInLuxels[2], int lightmapTextureSizeInLuxels[2]) +{ + vec_t mins[2], maxs[2], val=0; + int i,j, e=0; + dvertex_t *v=NULL; + texinfo_t *tex=NULL; + + mins[0] = mins[1] = 1e24; + maxs[0] = maxs[1] = -1e24; + + tex = &texinfo[s->texinfo]; + + for (i=0 ; inumedges ; i++) + { + e = dsurfedges[s->firstedge+i]; + if (e >= 0) + v = dvertexes + dedges[e].v[0]; + else + v = dvertexes + dedges[-e].v[1]; + + for (j=0 ; j<2 ; j++) + { + val = v->point[0] * tex->lightmapVecsLuxelsPerWorldUnits[j][0] + + v->point[1] * tex->lightmapVecsLuxelsPerWorldUnits[j][1] + + v->point[2] * tex->lightmapVecsLuxelsPerWorldUnits[j][2] + + tex->lightmapVecsLuxelsPerWorldUnits[j][3]; + if (val < mins[j]) + mins[j] = val; + if (val > maxs[j]) + maxs[j] = val; + } + } + + int nMaxLightmapDim = (s->dispinfo == -1) ? MAX_LIGHTMAP_DIM_WITHOUT_BORDER : MAX_DISP_LIGHTMAP_DIM_WITHOUT_BORDER; + for (i=0 ; i<2 ; i++) + { + mins[i] = ( float )floor( mins[i] ); + maxs[i] = ( float )ceil( maxs[i] ); + + lightmapTextureMinsInLuxels[i] = ( int )mins[i]; + lightmapTextureSizeInLuxels[i] = ( int )( maxs[i] - mins[i] ); + if( lightmapTextureSizeInLuxels[i] > nMaxLightmapDim + 1 ) + { + Vector point = vec3_origin; + for (int j=0 ; jnumedges ; j++) + { + e = dsurfedges[s->firstedge+j]; + v = (e<0)?dvertexes + dedges[-e].v[1] : dvertexes + dedges[e].v[0]; + point += v->point; + Warning( "Bad surface extents point: %f %f %f\n", v->point.x, v->point.y, v->point.z ); + } + point *= 1.0f/s->numedges; + Error( "Bad surface extents - surface is too big to have a lightmap\n\tmaterial %s around point (%.1f %.1f %.1f)\n\t(dimension: %d, %d>%d)\n", + TexDataStringTable_GetString( dtexdata[texinfo[s->texinfo].texdata].nameStringTableID ), + point.x, point.y, point.z, + ( int )i, + ( int )lightmapTextureSizeInLuxels[i], + ( int )( nMaxLightmapDim + 1 ) + ); + } + } +} + + +void UpdateAllFaceLightmapExtents() +{ + for( int i=0; i < numfaces; i++ ) + { + dface_t *pFace = &dfaces[i]; + + if ( texinfo[pFace->texinfo].flags & (SURF_SKY|SURF_NOLIGHT) ) + continue; // non-lit texture + + CalcFaceExtents( pFace, pFace->m_LightmapTextureMinsInLuxels, pFace->m_LightmapTextureSizeInLuxels ); + } +} + + +//----------------------------------------------------------------------------- +// +// Helper class to iterate over leaves, used by tools +// +//----------------------------------------------------------------------------- + +#define TEST_EPSILON (0.03125) + + +class CToolBSPTree : public ISpatialQuery +{ +public: + // Returns the number of leaves + int LeafCount() const; + + // Enumerates the leaves along a ray, box, etc. + bool EnumerateLeavesAtPoint( Vector const& pt, ISpatialLeafEnumerator* pEnum, int context ); + bool EnumerateLeavesInBox( Vector const& mins, Vector const& maxs, ISpatialLeafEnumerator* pEnum, int context ); + bool EnumerateLeavesInSphere( Vector const& center, float radius, ISpatialLeafEnumerator* pEnum, int context ); + bool EnumerateLeavesAlongRay( Ray_t const& ray, ISpatialLeafEnumerator* pEnum, int context ); +}; + + +//----------------------------------------------------------------------------- +// Returns the number of leaves +//----------------------------------------------------------------------------- + +int CToolBSPTree::LeafCount() const +{ + return numleafs; +} + + +//----------------------------------------------------------------------------- +// Enumerates the leaves at a point +//----------------------------------------------------------------------------- + +bool CToolBSPTree::EnumerateLeavesAtPoint( Vector const& pt, + ISpatialLeafEnumerator* pEnum, int context ) +{ + int node = 0; + while( node >= 0 ) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + if (DotProduct( pPlane->normal, pt ) <= pPlane->dist) + { + node = pNode->children[1]; + } + else + { + node = pNode->children[0]; + } + } + + return pEnum->EnumerateLeaf( - node - 1, context ); +} + + +//----------------------------------------------------------------------------- +// Enumerates the leaves in a box +//----------------------------------------------------------------------------- + +static bool EnumerateLeavesInBox_R( int node, Vector const& mins, + Vector const& maxs, ISpatialLeafEnumerator* pEnum, int context ) +{ + Vector cornermin, cornermax; + + while( node >= 0 ) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + // Arbitrary split plane here + for (int i = 0; i < 3; ++i) + { + if (pPlane->normal[i] >= 0) + { + cornermin[i] = mins[i]; + cornermax[i] = maxs[i]; + } + else + { + cornermin[i] = maxs[i]; + cornermax[i] = mins[i]; + } + } + + if ( (DotProduct( pPlane->normal, cornermax ) - pPlane->dist) <= -TEST_EPSILON ) + { + node = pNode->children[1]; + } + else if ( (DotProduct( pPlane->normal, cornermin ) - pPlane->dist) >= TEST_EPSILON ) + { + node = pNode->children[0]; + } + else + { + if (!EnumerateLeavesInBox_R( pNode->children[0], mins, maxs, pEnum, context )) + { + return false; + } + + return EnumerateLeavesInBox_R( pNode->children[1], mins, maxs, pEnum, context ); + } + } + + return pEnum->EnumerateLeaf( - node - 1, context ); +} + +bool CToolBSPTree::EnumerateLeavesInBox( Vector const& mins, Vector const& maxs, + ISpatialLeafEnumerator* pEnum, int context ) +{ + return EnumerateLeavesInBox_R( 0, mins, maxs, pEnum, context ); +} + +//----------------------------------------------------------------------------- +// Enumerate leaves within a sphere +//----------------------------------------------------------------------------- + +static bool EnumerateLeavesInSphere_R( int node, Vector const& origin, + float radius, ISpatialLeafEnumerator* pEnum, int context ) +{ + while( node >= 0 ) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + if (DotProduct( pPlane->normal, origin ) + radius - pPlane->dist <= -TEST_EPSILON ) + { + node = pNode->children[1]; + } + else if (DotProduct( pPlane->normal, origin ) - radius - pPlane->dist >= TEST_EPSILON ) + { + node = pNode->children[0]; + } + else + { + if (!EnumerateLeavesInSphere_R( pNode->children[0], + origin, radius, pEnum, context )) + { + return false; + } + + return EnumerateLeavesInSphere_R( pNode->children[1], + origin, radius, pEnum, context ); + } + } + + return pEnum->EnumerateLeaf( - node - 1, context ); +} + +bool CToolBSPTree::EnumerateLeavesInSphere( Vector const& center, float radius, ISpatialLeafEnumerator* pEnum, int context ) +{ + return EnumerateLeavesInSphere_R( 0, center, radius, pEnum, context ); +} + + +//----------------------------------------------------------------------------- +// Enumerate leaves along a ray +//----------------------------------------------------------------------------- + +static bool EnumerateLeavesAlongRay_R( int node, Ray_t const& ray, + Vector const& start, Vector const& end, ISpatialLeafEnumerator* pEnum, int context ) +{ + float front,back; + + while (node >= 0) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + if ( pPlane->type <= PLANE_Z ) + { + front = start[pPlane->type] - pPlane->dist; + back = end[pPlane->type] - pPlane->dist; + } + else + { + front = DotProduct(start, pPlane->normal) - pPlane->dist; + back = DotProduct(end, pPlane->normal) - pPlane->dist; + } + + if (front <= -TEST_EPSILON && back <= -TEST_EPSILON) + { + node = pNode->children[1]; + } + else if (front >= TEST_EPSILON && back >= TEST_EPSILON) + { + node = pNode->children[0]; + } + else + { + // test the front side first + bool side = front < 0; + + // Compute intersection point based on the original ray + float splitfrac; + float denom = DotProduct( ray.m_Delta, pPlane->normal ); + if ( denom == 0.0f ) + { + splitfrac = 1.0f; + } + else + { + splitfrac = ( pPlane->dist - DotProduct( ray.m_Start, pPlane->normal ) ) / denom; + if (splitfrac < 0) + splitfrac = 0; + else if (splitfrac > 1) + splitfrac = 1; + } + + // Compute the split point + Vector split; + VectorMA( ray.m_Start, splitfrac, ray.m_Delta, split ); + + bool r = EnumerateLeavesAlongRay_R (pNode->children[side], ray, start, split, pEnum, context ); + if (!r) + return r; + return EnumerateLeavesAlongRay_R (pNode->children[!side], ray, split, end, pEnum, context); + } + } + + return pEnum->EnumerateLeaf( - node - 1, context ); +} + +bool CToolBSPTree::EnumerateLeavesAlongRay( Ray_t const& ray, ISpatialLeafEnumerator* pEnum, int context ) +{ + if (!ray.m_IsSwept) + { + Vector mins, maxs; + VectorAdd( ray.m_Start, ray.m_Extents, maxs ); + VectorSubtract( ray.m_Start, ray.m_Extents, mins ); + + return EnumerateLeavesInBox_R( 0, mins, maxs, pEnum, context ); + } + + // FIXME: Extruded ray not implemented yet + Assert( ray.m_IsRay ); + + Vector end; + VectorAdd( ray.m_Start, ray.m_Delta, end ); + return EnumerateLeavesAlongRay_R( 0, ray, ray.m_Start, end, pEnum, context ); +} + + +//----------------------------------------------------------------------------- +// Singleton accessor +//----------------------------------------------------------------------------- + +ISpatialQuery* ToolBSPTree() +{ + static CToolBSPTree s_ToolBSPTree; + return &s_ToolBSPTree; +} + + + +//----------------------------------------------------------------------------- +// Enumerates nodes in front to back order... +//----------------------------------------------------------------------------- + +// FIXME: Do we want this in the IBSPTree interface? + +static bool EnumerateNodesAlongRay_R( int node, Ray_t const& ray, float start, float end, + IBSPNodeEnumerator* pEnum, int context ) +{ + float front, back; + float startDotN, deltaDotN; + + while (node >= 0) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + if ( pPlane->type <= PLANE_Z ) + { + startDotN = ray.m_Start[pPlane->type]; + deltaDotN = ray.m_Delta[pPlane->type]; + } + else + { + startDotN = DotProduct( ray.m_Start, pPlane->normal ); + deltaDotN = DotProduct( ray.m_Delta, pPlane->normal ); + } + + front = startDotN + start * deltaDotN - pPlane->dist; + back = startDotN + end * deltaDotN - pPlane->dist; + + if (front <= -TEST_EPSILON && back <= -TEST_EPSILON) + { + node = pNode->children[1]; + } + else if (front >= TEST_EPSILON && back >= TEST_EPSILON) + { + node = pNode->children[0]; + } + else + { + // test the front side first + bool side = front < 0; + + // Compute intersection point based on the original ray + float splitfrac; + if ( deltaDotN == 0.0f ) + { + splitfrac = 1.0f; + } + else + { + splitfrac = ( pPlane->dist - startDotN ) / deltaDotN; + if (splitfrac < 0.0f) + splitfrac = 0.0f; + else if (splitfrac > 1.0f) + splitfrac = 1.0f; + } + + bool r = EnumerateNodesAlongRay_R (pNode->children[side], ray, start, splitfrac, pEnum, context ); + if (!r) + return r; + + // Visit the node... + if (!pEnum->EnumerateNode( node, ray, splitfrac, context )) + return false; + + return EnumerateNodesAlongRay_R (pNode->children[!side], ray, splitfrac, end, pEnum, context); + } + } + + // Visit the leaf... + return pEnum->EnumerateLeaf( - node - 1, ray, start, end, context ); +} + + +bool EnumerateNodesAlongRay( Ray_t const& ray, IBSPNodeEnumerator* pEnum, int context ) +{ + Vector end; + VectorAdd( ray.m_Start, ray.m_Delta, end ); + return EnumerateNodesAlongRay_R( 0, ray, 0.0f, 1.0f, pEnum, context ); +} + + +//----------------------------------------------------------------------------- +// Helps us find all leaves associated with a particular cluster +//----------------------------------------------------------------------------- +CUtlVector g_ClusterLeaves; + +void BuildClusterTable( void ) +{ + int i, j; + int leafCount; + int leafList[MAX_MAP_LEAFS]; + + g_ClusterLeaves.SetCount( dvis->numclusters ); + for ( i = 0; i < dvis->numclusters; i++ ) + { + leafCount = 0; + for ( j = 0; j < numleafs; j++ ) + { + if ( dleafs[j].cluster == i ) + { + leafList[ leafCount ] = j; + leafCount++; + } + } + + g_ClusterLeaves[i].leafCount = leafCount; + if ( leafCount ) + { + g_ClusterLeaves[i].leafs.SetCount( leafCount ); + memcpy( g_ClusterLeaves[i].leafs.Base(), leafList, sizeof(int) * leafCount ); + } + } +} + +// There's a version of this in host.cpp!!! Make sure that they match. +void GetPlatformMapPath( const char *pMapPath, char *pPlatformMapPath, int dxlevel, int maxLength ) +{ + Q_StripExtension( pMapPath, pPlatformMapPath, maxLength ); + +// if( dxlevel <= 60 ) +// { +// Q_strncat( pPlatformMapPath, "_dx60", maxLength, COPY_ALL_CHARACTERS ); +// } + + Q_strncat( pPlatformMapPath, ".bsp", maxLength, COPY_ALL_CHARACTERS ); +} + +// There's a version of this in checksum_engine.cpp!!! Make sure that they match. +static bool CRC_MapFile(CRC32_t *crcvalue, const char *pszFileName) +{ + byte chunk[1024]; + lump_t *curLump; + + FileHandle_t fp = g_pFileSystem->Open( pszFileName, "rb" ); + if ( !fp ) + return false; + + // CRC across all lumps except for the Entities lump + for ( int l = 0; l < HEADER_LUMPS; ++l ) + { + if (l == LUMP_ENTITIES) + continue; + + curLump = &g_pBSPHeader->lumps[l]; + unsigned int nSize = curLump->filelen; + + g_pFileSystem->Seek( fp, curLump->fileofs, FILESYSTEM_SEEK_HEAD ); + + // Now read in 1K chunks + while ( nSize > 0 ) + { + int nBytesRead = 0; + + if ( nSize > 1024 ) + nBytesRead = g_pFileSystem->Read( chunk, 1024, fp ); + else + nBytesRead = g_pFileSystem->Read( chunk, nSize, fp ); + + // If any data was received, CRC it. + if ( nBytesRead > 0 ) + { + nSize -= nBytesRead; + CRC32_ProcessBuffer( crcvalue, chunk, nBytesRead ); + } + else + { + g_pFileSystem->Close( fp ); + return false; + } + } + } + + g_pFileSystem->Close( fp ); + return true; +} + + +void SetHDRMode( bool bHDR ) +{ + g_bHDR = bHDR; + if ( bHDR ) + { + pdlightdata = &dlightdataHDR; + g_pLeafAmbientLighting = &g_LeafAmbientLightingHDR; + g_pLeafAmbientIndex = &g_LeafAmbientIndexHDR; + pNumworldlights = &numworldlightsHDR; + dworldlights = dworldlightsHDR; +#ifdef VRAD + extern void VRadDetailProps_SetHDRMode( bool bHDR ); + VRadDetailProps_SetHDRMode( bHDR ); +#endif + } + else + { + pdlightdata = &dlightdataLDR; + g_pLeafAmbientLighting = &g_LeafAmbientLightingLDR; + g_pLeafAmbientIndex = &g_LeafAmbientIndexLDR; + pNumworldlights = &numworldlightsLDR; + dworldlights = dworldlightsLDR; +#ifdef VRAD + extern void VRadDetailProps_SetHDRMode( bool bHDR ); + VRadDetailProps_SetHDRMode( bHDR ); +#endif + } +} + +bool SwapVHV( void *pDestBase, void *pSrcBase ) +{ + byte *pDest = (byte*)pDestBase; + byte *pSrc = (byte*)pSrcBase; + + HardwareVerts::FileHeader_t *pHdr = (HardwareVerts::FileHeader_t*)( g_bSwapOnLoad ? pDest : pSrc ); + g_Swap.SwapFieldsToTargetEndian( (HardwareVerts::FileHeader_t*)pDest, (HardwareVerts::FileHeader_t*)pSrc ); + pSrc += sizeof(HardwareVerts::FileHeader_t); + pDest += sizeof(HardwareVerts::FileHeader_t); + + // This swap is pretty format specific + Assert( pHdr->m_nVersion == VHV_VERSION ); + if ( pHdr->m_nVersion != VHV_VERSION ) + return false; + + HardwareVerts::MeshHeader_t *pSrcMesh = (HardwareVerts::MeshHeader_t*)pSrc; + HardwareVerts::MeshHeader_t *pDestMesh = (HardwareVerts::MeshHeader_t*)pDest; + HardwareVerts::MeshHeader_t *pMesh = (HardwareVerts::MeshHeader_t*)( g_bSwapOnLoad ? pDest : pSrc ); + for ( int i = 0; i < pHdr->m_nMeshes; ++i, ++pMesh, ++pSrcMesh, ++pDestMesh ) + { + g_Swap.SwapFieldsToTargetEndian( pDestMesh, pSrcMesh ); + + pSrc = (byte*)pSrcBase + pMesh->m_nOffset; + pDest = (byte*)pDestBase + pMesh->m_nOffset; + + // Swap as a buffer of integers + // (source is bgra for an Intel swap to argb. PowerPC won't swap, so we need argb source. + g_Swap.SwapBufferToTargetEndian( (int*)pDest, (int*)pSrc, pMesh->m_nVertexes ); + } + return true; +} + +const char *ResolveStaticPropToModel( const char *pPropName ) +{ + // resolve back to static prop + int iProp = -1; + + // filename should be sp_???.vhv or sp_hdr_???.vhv + if ( V_strnicmp( pPropName, "sp_", 3 ) ) + { + return NULL; + } + const char *pPropNumber = V_strrchr( pPropName, '_' ); + if ( pPropNumber ) + { + sscanf( pPropNumber+1, "%d.vhv", &iProp ); + } + else + { + return NULL; + } + + // look up the prop to get to the actual model + if ( iProp < 0 || iProp >= g_StaticPropInstances.Count() ) + { + // prop out of range + return NULL; + } + int iModel = g_StaticPropInstances[iProp]; + if ( iModel < 0 || iModel >= g_StaticPropNames.Count() ) + { + // model out of range + return NULL; + } + + return g_StaticPropNames[iModel].String(); +} + +//----------------------------------------------------------------------------- +// Iterate files in pak file, distribute to converters +// pak file will be ready for serialization upon completion +//----------------------------------------------------------------------------- +void ConvertPakFileContents( const char *pInFilename ) +{ + IZip *newPakFile = IZip::CreateZip( NULL ); + + CUtlBuffer sourceBuf; + CUtlBuffer targetBuf; + bool bConverted; + CUtlVector< CUtlString > hdrFiles; + + int id = -1; + int fileSize; + while ( 1 ) + { + char relativeName[MAX_PATH]; + id = GetNextFilename( GetPakFile(), id, relativeName, sizeof( relativeName ), fileSize ); + if ( id == -1) + break; + + bConverted = false; + sourceBuf.Purge(); + targetBuf.Purge(); + + const char* pExtension = V_GetFileExtension( relativeName ); + const char* pExt = 0; + + bool bOK = ReadFileFromPak( GetPakFile(), relativeName, false, sourceBuf ); + if ( !bOK ) + { + Warning( "Failed to load '%s' from lump pak for conversion or copy in '%s'.\n", relativeName, pInFilename ); + continue; + } + + if ( pExtension && !V_stricmp( pExtension, "vtf" ) ) + { + bOK = g_pVTFConvertFunc( relativeName, sourceBuf, targetBuf, g_pCompressFunc ); + if ( !bOK ) + { + Warning( "Failed to convert '%s' in '%s'.\n", relativeName, pInFilename ); + continue; + } + + bConverted = true; + pExt = ".vtf"; + } + else if ( pExtension && !V_stricmp( pExtension, "vhv" ) ) + { + CUtlBuffer tempBuffer; + if ( g_pVHVFixupFunc ) + { + // caller supplied a fixup + const char *pModelName = ResolveStaticPropToModel( relativeName ); + if ( !pModelName ) + { + Warning( "Static Prop '%s' failed to resolve actual model in '%s'.\n", relativeName, pInFilename ); + continue; + } + + // output temp buffer may shrink, must use TellPut() to determine size + bOK = g_pVHVFixupFunc( relativeName, pModelName, sourceBuf, tempBuffer ); + if ( !bOK ) + { + Warning( "Failed to convert '%s' in '%s'.\n", relativeName, pInFilename ); + continue; + } + } + else + { + // use the source buffer as-is + tempBuffer.EnsureCapacity( sourceBuf.TellMaxPut() ); + tempBuffer.Put( sourceBuf.Base(), sourceBuf.TellMaxPut() ); + } + + // swap the VHV + targetBuf.EnsureCapacity( tempBuffer.TellPut() ); + bOK = SwapVHV( targetBuf.Base(), tempBuffer.Base() ); + if ( !bOK ) + { + Warning( "Failed to swap '%s' in '%s'.\n", relativeName, pInFilename ); + continue; + } + targetBuf.SeekPut( CUtlBuffer::SEEK_HEAD, tempBuffer.TellPut() ); + + if ( g_pCompressFunc ) + { + CUtlBuffer compressedBuffer; + targetBuf.SeekGet( CUtlBuffer::SEEK_HEAD, sizeof( HardwareVerts::FileHeader_t ) ); + bool bCompressed = g_pCompressFunc( targetBuf, compressedBuffer ); + if ( bCompressed ) + { + // copy all the header data off + CUtlBuffer headerBuffer; + headerBuffer.EnsureCapacity( sizeof( HardwareVerts::FileHeader_t ) ); + headerBuffer.Put( targetBuf.Base(), sizeof( HardwareVerts::FileHeader_t ) ); + + // reform the target with the header and then the compressed data + targetBuf.Clear(); + targetBuf.Put( headerBuffer.Base(), sizeof( HardwareVerts::FileHeader_t ) ); + targetBuf.Put( compressedBuffer.Base(), compressedBuffer.TellPut() ); + } + + targetBuf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + } + + bConverted = true; + pExt = ".vhv"; + } + + if ( !bConverted ) + { + // straight copy + AddBufferToPak( newPakFile, relativeName, sourceBuf.Base(), sourceBuf.TellMaxPut(), false ); + } + else + { + // converted filename + V_StripExtension( relativeName, relativeName, sizeof( relativeName ) ); + V_strcat( relativeName, ".360", sizeof( relativeName ) ); + V_strcat( relativeName, pExt, sizeof( relativeName ) ); + AddBufferToPak( newPakFile, relativeName, targetBuf.Base(), targetBuf.TellMaxPut(), false ); + } + + if ( V_stristr( relativeName, ".hdr" ) || V_stristr( relativeName, "_hdr" ) ) + { + hdrFiles.AddToTail( relativeName ); + } + + DevMsg( "Created '%s' in lump pak in '%s'.\n", relativeName, pInFilename ); + } + + // strip ldr version of hdr files + for ( int i=0; iRemoveFileFromZip( ldrFileName ); + } + } + + // discard old pak in favor of new pak + IZip::ReleaseZip( s_pakFile ); + s_pakFile = newPakFile; +} + +void SetAlignedLumpPosition( int lumpnum, int alignment = LUMP_ALIGNMENT ) +{ + g_pBSPHeader->lumps[lumpnum].fileofs = AlignFilePosition( g_hBSPFile, alignment ); +} + +template< class T > +int SwapLumpToDisk( int fieldType, int lumpnum ) +{ + if ( g_pBSPHeader->lumps[lumpnum].filelen == 0 ) + return 0; + + DevMsg( "Swapping %s\n", GetLumpName( lumpnum ) ); + + // lump swap may expand, allocate enough expansion room + void *pBuffer = malloc( 2*g_pBSPHeader->lumps[lumpnum].filelen ); + + // CopyLumpInternal will handle the swap on load case + unsigned int fieldSize = ( fieldType == FIELD_VECTOR ) ? sizeof(Vector) : sizeof(T); + unsigned int count = CopyLumpInternal( fieldType, lumpnum, (T*)pBuffer, g_pBSPHeader->lumps[lumpnum].version ); + g_pBSPHeader->lumps[lumpnum].filelen = count * fieldSize; + + if ( g_bSwapOnWrite ) + { + // Swap the lump in place before writing + switch( lumpnum ) + { + case LUMP_VISIBILITY: + SwapVisibilityLump( (byte*)pBuffer, (byte*)pBuffer, count ); + break; + + case LUMP_PHYSCOLLIDE: + // SwapPhyscollideLump may change size + SwapPhyscollideLump( (byte*)pBuffer, (byte*)pBuffer, count ); + g_pBSPHeader->lumps[lumpnum].filelen = count; + break; + + case LUMP_PHYSDISP: + SwapPhysdispLump( (byte*)pBuffer, (byte*)pBuffer, count ); + break; + + default: + g_Swap.SwapBufferToTargetEndian( (T*)pBuffer, (T*)pBuffer, g_pBSPHeader->lumps[lumpnum].filelen / sizeof(T) ); + break; + } + } + + SetAlignedLumpPosition( lumpnum ); + SafeWrite( g_hBSPFile, pBuffer, g_pBSPHeader->lumps[lumpnum].filelen ); + + free( pBuffer ); + + return g_pBSPHeader->lumps[lumpnum].filelen; +} + +template< class T > +int SwapLumpToDisk( int lumpnum ) +{ + if ( g_pBSPHeader->lumps[lumpnum].filelen == 0 || g_Lumps.bLumpParsed[lumpnum] ) + return 0; + + DevMsg( "Swapping %s\n", GetLumpName( lumpnum ) ); + + // lump swap may expand, allocate enough room + void *pBuffer = malloc( 2*g_pBSPHeader->lumps[lumpnum].filelen ); + + // CopyLumpInternal will handle the swap on load case + int count = CopyLumpInternal( lumpnum, (T*)pBuffer, g_pBSPHeader->lumps[lumpnum].version ); + g_pBSPHeader->lumps[lumpnum].filelen = count * sizeof(T); + + if ( g_bSwapOnWrite ) + { + // Swap the lump in place before writing + g_Swap.SwapFieldsToTargetEndian( (T*)pBuffer, (T*)pBuffer, count ); + } + + SetAlignedLumpPosition( lumpnum ); + SafeWrite( g_hBSPFile, pBuffer, g_pBSPHeader->lumps[lumpnum].filelen ); + free( pBuffer ); + + return g_pBSPHeader->lumps[lumpnum].filelen; +} + +void SwapLeafAmbientLightingLumpToDisk() +{ + if ( HasLump( LUMP_LEAF_AMBIENT_INDEX ) || HasLump( LUMP_LEAF_AMBIENT_INDEX_HDR ) ) + { + // current version, swap in place + if ( HasLump( LUMP_LEAF_AMBIENT_INDEX_HDR ) ) + { + // write HDR + SwapLumpToDisk< dleafambientlighting_t >( LUMP_LEAF_AMBIENT_LIGHTING_HDR ); + SwapLumpToDisk< dleafambientindex_t >( LUMP_LEAF_AMBIENT_INDEX_HDR ); + + // cull LDR + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen = 0; + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen = 0; + } + else + { + // no HDR, keep LDR version + SwapLumpToDisk< dleafambientlighting_t >( LUMP_LEAF_AMBIENT_LIGHTING ); + SwapLumpToDisk< dleafambientindex_t >( LUMP_LEAF_AMBIENT_INDEX ); + } + } + else + { + // older ambient lighting version (before index) + // load older ambient lighting into memory and build ambient/index + // an older leaf version would have already built the new LDR leaf ambient/index + int numLeafs = g_pBSPHeader->lumps[LUMP_LEAFS].filelen / sizeof( dleaf_t ); + LoadLeafAmbientLighting( numLeafs ); + + if ( HasLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) ) + { + DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) ); + DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_INDEX_HDR ) ); + + // write HDR + if ( g_bSwapOnWrite ) + { + g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientLightingHDR.Base(), g_LeafAmbientLightingHDR.Count() ); + g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientIndexHDR.Base(), g_LeafAmbientIndexHDR.Count() ); + } + + SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_LIGHTING_HDR ); + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].version = LUMP_LEAF_AMBIENT_LIGHTING_VERSION; + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].filelen = g_LeafAmbientLightingHDR.Count() * sizeof( dleafambientlighting_t ); + SafeWrite( g_hBSPFile, g_LeafAmbientLightingHDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].filelen ); + + SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_INDEX_HDR ); + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX_HDR].filelen = g_LeafAmbientIndexHDR.Count() * sizeof( dleafambientindex_t ); + SafeWrite( g_hBSPFile, g_LeafAmbientIndexHDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX_HDR].filelen ); + + // mark as processed + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING_HDR] = true; + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX_HDR] = true; + + // cull LDR + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen = 0; + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen = 0; + } + else + { + // no HDR, keep LDR version + DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_LIGHTING ) ); + DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_INDEX ) ); + + if ( g_bSwapOnWrite ) + { + g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientLightingLDR.Base(), g_LeafAmbientLightingLDR.Count() ); + g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientIndexLDR.Base(), g_LeafAmbientIndexLDR.Count() ); + } + + SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_LIGHTING ); + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].version = LUMP_LEAF_AMBIENT_LIGHTING_VERSION; + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen = g_LeafAmbientLightingLDR.Count() * sizeof( dleafambientlighting_t ); + SafeWrite( g_hBSPFile, g_LeafAmbientLightingLDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen ); + + SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_INDEX ); + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen = g_LeafAmbientIndexLDR.Count() * sizeof( dleafambientindex_t ); + SafeWrite( g_hBSPFile, g_LeafAmbientIndexLDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen ); + + // mark as processed + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING] = true; + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX] = true; + } + + g_LeafAmbientLightingLDR.Purge(); + g_LeafAmbientIndexLDR.Purge(); + g_LeafAmbientLightingHDR.Purge(); + g_LeafAmbientIndexHDR.Purge(); + } +} + +void SwapLeafLumpToDisk( void ) +{ + DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAFS ) ); + + // load the leafs + int count = LoadLeafs(); + if ( g_bSwapOnWrite ) + { + g_Swap.SwapFieldsToTargetEndian( dleafs, count ); + } + + bool bOldLeafVersion = ( LumpVersion( LUMP_LEAFS ) == 0 ); + if ( bOldLeafVersion ) + { + // version has been converted in the load process + // not updating the version ye, SwapLeafAmbientLightingLumpToDisk() can detect + g_pBSPHeader->lumps[LUMP_LEAFS].filelen = count * sizeof( dleaf_t ); + } + + SetAlignedLumpPosition( LUMP_LEAFS ); + SafeWrite( g_hBSPFile, dleafs, g_pBSPHeader->lumps[LUMP_LEAFS].filelen ); + + SwapLeafAmbientLightingLumpToDisk(); + + if ( bOldLeafVersion ) + { + // version has been converted in the load process + // can now safely change + g_pBSPHeader->lumps[LUMP_LEAFS].version = 1; + } + +#if defined( BSP_USE_LESS_MEMORY ) + if ( dleafs ) + { + free( dleafs ); + dleafs = NULL; + } +#endif +} + +void SwapOcclusionLumpToDisk( void ) +{ + DevMsg( "Swapping %s\n", GetLumpName( LUMP_OCCLUSION ) ); + + LoadOcclusionLump(); + SetAlignedLumpPosition( LUMP_OCCLUSION ); + AddOcclusionLump(); +} + +void SwapPakfileLumpToDisk( const char *pInFilename ) +{ + DevMsg( "Swapping %s\n", GetLumpName( LUMP_PAKFILE ) ); + + byte *pakbuffer = NULL; + int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer ); + if ( paksize > 0 ) + { + GetPakFile()->ActivateByteSwapping( IsX360() ); + GetPakFile()->ParseFromBuffer( pakbuffer, paksize ); + + ConvertPakFileContents( pInFilename ); + } + free( pakbuffer ); + + SetAlignedLumpPosition( LUMP_PAKFILE, XBOX_DVD_SECTORSIZE ); + WritePakFileLump(); + + ReleasePakFileLumps(); +} + +void SwapGameLumpsToDisk( void ) +{ + DevMsg( "Swapping %s\n", GetLumpName( LUMP_GAME_LUMP ) ); + + g_GameLumps.ParseGameLump( g_pBSPHeader ); + SetAlignedLumpPosition( LUMP_GAME_LUMP ); + AddGameLumps(); +} + +//----------------------------------------------------------------------------- +// Generate a table of all static props, used for resolving static prop lighting +// files back to their actual mdl. +//----------------------------------------------------------------------------- +void BuildStaticPropNameTable() +{ + g_StaticPropNames.Purge(); + g_StaticPropInstances.Purge(); + + g_GameLumps.ParseGameLump( g_pBSPHeader ); + + GameLumpHandle_t hGameLump = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); + if ( hGameLump != g_GameLumps.InvalidGameLump() ) + { + int nVersion = g_GameLumps.GetGameLumpVersion( hGameLump ); + if ( nVersion < 4 ) + { + // old unsupported version + return; + } + + if ( nVersion != 4 && nVersion != 5 && nVersion != 6 ) + { + Error( "Unknown Static Prop Lump version %d!\n", nVersion ); + } + + byte *pGameLumpData = (byte *)g_GameLumps.GetGameLump( hGameLump ); + if ( pGameLumpData && g_GameLumps.GameLumpSize( hGameLump ) ) + { + // get the model dictionary + int count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + StaticPropDictLump_t *pStaticPropDictLump = (StaticPropDictLump_t *)pGameLumpData; + for ( int i = 0; i < count; i++ ) + { + g_StaticPropNames.AddToTail( pStaticPropDictLump[i].m_Name ); + } + pGameLumpData += count * sizeof( StaticPropDictLump_t ); + + // skip the leaf list + count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + pGameLumpData += count * sizeof( StaticPropLeafLump_t ); + + // get the instances + count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + for ( int i = 0; i < count; i++ ) + { + int propType; + if ( nVersion == 4 ) + { + propType = ((StaticPropLumpV4_t *)pGameLumpData)->m_PropType; + pGameLumpData += sizeof( StaticPropLumpV4_t ); + } + else if ( nVersion == 5 ) + { + propType = ((StaticPropLumpV5_t *)pGameLumpData)->m_PropType; + pGameLumpData += sizeof( StaticPropLumpV5_t ); + } + else + { + propType = ((StaticPropLump_t *)pGameLumpData)->m_PropType; + pGameLumpData += sizeof( StaticPropLump_t ); + } + g_StaticPropInstances.AddToTail( propType ); + } + } + } + + g_GameLumps.DestroyAllGameLumps(); +} + +int AlignBuffer( CUtlBuffer &buffer, int alignment ) +{ + unsigned int newPosition = AlignValue( buffer.TellPut(), alignment ); + int padLength = newPosition - buffer.TellPut(); + for ( int i = 0; ipLump->fileofs; + int fileOffsetB = pSortedLumpB->pLump->fileofs; + + int fileSizeA = pSortedLumpA->pLump->filelen; + int fileSizeB = pSortedLumpB->pLump->filelen; + + // invalid or empty lumps get sorted together + if ( !fileSizeA ) + { + fileOffsetA = 0; + } + if ( !fileSizeB ) + { + fileOffsetB = 0; + } + + // compare by offset, want ascending + if ( fileOffsetA < fileOffsetB ) + { + return -1; + } + else if ( fileOffsetA > fileOffsetB ) + { + return 1; + } + + return 0; +} + +bool CompressGameLump( dheader_t *pInBSPHeader, dheader_t *pOutBSPHeader, CUtlBuffer &outputBuffer, CompressFunc_t pCompressFunc ) +{ + CByteswap byteSwap; + + dgamelumpheader_t* pInGameLumpHeader = (dgamelumpheader_t*)(((byte *)pInBSPHeader) + pInBSPHeader->lumps[LUMP_GAME_LUMP].fileofs); + dgamelump_t* pInGameLump = (dgamelump_t*)(pInGameLumpHeader + 1); + + byteSwap.ActivateByteSwapping( true ); + byteSwap.SwapFieldsToTargetEndian( pInGameLumpHeader ); + byteSwap.SwapFieldsToTargetEndian( pInGameLump, pInGameLumpHeader->lumpCount ); + + unsigned int newOffset = outputBuffer.TellPut(); + outputBuffer.Put( pInGameLumpHeader, sizeof( dgamelumpheader_t ) ); + outputBuffer.Put( pInGameLump, pInGameLumpHeader->lumpCount * sizeof( dgamelump_t ) ); + + dgamelumpheader_t* pOutGameLumpHeader = (dgamelumpheader_t*)((byte *)outputBuffer.Base() + newOffset); + dgamelump_t* pOutGameLump = (dgamelump_t*)(pOutGameLumpHeader + 1); + + // add a dummy terminal gamelump + // purposely NOT updating the .filelen to reflect the compressed size, but leaving as original size + // callers use the next entry offset to determine compressed size + pOutGameLumpHeader->lumpCount++; + dgamelump_t dummyLump = { 0 }; + outputBuffer.Put( &dummyLump, sizeof( dgamelump_t ) ); + + for ( int i = 0; i < pInGameLumpHeader->lumpCount; i++ ) + { + CUtlBuffer inputBuffer; + CUtlBuffer compressedBuffer; + + pOutGameLump[i].fileofs = AlignBuffer( outputBuffer, 4 ); + + if ( pInGameLump[i].filelen ) + { + inputBuffer.SetExternalBuffer( ((byte *)pInBSPHeader) + pInGameLump[i].fileofs, pInGameLump[i].filelen, pInGameLump[i].filelen ); + + bool bCompressed = pCompressFunc( inputBuffer, compressedBuffer ); + if ( bCompressed ) + { + pOutGameLump[i].flags |= GAMELUMPFLAG_COMPRESSED; + + outputBuffer.Put( compressedBuffer.Base(), compressedBuffer.TellPut() ); + compressedBuffer.Purge(); + } + else + { + // as is + outputBuffer.Put( inputBuffer.Base(), inputBuffer.TellPut() ); + } + } + } + + // fix the dummy terminal lump + int lastLump = pOutGameLumpHeader->lumpCount-1; + pOutGameLump[lastLump].fileofs = outputBuffer.TellPut(); + + // fix the output for 360, swapping it back + byteSwap.SwapFieldsToTargetEndian( pOutGameLump, pOutGameLumpHeader->lumpCount ); + byteSwap.SwapFieldsToTargetEndian( pOutGameLumpHeader ); + + pOutBSPHeader->lumps[LUMP_GAME_LUMP].fileofs = newOffset; + pOutBSPHeader->lumps[LUMP_GAME_LUMP].filelen = outputBuffer.TellPut() - newOffset; + + return true; +} + +bool CompressBSP( CUtlBuffer &inputBuffer, CUtlBuffer &outputBuffer, CompressFunc_t pCompressFunc ) +{ + CByteswap byteSwap; + + dheader_t *pInBSPHeader = (dheader_t *)inputBuffer.Base(); + if ( pInBSPHeader->ident != BigLong( IDBSPHEADER ) || !pCompressFunc ) + { + // only compress 360 bsp's + return false; + } + + // bsp is 360, swap the header back + byteSwap.ActivateByteSwapping( true ); + byteSwap.SwapFieldsToTargetEndian( pInBSPHeader ); + + // output will be smaller, use input size as upper bound + outputBuffer.EnsureCapacity( inputBuffer.TellMaxPut() ); + outputBuffer.Put( pInBSPHeader, sizeof( dheader_t ) ); + + dheader_t *pOutBSPHeader = (dheader_t *)outputBuffer.Base(); + + // must adhere to input lump's offset order and process according to that, NOT lump num + // sort by offset order + CUtlVector< SortedLump_t > sortedLumps; + for ( int i = 0; i < HEADER_LUMPS; i++ ) + { + int iIndex = sortedLumps.AddToTail(); + sortedLumps[iIndex].lumpNum = i; + sortedLumps[iIndex].pLump = &pInBSPHeader->lumps[i]; + } + sortedLumps.Sort( SortLumpsByOffset ); + + // iterate in sorted order + for ( int i = 0; i < HEADER_LUMPS; ++i ) + { + SortedLump_t *pSortedLump = &sortedLumps[i]; + int lumpNum = pSortedLump->lumpNum; + + if ( !pSortedLump->pLump->filelen ) + { + // degenerate + pOutBSPHeader->lumps[lumpNum].fileofs = 0; + } + else + { + int alignment = 4; + if ( lumpNum == LUMP_PAKFILE ) + { + alignment = 2048; + } + unsigned int newOffset = AlignBuffer( outputBuffer, alignment ); + + // only set by compressed lumps, hides the uncompressed size + *((unsigned int *)pOutBSPHeader->lumps[lumpNum].fourCC) = 0; + + CUtlBuffer inputBuffer; + inputBuffer.SetExternalBuffer( ((byte *)pInBSPHeader) + pSortedLump->pLump->fileofs, pSortedLump->pLump->filelen, pSortedLump->pLump->filelen ); + + if ( lumpNum == LUMP_GAME_LUMP ) + { + // the game lump has to have each of its components individually compressed + CompressGameLump( pInBSPHeader, pOutBSPHeader, outputBuffer, pCompressFunc ); + } + else if ( lumpNum == LUMP_PAKFILE ) + { + // add as is + pOutBSPHeader->lumps[lumpNum].fileofs = newOffset; + outputBuffer.Put( inputBuffer.Base(), inputBuffer.TellPut() ); + } + else + { + CUtlBuffer compressedBuffer; + bool bCompressed = pCompressFunc( inputBuffer, compressedBuffer ); + if ( bCompressed ) + { + // placing the uncompressed size in the unused fourCC, will decode at runtime + *((unsigned int *)pOutBSPHeader->lumps[lumpNum].fourCC) = BigLong( inputBuffer.TellPut() ); + pOutBSPHeader->lumps[lumpNum].filelen = compressedBuffer.TellPut(); + pOutBSPHeader->lumps[lumpNum].fileofs = newOffset; + outputBuffer.Put( compressedBuffer.Base(), compressedBuffer.TellPut() ); + compressedBuffer.Purge(); + } + else + { + // add as is + pOutBSPHeader->lumps[lumpNum].fileofs = newOffset; + outputBuffer.Put( inputBuffer.Base(), inputBuffer.TellPut() ); + } + } + } + } + + // fix the output for 360, swapping it back + byteSwap.SetTargetBigEndian( true ); + byteSwap.SwapFieldsToTargetEndian( pOutBSPHeader ); + + return true; +} + +//----------------------------------------------------------------------------- +// For all lumps in a bsp: Loads the lump from file A, swaps it, writes it to file B. +// This limits the memory used for the swap process which helps the Xbox 360. +// +// NOTE: These lumps will be written to the file in exactly the order they appear here, +// so they can be shifted around if desired for file access optimization. +//----------------------------------------------------------------------------- +bool SwapBSPFile( const char *pInFilename, const char *pOutFilename, bool bSwapOnLoad, VTFConvertFunc_t pVTFConvertFunc, VHVFixupFunc_t pVHVFixupFunc, CompressFunc_t pCompressFunc ) +{ + DevMsg( "Creating %s\n", pOutFilename ); + + if ( !g_pFileSystem->FileExists( pInFilename ) ) + { + Warning( "Error! Couldn't open input file %s - BSP swap failed!\n", pInFilename ); + return false; + } + + g_hBSPFile = SafeOpenWrite( pOutFilename ); + if ( !g_hBSPFile ) + { + Warning( "Error! Couldn't open output file %s - BSP swap failed!\n", pOutFilename ); + return false; + } + + if ( !pVTFConvertFunc ) + { + Warning( "Error! Missing VTF Conversion function\n" ); + return false; + } + g_pVTFConvertFunc = pVTFConvertFunc; + + // optional VHV fixup + g_pVHVFixupFunc = pVHVFixupFunc; + + // optional compression callback + g_pCompressFunc = pCompressFunc; + + // These must be mutually exclusive + g_bSwapOnLoad = bSwapOnLoad; + g_bSwapOnWrite = !bSwapOnLoad; + + g_Swap.ActivateByteSwapping( true ); + + OpenBSPFile( pInFilename ); + + // CRC the bsp first + CRC32_t mapCRC; + CRC32_Init(&mapCRC); + if ( !CRC_MapFile( &mapCRC, pInFilename ) ) + { + Warning( "Failed to CRC the bsp\n" ); + return false; + } + + // hold a dictionary of all the static prop names + // this is needed to properly convert any VHV files inside the pak lump + BuildStaticPropNameTable(); + + // Set the output file pointer after the header + dheader_t dummyHeader = { 0 }; + SafeWrite( g_hBSPFile, &dummyHeader, sizeof( dheader_t ) ); + + // To allow for alignment fixups, the lumps will be written to the + // output file in the order they appear in this function. + + // NOTE: Flags for 360 !!!MUST!!! be first + SwapLumpToDisk< dflagslump_t >( LUMP_MAP_FLAGS ); + + // complex lump swaps first or for later contingent data + SwapLeafLumpToDisk(); + SwapOcclusionLumpToDisk(); + SwapGameLumpsToDisk(); + + // Strip dead or non relevant lumps + g_pBSPHeader->lumps[LUMP_DISP_LIGHTMAP_ALPHAS].filelen = 0; + g_pBSPHeader->lumps[LUMP_FACEIDS].filelen = 0; + + // Strip obsolete LDR in favor of HDR + if ( SwapLumpToDisk( LUMP_FACES_HDR ) ) + { + g_pBSPHeader->lumps[LUMP_FACES].filelen = 0; + } + else + { + // no HDR, keep LDR version + SwapLumpToDisk( LUMP_FACES ); + } + + if ( SwapLumpToDisk( LUMP_WORLDLIGHTS_HDR ) ) + { + g_pBSPHeader->lumps[LUMP_WORLDLIGHTS].filelen = 0; + } + else + { + // no HDR, keep LDR version + SwapLumpToDisk( LUMP_WORLDLIGHTS ); + } + + // Simple lump swaps + SwapLumpToDisk( FIELD_CHARACTER, LUMP_PHYSDISP ); + SwapLumpToDisk( FIELD_CHARACTER, LUMP_PHYSCOLLIDE ); + SwapLumpToDisk( FIELD_CHARACTER, LUMP_VISIBILITY ); + SwapLumpToDisk( LUMP_MODELS ); + SwapLumpToDisk( LUMP_VERTEXES ); + SwapLumpToDisk( LUMP_PLANES ); + SwapLumpToDisk( LUMP_NODES ); + SwapLumpToDisk( LUMP_TEXINFO ); + SwapLumpToDisk( LUMP_TEXDATA ); + SwapLumpToDisk( LUMP_DISPINFO ); + SwapLumpToDisk( LUMP_DISP_VERTS ); + SwapLumpToDisk( LUMP_DISP_TRIS ); + SwapLumpToDisk( FIELD_CHARACTER, LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS ); + SwapLumpToDisk( LUMP_FACE_MACRO_TEXTURE_INFO ); + SwapLumpToDisk( LUMP_PRIMITIVES ); + SwapLumpToDisk( LUMP_PRIMVERTS ); + SwapLumpToDisk( FIELD_SHORT, LUMP_PRIMINDICES ); + SwapLumpToDisk( LUMP_ORIGINALFACES ); + SwapLumpToDisk( FIELD_SHORT, LUMP_LEAFFACES ); + SwapLumpToDisk( FIELD_SHORT, LUMP_LEAFBRUSHES ); + SwapLumpToDisk( FIELD_INTEGER, LUMP_SURFEDGES ); + SwapLumpToDisk( LUMP_EDGES ); + SwapLumpToDisk( LUMP_BRUSHES ); + SwapLumpToDisk( LUMP_BRUSHSIDES ); + SwapLumpToDisk( LUMP_AREAS ); + SwapLumpToDisk( LUMP_AREAPORTALS ); + SwapLumpToDisk( FIELD_CHARACTER, LUMP_ENTITIES ); + SwapLumpToDisk( LUMP_LEAFWATERDATA ); + SwapLumpToDisk( FIELD_VECTOR, LUMP_VERTNORMALS ); + SwapLumpToDisk( FIELD_SHORT, LUMP_VERTNORMALINDICES ); + SwapLumpToDisk( FIELD_VECTOR, LUMP_CLIPPORTALVERTS ); + SwapLumpToDisk( LUMP_CUBEMAPS ); + SwapLumpToDisk( FIELD_CHARACTER, LUMP_TEXDATA_STRING_DATA ); + SwapLumpToDisk( FIELD_INTEGER, LUMP_TEXDATA_STRING_TABLE ); + SwapLumpToDisk( LUMP_OVERLAYS ); + SwapLumpToDisk( LUMP_WATEROVERLAYS ); + SwapLumpToDisk( FIELD_SHORT, LUMP_LEAFMINDISTTOWATER ); + SwapLumpToDisk( LUMP_OVERLAY_FADES ); + + + // NOTE: this data placed at the end for the sake of 360: + { + // NOTE: lighting must be the penultimate lump + // (allows 360 to free this memory part-way through map loading) + if ( SwapLumpToDisk( FIELD_CHARACTER, LUMP_LIGHTING_HDR ) ) + { + g_pBSPHeader->lumps[LUMP_LIGHTING].filelen = 0; + } + else + { + // no HDR, keep LDR version + SwapLumpToDisk( FIELD_CHARACTER, LUMP_LIGHTING ); + } + // NOTE: Pakfile for 360 !!!MUST!!! be last + SwapPakfileLumpToDisk( pInFilename ); + } + + + // Store the crc in the flags lump version field + g_pBSPHeader->lumps[LUMP_MAP_FLAGS].version = mapCRC; + + // Pad out the end of the file to a sector boundary for optimal IO + AlignFilePosition( g_hBSPFile, XBOX_DVD_SECTORSIZE ); + + // Warn of any lumps that didn't get swapped + for ( int i = 0; i < HEADER_LUMPS; ++i ) + { + if ( HasLump( i ) && !g_Lumps.bLumpParsed[i] ) + { + // a new lump got added that needs to have a swap function + Warning( "BSP: '%s', %s has no swap or copy function. Discarding!\n", pInFilename, GetLumpName(i) ); + + // the data didn't get copied, so don't reference garbage + g_pBSPHeader->lumps[i].filelen = 0; + } + } + + // Write the updated header + g_pFileSystem->Seek( g_hBSPFile, 0, FILESYSTEM_SEEK_HEAD ); + WriteData( g_pBSPHeader ); + g_pFileSystem->Close( g_hBSPFile ); + g_hBSPFile = 0; + + // Cleanup + g_Swap.ActivateByteSwapping( false ); + + CloseBSPFile(); + + g_StaticPropNames.Purge(); + g_StaticPropInstances.Purge(); + + DevMsg( "Finished BSP Swap\n" ); + + // caller provided compress func will further compress compatible lumps + if ( pCompressFunc ) + { + CUtlBuffer inputBuffer; + if ( !g_pFileSystem->ReadFile( pOutFilename, NULL, inputBuffer ) ) + { + Warning( "Error! Couldn't read file %s - final BSP compression failed!\n", pOutFilename ); + return false; + } + + CUtlBuffer outputBuffer; + if ( !CompressBSP( inputBuffer, outputBuffer, pCompressFunc ) ) + { + Warning( "Error! Failed to compress BSP '%s'!\n", pOutFilename ); + return false; + } + + g_hBSPFile = SafeOpenWrite( pOutFilename ); + if ( !g_hBSPFile ) + { + Warning( "Error! Couldn't open output file %s - BSP swap failed!\n", pOutFilename ); + return false; + } + SafeWrite( g_hBSPFile, outputBuffer.Base(), outputBuffer.TellPut() ); + g_pFileSystem->Close( g_hBSPFile ); + g_hBSPFile = 0; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Get the pak lump from a BSP +//----------------------------------------------------------------------------- +bool GetPakFileLump( const char *pBSPFilename, void **pPakData, int *pPakSize ) +{ + *pPakData = NULL; + *pPakSize = 0; + + if ( !g_pFileSystem->FileExists( pBSPFilename ) ) + { + Warning( "Error! Couldn't open file %s!\n", pBSPFilename ); + return false; + } + + // determine endian nature + dheader_t *pHeader; + LoadFile( pBSPFilename, (void **)&pHeader ); + bool bSwap = ( pHeader->ident == BigLong( IDBSPHEADER ) ); + free( pHeader ); + + g_bSwapOnLoad = bSwap; + g_bSwapOnWrite = !bSwap; + + OpenBSPFile( pBSPFilename ); + + if ( g_pBSPHeader->lumps[LUMP_PAKFILE].filelen ) + { + *pPakSize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, pPakData ); + } + + CloseBSPFile(); + + return true; +} + +// compare function for qsort below +static int LumpOffsetCompare( const void *pElem1, const void *pElem2 ) +{ + int lump1 = *(byte *)pElem1; + int lump2 = *(byte *)pElem2; + + if ( lump1 != lump2 ) + { + // force LUMP_MAP_FLAGS to be first, always + if ( lump1 == LUMP_MAP_FLAGS ) + { + return -1; + } + else if ( lump2 == LUMP_MAP_FLAGS ) + { + return 1; + } + + // force LUMP_PAKFILE to be last, always + if ( lump1 == LUMP_PAKFILE ) + { + return 1; + } + else if ( lump2 == LUMP_PAKFILE ) + { + return -1; + } + } + + int fileOffset1 = g_pBSPHeader->lumps[lump1].fileofs; + int fileOffset2 = g_pBSPHeader->lumps[lump2].fileofs; + + // invalid or empty lumps will get sorted together + if ( !g_pBSPHeader->lumps[lump1].filelen ) + { + fileOffset1 = 0; + } + + if ( !g_pBSPHeader->lumps[lump2].filelen ) + { + fileOffset2 = 0; + } + + // compare by offset + if ( fileOffset1 < fileOffset2 ) + { + return -1; + } + else if ( fileOffset1 > fileOffset2 ) + { + return 1; + } + return 0; +} + +//----------------------------------------------------------------------------- +// Replace the pak lump in a BSP +//----------------------------------------------------------------------------- +bool SetPakFileLump( const char *pBSPFilename, const char *pNewFilename, void *pPakData, int pakSize ) +{ + if ( !g_pFileSystem->FileExists( pBSPFilename ) ) + { + Warning( "Error! Couldn't open file %s!\n", pBSPFilename ); + return false; + } + + // determine endian nature + dheader_t *pHeader; + LoadFile( pBSPFilename, (void **)&pHeader ); + bool bSwap = ( pHeader->ident == BigLong( IDBSPHEADER ) ); + free( pHeader ); + + g_bSwapOnLoad = bSwap; + g_bSwapOnWrite = bSwap; + + OpenBSPFile( pBSPFilename ); + + // save a copy of the old header + // generating a new bsp is a destructive operation + dheader_t oldHeader; + oldHeader = *g_pBSPHeader; + + g_hBSPFile = SafeOpenWrite( pNewFilename ); + if ( !g_hBSPFile ) + { + return false; + } + + // placeholder only, reset at conclusion + WriteData( &oldHeader ); + + // lumps must be reserialized in same relative offset order + // build sorted order table + int readOrder[HEADER_LUMPS]; + for ( int i=0; ilumps[lump].filelen; + if ( length ) + { + // save the lump data + int offset = g_pBSPHeader->lumps[lump].fileofs; + SetAlignedLumpPosition( lump ); + SafeWrite( g_hBSPFile, (byte *)g_pBSPHeader + offset, length ); + } + else + { + g_pBSPHeader->lumps[lump].fileofs = 0; + } + } + + // Always write the pak file at the end + // Pad out the end of the file to a sector boundary for optimal IO + g_pBSPHeader->lumps[LUMP_PAKFILE].fileofs = AlignFilePosition( g_hBSPFile, XBOX_DVD_SECTORSIZE ); + g_pBSPHeader->lumps[LUMP_PAKFILE].filelen = pakSize; + SafeWrite( g_hBSPFile, pPakData, pakSize ); + + // Pad out the end of the file to a sector boundary for optimal IO + AlignFilePosition( g_hBSPFile, XBOX_DVD_SECTORSIZE ); + + // Write the updated header + g_pFileSystem->Seek( g_hBSPFile, 0, FILESYSTEM_SEEK_HEAD ); + WriteData( g_pBSPHeader ); + g_pFileSystem->Close( g_hBSPFile ); + + CloseBSPFile(); + + return true; +} + +//----------------------------------------------------------------------------- +// Build a list of files that BSP owns, world/cubemap materials, static props, etc. +//----------------------------------------------------------------------------- +bool GetBSPDependants( const char *pBSPFilename, CUtlVector< CUtlString > *pList ) +{ + if ( !g_pFileSystem->FileExists( pBSPFilename ) ) + { + Warning( "Error! Couldn't open file %s!\n", pBSPFilename ); + return false; + } + + // must be set, but exact hdr not critical for dependant traversal + SetHDRMode( false ); + + LoadBSPFile( pBSPFilename ); + + char szBspName[MAX_PATH]; + V_FileBase( pBSPFilename, szBspName, sizeof( szBspName ) ); + V_SetExtension( szBspName, ".bsp", sizeof( szBspName ) ); + + // get embedded pak files, and internals + char szFilename[MAX_PATH]; + int fileSize; + int fileId = -1; + for ( ;; ) + { + fileId = GetPakFile()->GetNextFilename( fileId, szFilename, sizeof( szFilename ), fileSize ); + if ( fileId == -1 ) + { + break; + } + pList->AddToTail( szFilename ); + } + + // get all the world materials + for ( int i=0; iAddToTail( szFilename ); + } + + // get all the static props + GameLumpHandle_t hGameLump = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); + if ( hGameLump != g_GameLumps.InvalidGameLump() ) + { + byte *pGameLumpData = (byte *)g_GameLumps.GetGameLump( hGameLump ); + if ( pGameLumpData && g_GameLumps.GameLumpSize( hGameLump ) ) + { + int count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + + StaticPropDictLump_t *pStaticPropDictLump = (StaticPropDictLump_t *)pGameLumpData; + for ( int i=0; iAddToTail( pStaticPropDictLump[i].m_Name ); + } + } + } + + // get all the detail props + hGameLump = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROPS ); + if ( hGameLump != g_GameLumps.InvalidGameLump() ) + { + byte *pGameLumpData = (byte *)g_GameLumps.GetGameLump( hGameLump ); + if ( pGameLumpData && g_GameLumps.GameLumpSize( hGameLump ) ) + { + int count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + + DetailObjectDictLump_t *pDetailObjectDictLump = (DetailObjectDictLump_t *)pGameLumpData; + for ( int i=0; iAddToTail( pDetailObjectDictLump[i].m_Name ); + } + pGameLumpData += count * sizeof( DetailObjectDictLump_t ); + + if ( g_GameLumps.GetGameLumpVersion( hGameLump ) == 4 ) + { + count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + if ( count ) + { + // All detail prop sprites must lie in the material detail/detailsprites + pList->AddToTail( "materials/detail/detailsprites.vmt" ); + } + } + } + } + + UnloadBSPFile(); + + return true; +} + diff --git a/mp/src/utils/common/bsplib.h b/mp/src/utils/common/bsplib.h index 83486e8b..80952ba9 100644 --- a/mp/src/utils/common/bsplib.h +++ b/mp/src/utils/common/bsplib.h @@ -1,404 +1,404 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// - -#ifndef BSPLIB_H -#define BSPLIB_H - -#ifdef _WIN32 -#pragma once -#endif - - -#include "bspfile.h" -#include "utlvector.h" -#include "utlstring.h" -#include "utllinkedlist.h" -#include "byteswap.h" -#ifdef ENGINE_DLL -#include "zone.h" -#endif - -#ifdef ENGINE_DLL -typedef CUtlVector > CDispLightmapSamplePositions; -#else -typedef CUtlVector CDispLightmapSamplePositions; -#endif - -class ISpatialQuery; -struct Ray_t; -class Vector2D; -struct portal_t; -class CUtlBuffer; -class IZip; - -// this is only true in vrad -extern bool g_bHDR; - -// default width/height of luxels in world units. -#define DEFAULT_LUXEL_SIZE ( 16.0f ) - -#define SINGLE_BRUSH_MAP (MAX_BRUSH_LIGHTMAP_DIM_INCLUDING_BORDER*MAX_BRUSH_LIGHTMAP_DIM_INCLUDING_BORDER) -#define SINGLEMAP (MAX_LIGHTMAP_DIM_INCLUDING_BORDER*MAX_LIGHTMAP_DIM_INCLUDING_BORDER) - -struct entity_t -{ - Vector origin; - int firstbrush; - int numbrushes; - epair_t *epairs; - - // only valid for func_areaportals - int areaportalnum; - int portalareas[2]; - portal_t *m_pPortalsLeadingIntoAreas[2]; // portals leading into portalareas -}; - -extern int num_entities; -extern entity_t entities[MAX_MAP_ENTITIES]; - -extern int nummodels; -extern dmodel_t dmodels[MAX_MAP_MODELS]; - -extern int visdatasize; -extern byte dvisdata[MAX_MAP_VISIBILITY]; -extern dvis_t *dvis; - -extern CUtlVector dlightdataHDR; -extern CUtlVector dlightdataLDR; -extern CUtlVector *pdlightdata; -extern CUtlVector dentdata; - -extern int numleafs; -#if !defined( _X360 ) -extern dleaf_t dleafs[MAX_MAP_LEAFS]; -#else -extern dleaf_t *dleafs; -#endif -extern CUtlVector *g_pLeafAmbientLighting; -extern CUtlVector *g_pLeafAmbientIndex; -extern unsigned short g_LeafMinDistToWater[MAX_MAP_LEAFS]; - -extern int numplanes; -extern dplane_t dplanes[MAX_MAP_PLANES]; - -extern int numvertexes; -extern dvertex_t dvertexes[MAX_MAP_VERTS]; - -extern int g_numvertnormalindices; // dfaces reference these. These index g_vertnormals. -extern unsigned short g_vertnormalindices[MAX_MAP_VERTNORMALS]; - -extern int g_numvertnormals; -extern Vector g_vertnormals[MAX_MAP_VERTNORMALS]; - -extern int numnodes; -extern dnode_t dnodes[MAX_MAP_NODES]; - -extern CUtlVector texinfo; - -extern int numtexdata; -extern dtexdata_t dtexdata[MAX_MAP_TEXDATA]; - -// displacement map .bsp file info -extern CUtlVector g_dispinfo; -extern CUtlVector g_DispVerts; -extern CUtlVector g_DispTris; -extern CDispLightmapSamplePositions g_DispLightmapSamplePositions; // LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS - -extern int numorigfaces; -extern dface_t dorigfaces[MAX_MAP_FACES]; - -extern int g_numprimitives; -extern dprimitive_t g_primitives[MAX_MAP_PRIMITIVES]; - -extern int g_numprimverts; -extern dprimvert_t g_primverts[MAX_MAP_PRIMVERTS]; - -extern int g_numprimindices; -extern unsigned short g_primindices[MAX_MAP_PRIMINDICES]; - -extern int numfaces; -extern dface_t dfaces[MAX_MAP_FACES]; - -extern int numfaceids; -extern CUtlVector dfaceids; - -extern int numfaces_hdr; -extern dface_t dfaces_hdr[MAX_MAP_FACES]; - -extern int numedges; -extern dedge_t dedges[MAX_MAP_EDGES]; - -extern int numleaffaces; -extern unsigned short dleaffaces[MAX_MAP_LEAFFACES]; - -extern int numleafbrushes; -extern unsigned short dleafbrushes[MAX_MAP_LEAFBRUSHES]; - -extern int numsurfedges; -extern int dsurfedges[MAX_MAP_SURFEDGES]; - -extern int numareas; -extern darea_t dareas[MAX_MAP_AREAS]; - -extern int numareaportals; -extern dareaportal_t dareaportals[MAX_MAP_AREAPORTALS]; - -extern int numbrushes; -extern dbrush_t dbrushes[MAX_MAP_BRUSHES]; - -extern int numbrushsides; -extern dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; - -extern int *pNumworldlights; -extern dworldlight_t *dworldlights; - -extern Vector g_ClipPortalVerts[MAX_MAP_PORTALVERTS]; -extern int g_nClipPortalVerts; - -extern dcubemapsample_t g_CubemapSamples[MAX_MAP_CUBEMAPSAMPLES]; -extern int g_nCubemapSamples; - -extern int g_nOverlayCount; -extern doverlay_t g_Overlays[MAX_MAP_OVERLAYS]; -extern doverlayfade_t g_OverlayFades[MAX_MAP_OVERLAYS]; // Parallel array of fade info in a separate lump to avoid breaking backwards compat - -extern int g_nWaterOverlayCount; -extern dwateroverlay_t g_WaterOverlays[MAX_MAP_WATEROVERLAYS]; - -extern CUtlVector g_TexDataStringData; -extern CUtlVector g_TexDataStringTable; - -extern int numleafwaterdata; -extern dleafwaterdata_t dleafwaterdata[MAX_MAP_LEAFWATERDATA]; - -extern CUtlVector g_FaceMacroTextureInfos; - -extern CUtlVector g_OccluderData; -extern CUtlVector g_OccluderPolyData; -extern CUtlVector g_OccluderVertexIndices; - -// level flags - see LVLFLAGS_xxx in bspfile.h -extern uint32 g_LevelFlags; - -// physics collision data -extern byte *g_pPhysCollide; -extern int g_PhysCollideSize; -extern byte *g_pPhysDisp; -extern int g_PhysDispSize; - -// Embedded pack/pak file -IZip *GetPakFile( void ); -IZip *GetSwapPakFile( void ); -void ClearPakFile( IZip *pak ); -void AddFileToPak( IZip *pak, const char *pRelativeName, const char *fullpath ); -void AddBufferToPak( IZip *pak, const char *pRelativeName, void *data, int length, bool bTextMode ); -bool FileExistsInPak( IZip *pak, const char *pRelativeName ); -bool ReadFileFromPak( IZip *pak, const char *pRelativeName, bool bTextMode, CUtlBuffer &buf ); -void RemoveFileFromPak( IZip *pak, const char *pRelativeName ); -int GetNextFilename( IZip *pak, int id, char *pBuffer, int bufferSize, int &fileSize ); -void ForceAlignment( IZip *pak, bool bAlign, bool bCompatibleFormat, unsigned int alignmentSize ); - -typedef bool (*CompressFunc_t)( CUtlBuffer &inputBuffer, CUtlBuffer &outputBuffer ); -typedef bool (*VTFConvertFunc_t)( const char *pDebugName, CUtlBuffer &sourceBuf, CUtlBuffer &targetBuf, CompressFunc_t pCompressFunc ); -typedef bool (*VHVFixupFunc_t)( const char *pVhvFilename, const char *pModelName, CUtlBuffer &sourceBuf, CUtlBuffer &targetBuf ); - -//----------------------------------------------------------------------------- -// Game lump memory storage -//----------------------------------------------------------------------------- -// NOTE: This is not optimal at all; since I expect client lumps to -// not be accessed all that often. - -struct GameLump_t -{ - GameLumpId_t m_Id; - unsigned short m_Flags; - unsigned short m_Version; - CUtlMemory< unsigned char > m_Memory; -}; - -//----------------------------------------------------------------------------- -// Handle to a game lump -//----------------------------------------------------------------------------- -typedef unsigned short GameLumpHandle_t; - -class CGameLump -{ -public: - //----------------------------------------------------------------------------- - // Convert four-CC code to a handle + back - //----------------------------------------------------------------------------- - GameLumpHandle_t GetGameLumpHandle( GameLumpId_t id ); - GameLumpId_t GetGameLumpId( GameLumpHandle_t handle ); - int GetGameLumpFlags( GameLumpHandle_t handle ); - int GetGameLumpVersion( GameLumpHandle_t handle ); - void ComputeGameLumpSizeAndCount( int& size, int& clumpCount ); - void ParseGameLump( dheader_t* pHeader ); - void SwapGameLump( GameLumpId_t id, int version, byte *dest, byte *src, int size ); - - - //----------------------------------------------------------------------------- - // Game lump accessor methods - //----------------------------------------------------------------------------- - void* GetGameLump( GameLumpHandle_t handle ); - int GameLumpSize( GameLumpHandle_t handle ); - - - //----------------------------------------------------------------------------- - // Game lump iteration methods - //----------------------------------------------------------------------------- - GameLumpHandle_t FirstGameLump(); - GameLumpHandle_t NextGameLump( GameLumpHandle_t handle ); - GameLumpHandle_t InvalidGameLump(); - - - //----------------------------------------------------------------------------- - // Game lump creation/destruction method - //----------------------------------------------------------------------------- - GameLumpHandle_t CreateGameLump( GameLumpId_t id, int size, int flags, int version ); - void DestroyGameLump( GameLumpHandle_t handle ); - void DestroyAllGameLumps(); - -private: - CUtlLinkedList< GameLump_t, GameLumpHandle_t > m_GameLumps; -}; - -extern CGameLump g_GameLumps; -extern CByteswap g_Swap; - -//----------------------------------------------------------------------------- -// Helper for the bspzip tool -//----------------------------------------------------------------------------- -void ExtractZipFileFromBSP( char *pBSPFileName, char *pZipFileName ); - - -//----------------------------------------------------------------------------- -// String table methods -//----------------------------------------------------------------------------- -const char * TexDataStringTable_GetString( int stringID ); -int TexDataStringTable_AddOrFindString( const char *pString ); - -void DecompressVis (byte *in, byte *decompressed); -int CompressVis (byte *vis, byte *dest); - -void OpenBSPFile( const char *filename ); -void CloseBSPFile(void); -void LoadBSPFile( const char *filename ); -void LoadBSPFile_FileSystemOnly( const char *filename ); -void LoadBSPFileTexinfo( const char *filename ); -void WriteBSPFile( const char *filename, char *pUnused = NULL ); -void PrintBSPFileSizes(void); -void PrintBSPPackDirectory(void); -void ReleasePakFileLumps(void); -bool SwapBSPFile( const char *filename, const char *swapFilename, bool bSwapOnLoad, VTFConvertFunc_t pVTFConvertFunc, VHVFixupFunc_t pVHVFixupFunc, CompressFunc_t pCompressFunc ); -bool GetPakFileLump( const char *pBSPFilename, void **pPakData, int *pPakSize ); -bool SetPakFileLump( const char *pBSPFilename, const char *pNewFilename, void *pPakData, int pakSize ); -void WriteLumpToFile( char *filename, int lump ); -void WriteLumpToFile( char *filename, int lump, int nLumpVersion, void *pBuffer, size_t nBufLen ); -bool GetBSPDependants( const char *pBSPFilename, CUtlVector< CUtlString > *pList ); -void UnloadBSPFile(); - -void ParseEntities (void); -void UnparseEntities (void); -void PrintEntity (entity_t *ent); - -void SetKeyValue (entity_t *ent, const char *key, const char *value); -char *ValueForKey (entity_t *ent, char *key); -// will return "" if not present -int IntForKey (entity_t *ent, char *key); -int IntForKeyWithDefault(entity_t *ent, char *key, int nDefault ); -vec_t FloatForKey (entity_t *ent, char *key); -vec_t FloatForKeyWithDefault (entity_t *ent, char *key, float default_value); -void GetVectorForKey (entity_t *ent, char *key, Vector& vec); -void GetVector2DForKey (entity_t *ent, char *key, Vector2D& vec); -void GetAnglesForKey (entity_t *ent, char *key, QAngle& vec); -epair_t *ParseEpair (void); -void StripTrailing (char *e); - -// Build a list of the face's vertices (index into dvertexes). -// points must be able to hold pFace->numedges indices. -void BuildFaceCalcWindingData( dface_t *pFace, int *points ); - -// Convert a tristrip to a trilist. -// Removes degenerates. -// Fills in pTriListIndices and pnTriListIndices. -// You must free pTriListIndices with delete[]. -void TriStripToTriList( - unsigned short const *pTriStripIndices, - int nTriStripIndices, - unsigned short **pTriListIndices, - int *pnTriListIndices ); - -// Calculates the lightmap coordinates at a given set of positions given the -// lightmap basis information. -void CalcTextureCoordsAtPoints( - float const texelsPerWorldUnits[2][4], - int const subtractOffset[2], - Vector const *pPoints, - int const nPoints, - Vector2D *pCoords ); - -// Figure out lightmap extents on all (lit) faces. -void UpdateAllFaceLightmapExtents(); - - -//----------------------------------------------------------------------------- -// Gets at an interface for the tree for enumeration of leaves in volumes. -//----------------------------------------------------------------------------- -ISpatialQuery* ToolBSPTree(); - -class IBSPNodeEnumerator -{ -public: - // call back with a node and a context - virtual bool EnumerateNode( int node, Ray_t const& ray, float f, int context ) = 0; - - // call back with a leaf and a context - virtual bool EnumerateLeaf( int leaf, Ray_t const& ray, float start, float end, int context ) = 0; -}; - -//----------------------------------------------------------------------------- -// Enumerates nodes + leafs in front to back order... -//----------------------------------------------------------------------------- -bool EnumerateNodesAlongRay( Ray_t const& ray, IBSPNodeEnumerator* pEnum, int context ); - - -//----------------------------------------------------------------------------- -// Helps us find all leaves associated with a particular cluster -//----------------------------------------------------------------------------- -struct clusterlist_t -{ - int leafCount; - CUtlVector leafs; -}; - -extern CUtlVector g_ClusterLeaves; - -// Call this to build the mapping from cluster to leaves -void BuildClusterTable( ); - -void GetPlatformMapPath( const char *pMapPath, char *pPlatformMapPath, int dxlevel, int maxLength ); - -void SetHDRMode( bool bHDR ); - -// ----------------------------------------------------------------------------- // -// Helper accessors for the various structures. -// ----------------------------------------------------------------------------- // - -inline ColorRGBExp32* dface_AvgLightColor( dface_t *pFace, int nLightStyleIndex ) -{ - return (ColorRGBExp32*)&(*pdlightdata)[pFace->lightofs - (nLightStyleIndex+1) * 4]; -} - -inline const char* TexInfo_TexName( int iTexInfo ) -{ - return TexDataStringTable_GetString( dtexdata[texinfo[iTexInfo].texdata].nameStringTableID ); -} - - -#endif // BSPLIB_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef BSPLIB_H +#define BSPLIB_H + +#ifdef _WIN32 +#pragma once +#endif + + +#include "bspfile.h" +#include "utlvector.h" +#include "utlstring.h" +#include "utllinkedlist.h" +#include "byteswap.h" +#ifdef ENGINE_DLL +#include "zone.h" +#endif + +#ifdef ENGINE_DLL +typedef CUtlVector > CDispLightmapSamplePositions; +#else +typedef CUtlVector CDispLightmapSamplePositions; +#endif + +class ISpatialQuery; +struct Ray_t; +class Vector2D; +struct portal_t; +class CUtlBuffer; +class IZip; + +// this is only true in vrad +extern bool g_bHDR; + +// default width/height of luxels in world units. +#define DEFAULT_LUXEL_SIZE ( 16.0f ) + +#define SINGLE_BRUSH_MAP (MAX_BRUSH_LIGHTMAP_DIM_INCLUDING_BORDER*MAX_BRUSH_LIGHTMAP_DIM_INCLUDING_BORDER) +#define SINGLEMAP (MAX_LIGHTMAP_DIM_INCLUDING_BORDER*MAX_LIGHTMAP_DIM_INCLUDING_BORDER) + +struct entity_t +{ + Vector origin; + int firstbrush; + int numbrushes; + epair_t *epairs; + + // only valid for func_areaportals + int areaportalnum; + int portalareas[2]; + portal_t *m_pPortalsLeadingIntoAreas[2]; // portals leading into portalareas +}; + +extern int num_entities; +extern entity_t entities[MAX_MAP_ENTITIES]; + +extern int nummodels; +extern dmodel_t dmodels[MAX_MAP_MODELS]; + +extern int visdatasize; +extern byte dvisdata[MAX_MAP_VISIBILITY]; +extern dvis_t *dvis; + +extern CUtlVector dlightdataHDR; +extern CUtlVector dlightdataLDR; +extern CUtlVector *pdlightdata; +extern CUtlVector dentdata; + +extern int numleafs; +#if !defined( _X360 ) +extern dleaf_t dleafs[MAX_MAP_LEAFS]; +#else +extern dleaf_t *dleafs; +#endif +extern CUtlVector *g_pLeafAmbientLighting; +extern CUtlVector *g_pLeafAmbientIndex; +extern unsigned short g_LeafMinDistToWater[MAX_MAP_LEAFS]; + +extern int numplanes; +extern dplane_t dplanes[MAX_MAP_PLANES]; + +extern int numvertexes; +extern dvertex_t dvertexes[MAX_MAP_VERTS]; + +extern int g_numvertnormalindices; // dfaces reference these. These index g_vertnormals. +extern unsigned short g_vertnormalindices[MAX_MAP_VERTNORMALS]; + +extern int g_numvertnormals; +extern Vector g_vertnormals[MAX_MAP_VERTNORMALS]; + +extern int numnodes; +extern dnode_t dnodes[MAX_MAP_NODES]; + +extern CUtlVector texinfo; + +extern int numtexdata; +extern dtexdata_t dtexdata[MAX_MAP_TEXDATA]; + +// displacement map .bsp file info +extern CUtlVector g_dispinfo; +extern CUtlVector g_DispVerts; +extern CUtlVector g_DispTris; +extern CDispLightmapSamplePositions g_DispLightmapSamplePositions; // LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS + +extern int numorigfaces; +extern dface_t dorigfaces[MAX_MAP_FACES]; + +extern int g_numprimitives; +extern dprimitive_t g_primitives[MAX_MAP_PRIMITIVES]; + +extern int g_numprimverts; +extern dprimvert_t g_primverts[MAX_MAP_PRIMVERTS]; + +extern int g_numprimindices; +extern unsigned short g_primindices[MAX_MAP_PRIMINDICES]; + +extern int numfaces; +extern dface_t dfaces[MAX_MAP_FACES]; + +extern int numfaceids; +extern CUtlVector dfaceids; + +extern int numfaces_hdr; +extern dface_t dfaces_hdr[MAX_MAP_FACES]; + +extern int numedges; +extern dedge_t dedges[MAX_MAP_EDGES]; + +extern int numleaffaces; +extern unsigned short dleaffaces[MAX_MAP_LEAFFACES]; + +extern int numleafbrushes; +extern unsigned short dleafbrushes[MAX_MAP_LEAFBRUSHES]; + +extern int numsurfedges; +extern int dsurfedges[MAX_MAP_SURFEDGES]; + +extern int numareas; +extern darea_t dareas[MAX_MAP_AREAS]; + +extern int numareaportals; +extern dareaportal_t dareaportals[MAX_MAP_AREAPORTALS]; + +extern int numbrushes; +extern dbrush_t dbrushes[MAX_MAP_BRUSHES]; + +extern int numbrushsides; +extern dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; + +extern int *pNumworldlights; +extern dworldlight_t *dworldlights; + +extern Vector g_ClipPortalVerts[MAX_MAP_PORTALVERTS]; +extern int g_nClipPortalVerts; + +extern dcubemapsample_t g_CubemapSamples[MAX_MAP_CUBEMAPSAMPLES]; +extern int g_nCubemapSamples; + +extern int g_nOverlayCount; +extern doverlay_t g_Overlays[MAX_MAP_OVERLAYS]; +extern doverlayfade_t g_OverlayFades[MAX_MAP_OVERLAYS]; // Parallel array of fade info in a separate lump to avoid breaking backwards compat + +extern int g_nWaterOverlayCount; +extern dwateroverlay_t g_WaterOverlays[MAX_MAP_WATEROVERLAYS]; + +extern CUtlVector g_TexDataStringData; +extern CUtlVector g_TexDataStringTable; + +extern int numleafwaterdata; +extern dleafwaterdata_t dleafwaterdata[MAX_MAP_LEAFWATERDATA]; + +extern CUtlVector g_FaceMacroTextureInfos; + +extern CUtlVector g_OccluderData; +extern CUtlVector g_OccluderPolyData; +extern CUtlVector g_OccluderVertexIndices; + +// level flags - see LVLFLAGS_xxx in bspfile.h +extern uint32 g_LevelFlags; + +// physics collision data +extern byte *g_pPhysCollide; +extern int g_PhysCollideSize; +extern byte *g_pPhysDisp; +extern int g_PhysDispSize; + +// Embedded pack/pak file +IZip *GetPakFile( void ); +IZip *GetSwapPakFile( void ); +void ClearPakFile( IZip *pak ); +void AddFileToPak( IZip *pak, const char *pRelativeName, const char *fullpath ); +void AddBufferToPak( IZip *pak, const char *pRelativeName, void *data, int length, bool bTextMode ); +bool FileExistsInPak( IZip *pak, const char *pRelativeName ); +bool ReadFileFromPak( IZip *pak, const char *pRelativeName, bool bTextMode, CUtlBuffer &buf ); +void RemoveFileFromPak( IZip *pak, const char *pRelativeName ); +int GetNextFilename( IZip *pak, int id, char *pBuffer, int bufferSize, int &fileSize ); +void ForceAlignment( IZip *pak, bool bAlign, bool bCompatibleFormat, unsigned int alignmentSize ); + +typedef bool (*CompressFunc_t)( CUtlBuffer &inputBuffer, CUtlBuffer &outputBuffer ); +typedef bool (*VTFConvertFunc_t)( const char *pDebugName, CUtlBuffer &sourceBuf, CUtlBuffer &targetBuf, CompressFunc_t pCompressFunc ); +typedef bool (*VHVFixupFunc_t)( const char *pVhvFilename, const char *pModelName, CUtlBuffer &sourceBuf, CUtlBuffer &targetBuf ); + +//----------------------------------------------------------------------------- +// Game lump memory storage +//----------------------------------------------------------------------------- +// NOTE: This is not optimal at all; since I expect client lumps to +// not be accessed all that often. + +struct GameLump_t +{ + GameLumpId_t m_Id; + unsigned short m_Flags; + unsigned short m_Version; + CUtlMemory< unsigned char > m_Memory; +}; + +//----------------------------------------------------------------------------- +// Handle to a game lump +//----------------------------------------------------------------------------- +typedef unsigned short GameLumpHandle_t; + +class CGameLump +{ +public: + //----------------------------------------------------------------------------- + // Convert four-CC code to a handle + back + //----------------------------------------------------------------------------- + GameLumpHandle_t GetGameLumpHandle( GameLumpId_t id ); + GameLumpId_t GetGameLumpId( GameLumpHandle_t handle ); + int GetGameLumpFlags( GameLumpHandle_t handle ); + int GetGameLumpVersion( GameLumpHandle_t handle ); + void ComputeGameLumpSizeAndCount( int& size, int& clumpCount ); + void ParseGameLump( dheader_t* pHeader ); + void SwapGameLump( GameLumpId_t id, int version, byte *dest, byte *src, int size ); + + + //----------------------------------------------------------------------------- + // Game lump accessor methods + //----------------------------------------------------------------------------- + void* GetGameLump( GameLumpHandle_t handle ); + int GameLumpSize( GameLumpHandle_t handle ); + + + //----------------------------------------------------------------------------- + // Game lump iteration methods + //----------------------------------------------------------------------------- + GameLumpHandle_t FirstGameLump(); + GameLumpHandle_t NextGameLump( GameLumpHandle_t handle ); + GameLumpHandle_t InvalidGameLump(); + + + //----------------------------------------------------------------------------- + // Game lump creation/destruction method + //----------------------------------------------------------------------------- + GameLumpHandle_t CreateGameLump( GameLumpId_t id, int size, int flags, int version ); + void DestroyGameLump( GameLumpHandle_t handle ); + void DestroyAllGameLumps(); + +private: + CUtlLinkedList< GameLump_t, GameLumpHandle_t > m_GameLumps; +}; + +extern CGameLump g_GameLumps; +extern CByteswap g_Swap; + +//----------------------------------------------------------------------------- +// Helper for the bspzip tool +//----------------------------------------------------------------------------- +void ExtractZipFileFromBSP( char *pBSPFileName, char *pZipFileName ); + + +//----------------------------------------------------------------------------- +// String table methods +//----------------------------------------------------------------------------- +const char * TexDataStringTable_GetString( int stringID ); +int TexDataStringTable_AddOrFindString( const char *pString ); + +void DecompressVis (byte *in, byte *decompressed); +int CompressVis (byte *vis, byte *dest); + +void OpenBSPFile( const char *filename ); +void CloseBSPFile(void); +void LoadBSPFile( const char *filename ); +void LoadBSPFile_FileSystemOnly( const char *filename ); +void LoadBSPFileTexinfo( const char *filename ); +void WriteBSPFile( const char *filename, char *pUnused = NULL ); +void PrintBSPFileSizes(void); +void PrintBSPPackDirectory(void); +void ReleasePakFileLumps(void); +bool SwapBSPFile( const char *filename, const char *swapFilename, bool bSwapOnLoad, VTFConvertFunc_t pVTFConvertFunc, VHVFixupFunc_t pVHVFixupFunc, CompressFunc_t pCompressFunc ); +bool GetPakFileLump( const char *pBSPFilename, void **pPakData, int *pPakSize ); +bool SetPakFileLump( const char *pBSPFilename, const char *pNewFilename, void *pPakData, int pakSize ); +void WriteLumpToFile( char *filename, int lump ); +void WriteLumpToFile( char *filename, int lump, int nLumpVersion, void *pBuffer, size_t nBufLen ); +bool GetBSPDependants( const char *pBSPFilename, CUtlVector< CUtlString > *pList ); +void UnloadBSPFile(); + +void ParseEntities (void); +void UnparseEntities (void); +void PrintEntity (entity_t *ent); + +void SetKeyValue (entity_t *ent, const char *key, const char *value); +char *ValueForKey (entity_t *ent, char *key); +// will return "" if not present +int IntForKey (entity_t *ent, char *key); +int IntForKeyWithDefault(entity_t *ent, char *key, int nDefault ); +vec_t FloatForKey (entity_t *ent, char *key); +vec_t FloatForKeyWithDefault (entity_t *ent, char *key, float default_value); +void GetVectorForKey (entity_t *ent, char *key, Vector& vec); +void GetVector2DForKey (entity_t *ent, char *key, Vector2D& vec); +void GetAnglesForKey (entity_t *ent, char *key, QAngle& vec); +epair_t *ParseEpair (void); +void StripTrailing (char *e); + +// Build a list of the face's vertices (index into dvertexes). +// points must be able to hold pFace->numedges indices. +void BuildFaceCalcWindingData( dface_t *pFace, int *points ); + +// Convert a tristrip to a trilist. +// Removes degenerates. +// Fills in pTriListIndices and pnTriListIndices. +// You must free pTriListIndices with delete[]. +void TriStripToTriList( + unsigned short const *pTriStripIndices, + int nTriStripIndices, + unsigned short **pTriListIndices, + int *pnTriListIndices ); + +// Calculates the lightmap coordinates at a given set of positions given the +// lightmap basis information. +void CalcTextureCoordsAtPoints( + float const texelsPerWorldUnits[2][4], + int const subtractOffset[2], + Vector const *pPoints, + int const nPoints, + Vector2D *pCoords ); + +// Figure out lightmap extents on all (lit) faces. +void UpdateAllFaceLightmapExtents(); + + +//----------------------------------------------------------------------------- +// Gets at an interface for the tree for enumeration of leaves in volumes. +//----------------------------------------------------------------------------- +ISpatialQuery* ToolBSPTree(); + +class IBSPNodeEnumerator +{ +public: + // call back with a node and a context + virtual bool EnumerateNode( int node, Ray_t const& ray, float f, int context ) = 0; + + // call back with a leaf and a context + virtual bool EnumerateLeaf( int leaf, Ray_t const& ray, float start, float end, int context ) = 0; +}; + +//----------------------------------------------------------------------------- +// Enumerates nodes + leafs in front to back order... +//----------------------------------------------------------------------------- +bool EnumerateNodesAlongRay( Ray_t const& ray, IBSPNodeEnumerator* pEnum, int context ); + + +//----------------------------------------------------------------------------- +// Helps us find all leaves associated with a particular cluster +//----------------------------------------------------------------------------- +struct clusterlist_t +{ + int leafCount; + CUtlVector leafs; +}; + +extern CUtlVector g_ClusterLeaves; + +// Call this to build the mapping from cluster to leaves +void BuildClusterTable( ); + +void GetPlatformMapPath( const char *pMapPath, char *pPlatformMapPath, int dxlevel, int maxLength ); + +void SetHDRMode( bool bHDR ); + +// ----------------------------------------------------------------------------- // +// Helper accessors for the various structures. +// ----------------------------------------------------------------------------- // + +inline ColorRGBExp32* dface_AvgLightColor( dface_t *pFace, int nLightStyleIndex ) +{ + return (ColorRGBExp32*)&(*pdlightdata)[pFace->lightofs - (nLightStyleIndex+1) * 4]; +} + +inline const char* TexInfo_TexName( int iTexInfo ) +{ + return TexDataStringTable_GetString( dtexdata[texinfo[iTexInfo].texdata].nameStringTableID ); +} + + +#endif // BSPLIB_H diff --git a/mp/src/utils/common/cmdlib.cpp b/mp/src/utils/common/cmdlib.cpp index a6962380..a5f9b747 100644 --- a/mp/src/utils/common/cmdlib.cpp +++ b/mp/src/utils/common/cmdlib.cpp @@ -1,1007 +1,1007 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// ----------------------- -// cmdlib.c -// ----------------------- -#include "tier0/platform.h" -#ifdef IS_WINDOWS_PC -#include -#endif -#include "cmdlib.h" -#include -#include -#include "tier1/strtools.h" -#ifdef _WIN32 -#include -#endif -#include "utlvector.h" -#include "filesystem_helpers.h" -#include "utllinkedlist.h" -#include "tier0/icommandline.h" -#include "KeyValues.h" -#include "filesystem_tools.h" - -#if defined( MPI ) - - #include "vmpi.h" - #include "vmpi_tools_shared.h" - -#endif - - -#if defined( _WIN32 ) || defined( WIN32 ) -#include -#endif - -#if defined( _X360 ) -#include "xbox/xbox_win32stubs.h" -#endif - -// set these before calling CheckParm -int myargc; -char **myargv; - -char com_token[1024]; - -qboolean archive; -char archivedir[1024]; - -FileHandle_t g_pLogFile = 0; - -CUtlLinkedList g_CleanupFunctions; -CUtlLinkedList g_ExtraSpewHooks; - -bool g_bStopOnExit = false; -void (*g_ExtraSpewHook)(const char*) = NULL; - -#if defined( _WIN32 ) || defined( WIN32 ) - -void CmdLib_FPrintf( FileHandle_t hFile, const char *pFormat, ... ) -{ - static CUtlVector buf; - if ( buf.Count() == 0 ) - buf.SetCount( 1024 ); - - va_list marker; - va_start( marker, pFormat ); - - while ( 1 ) - { - int ret = Q_vsnprintf( buf.Base(), buf.Count(), pFormat, marker ); - if ( ret >= 0 ) - { - // Write the string. - g_pFileSystem->Write( buf.Base(), ret, hFile ); - - break; - } - else - { - // Make the buffer larger. - int newSize = buf.Count() * 2; - buf.SetCount( newSize ); - if ( buf.Count() != newSize ) - { - Error( "CmdLib_FPrintf: can't allocate space for text." ); - } - } - } - - va_end( marker ); -} - -char* CmdLib_FGets( char *pOut, int outSize, FileHandle_t hFile ) -{ - int iCur=0; - for ( ; iCur < (outSize-1); iCur++ ) - { - char c; - if ( !g_pFileSystem->Read( &c, 1, hFile ) ) - { - if ( iCur == 0 ) - return NULL; - else - break; - } - - pOut[iCur] = c; - if ( c == '\n' ) - break; - - if ( c == EOF ) - { - if ( iCur == 0 ) - return NULL; - else - break; - } - } - - pOut[iCur] = 0; - return pOut; -} - -#if !defined( _X360 ) -#include -#endif - -// This pauses before exiting if they use -StopOnExit. Useful for debugging. -class CExitStopper -{ -public: - ~CExitStopper() - { - if ( g_bStopOnExit ) - { - Warning( "\nPress any key to quit.\n" ); - getch(); - } - } -} g_ExitStopper; - - -static unsigned short g_InitialColor = 0xFFFF; -static unsigned short g_LastColor = 0xFFFF; -static unsigned short g_BadColor = 0xFFFF; -static WORD g_BackgroundFlags = 0xFFFF; -static void GetInitialColors( ) -{ -#if !defined( _X360 ) - // Get the old background attributes. - CONSOLE_SCREEN_BUFFER_INFO oldInfo; - GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &oldInfo ); - g_InitialColor = g_LastColor = oldInfo.wAttributes & (FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY); - g_BackgroundFlags = oldInfo.wAttributes & (BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE|BACKGROUND_INTENSITY); - - g_BadColor = 0; - if (g_BackgroundFlags & BACKGROUND_RED) - g_BadColor |= FOREGROUND_RED; - if (g_BackgroundFlags & BACKGROUND_GREEN) - g_BadColor |= FOREGROUND_GREEN; - if (g_BackgroundFlags & BACKGROUND_BLUE) - g_BadColor |= FOREGROUND_BLUE; - if (g_BackgroundFlags & BACKGROUND_INTENSITY) - g_BadColor |= FOREGROUND_INTENSITY; -#endif -} - -WORD SetConsoleTextColor( int red, int green, int blue, int intensity ) -{ - WORD ret = g_LastColor; -#if !defined( _X360 ) - - g_LastColor = 0; - if( red ) g_LastColor |= FOREGROUND_RED; - if( green ) g_LastColor |= FOREGROUND_GREEN; - if( blue ) g_LastColor |= FOREGROUND_BLUE; - if( intensity ) g_LastColor |= FOREGROUND_INTENSITY; - - // Just use the initial color if there's a match... - if (g_LastColor == g_BadColor) - g_LastColor = g_InitialColor; - - SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), g_LastColor | g_BackgroundFlags ); -#endif - return ret; -} - -void RestoreConsoleTextColor( WORD color ) -{ -#if !defined( _X360 ) - SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), color | g_BackgroundFlags ); - g_LastColor = color; -#endif -} - - -#if defined( CMDLIB_NODBGLIB ) - -// This can go away when everything is in bin. -void Error( char const *pMsg, ... ) -{ - va_list marker; - va_start( marker, pMsg ); - vprintf( pMsg, marker ); - va_end( marker ); - - exit( -1 ); -} - -#else - -CRITICAL_SECTION g_SpewCS; -bool g_bSpewCSInitted = false; -bool g_bSuppressPrintfOutput = false; - -SpewRetval_t CmdLib_SpewOutputFunc( SpewType_t type, char const *pMsg ) -{ - // Hopefully two threads won't call this simultaneously right at the start! - if ( !g_bSpewCSInitted ) - { - InitializeCriticalSection( &g_SpewCS ); - g_bSpewCSInitted = true; - } - - WORD old; - SpewRetval_t retVal; - - EnterCriticalSection( &g_SpewCS ); - { - if (( type == SPEW_MESSAGE ) || (type == SPEW_LOG )) - { - Color c = *GetSpewOutputColor(); - if ( c.r() != 255 || c.g() != 255 || c.b() != 255 ) - { - // custom color - old = SetConsoleTextColor( c.r(), c.g(), c.b(), c.a() ); - } - else - { - old = SetConsoleTextColor( 1, 1, 1, 0 ); - } - retVal = SPEW_CONTINUE; - } - else if( type == SPEW_WARNING ) - { - old = SetConsoleTextColor( 1, 1, 0, 1 ); - retVal = SPEW_CONTINUE; - } - else if( type == SPEW_ASSERT ) - { - old = SetConsoleTextColor( 1, 0, 0, 1 ); - retVal = SPEW_DEBUGGER; - -#ifdef MPI - // VMPI workers don't want to bring up dialogs and suchlike. - // They need to have a special function installed to handle - // the exceptions and write the minidumps. - // Install the function after VMPI_Init with a call: - // SetupToolsMinidumpHandler( VMPI_ExceptionFilter ); - if ( g_bUseMPI && !g_bMPIMaster && !Plat_IsInDebugSession() ) - { - // Generating an exception and letting the - // installed handler handle it - ::RaiseException - ( - 0, // dwExceptionCode - EXCEPTION_NONCONTINUABLE, // dwExceptionFlags - 0, // nNumberOfArguments, - NULL // const ULONG_PTR* lpArguments - ); - - // Never get here (non-continuable exception) - - VMPI_HandleCrash( pMsg, NULL, true ); - exit( 0 ); - } -#endif - } - else if( type == SPEW_ERROR ) - { - old = SetConsoleTextColor( 1, 0, 0, 1 ); - retVal = SPEW_ABORT; // doesn't matter.. we exit below so we can return an errorlevel (which dbg.dll doesn't do). - } - else - { - old = SetConsoleTextColor( 1, 1, 1, 1 ); - retVal = SPEW_CONTINUE; - } - - if ( !g_bSuppressPrintfOutput || type == SPEW_ERROR ) - printf( "%s", pMsg ); - - OutputDebugString( pMsg ); - - if ( type == SPEW_ERROR ) - { - printf( "\n" ); - OutputDebugString( "\n" ); - } - - if( g_pLogFile ) - { - CmdLib_FPrintf( g_pLogFile, "%s", pMsg ); - g_pFileSystem->Flush( g_pLogFile ); - } - - // Dispatch to other spew hooks. - FOR_EACH_LL( g_ExtraSpewHooks, i ) - g_ExtraSpewHooks[i]( pMsg ); - - RestoreConsoleTextColor( old ); - } - LeaveCriticalSection( &g_SpewCS ); - - if ( type == SPEW_ERROR ) - { - CmdLib_Exit( 1 ); - } - - return retVal; -} - - -void InstallSpewFunction() -{ - setvbuf( stdout, NULL, _IONBF, 0 ); - setvbuf( stderr, NULL, _IONBF, 0 ); - - SpewOutputFunc( CmdLib_SpewOutputFunc ); - GetInitialColors(); -} - - -void InstallExtraSpewHook( SpewHookFn pFn ) -{ - g_ExtraSpewHooks.AddToTail( pFn ); -} - -#if 0 -void CmdLib_AllocError( unsigned long size ) -{ - Error( "Error trying to allocate %d bytes.\n", size ); -} - - -int CmdLib_NewHandler( size_t size ) -{ - CmdLib_AllocError( size ); - return 0; -} -#endif - -void InstallAllocationFunctions() -{ -// _set_new_mode( 1 ); // so if malloc() fails, we exit. -// _set_new_handler( CmdLib_NewHandler ); -} - -void SetSpewFunctionLogFile( char const *pFilename ) -{ - Assert( (!g_pLogFile) ); - g_pLogFile = g_pFileSystem->Open( pFilename, "a" ); - - Assert( g_pLogFile ); - if (!g_pLogFile) - Error("Can't create LogFile:\"%s\"\n", pFilename ); - - CmdLib_FPrintf( g_pLogFile, "\n\n\n" ); -} - - -void CloseSpewFunctionLogFile() -{ - if ( g_pFileSystem && g_pLogFile ) - { - g_pFileSystem->Close( g_pLogFile ); - g_pLogFile = FILESYSTEM_INVALID_HANDLE; - } -} - - -void CmdLib_AtCleanup( CleanupFn pFn ) -{ - g_CleanupFunctions.AddToTail( pFn ); -} - - -void CmdLib_Cleanup() -{ - CloseSpewFunctionLogFile(); - - CmdLib_TermFileSystem(); - - FOR_EACH_LL( g_CleanupFunctions, i ) - g_CleanupFunctions[i](); - -#if defined( MPI ) - // Unfortunately, when you call exit(), even if you have things registered with atexit(), - // threads go into a seemingly undefined state where GetExitCodeThread gives STILL_ACTIVE - // and WaitForSingleObject will stall forever on the thread. Because of this, we must cleanup - // everything that uses threads before exiting. - VMPI_Finalize(); -#endif -} - - -void CmdLib_Exit( int exitCode ) -{ - TerminateProcess( GetCurrentProcess(), 1 ); -} - - - -#endif - -#endif - - - - -/* -=================== -ExpandWildcards - -Mimic unix command line expansion -=================== -*/ -#define MAX_EX_ARGC 1024 -int ex_argc; -char *ex_argv[MAX_EX_ARGC]; -#if defined( _WIN32 ) && !defined( _X360 ) -#include "io.h" -void ExpandWildcards (int *argc, char ***argv) -{ - struct _finddata_t fileinfo; - int handle; - int i; - char filename[1024]; - char filebase[1024]; - char *path; - - ex_argc = 0; - for (i=0 ; i<*argc ; i++) - { - path = (*argv)[i]; - if ( path[0] == '-' - || ( !strstr(path, "*") && !strstr(path, "?") ) ) - { - ex_argv[ex_argc++] = path; - continue; - } - - handle = _findfirst (path, &fileinfo); - if (handle == -1) - return; - - Q_ExtractFilePath (path, filebase, sizeof( filebase )); - - do - { - sprintf (filename, "%s%s", filebase, fileinfo.name); - ex_argv[ex_argc++] = copystring (filename); - } while (_findnext( handle, &fileinfo ) != -1); - - _findclose (handle); - } - - *argc = ex_argc; - *argv = ex_argv; -} -#else -void ExpandWildcards (int *argc, char ***argv) -{ -} -#endif - - -// only printf if in verbose mode -qboolean verbose = false; -void qprintf (const char *format, ...) -{ - if (!verbose) - return; - - va_list argptr; - va_start (argptr,format); - - char str[2048]; - Q_vsnprintf( str, sizeof(str), format, argptr ); - -#if defined( CMDLIB_NODBGLIB ) - printf( "%s", str ); -#else - Msg( "%s", str ); -#endif - - va_end (argptr); -} - - -// ---------------------------------------------------------------------------------------------------- // -// Helpers. -// ---------------------------------------------------------------------------------------------------- // - -static void CmdLib_getwd( char *out, int outSize ) -{ -#if defined( _WIN32 ) || defined( WIN32 ) - _getcwd( out, outSize ); - Q_strncat( out, "\\", outSize, COPY_ALL_CHARACTERS ); -#else - getcwd(out, outSize); - strcat(out, "/"); -#endif - Q_FixSlashes( out ); -} - -char *ExpandArg (char *path) -{ - static char full[1024]; - - if (path[0] != '/' && path[0] != '\\' && path[1] != ':') - { - CmdLib_getwd (full, sizeof( full )); - Q_strncat (full, path, sizeof( full ), COPY_ALL_CHARACTERS); - } - else - Q_strncpy (full, path, sizeof( full )); - return full; -} - - -char *ExpandPath (char *path) -{ - static char full[1024]; - if (path[0] == '/' || path[0] == '\\' || path[1] == ':') - return path; - sprintf (full, "%s%s", qdir, path); - return full; -} - - - -char *copystring(const char *s) -{ - char *b; - b = (char *)malloc(strlen(s)+1); - strcpy (b, s); - return b; -} - - -void GetHourMinuteSeconds( int nInputSeconds, int &nHours, int &nMinutes, int &nSeconds ) -{ -} - - -void GetHourMinuteSecondsString( int nInputSeconds, char *pOut, int outLen ) -{ - int nMinutes = nInputSeconds / 60; - int nSeconds = nInputSeconds - nMinutes * 60; - int nHours = nMinutes / 60; - nMinutes -= nHours * 60; - - const char *extra[2] = { "", "s" }; - - if ( nHours > 0 ) - Q_snprintf( pOut, outLen, "%d hour%s, %d minute%s, %d second%s", nHours, extra[nHours != 1], nMinutes, extra[nMinutes != 1], nSeconds, extra[nSeconds != 1] ); - else if ( nMinutes > 0 ) - Q_snprintf( pOut, outLen, "%d minute%s, %d second%s", nMinutes, extra[nMinutes != 1], nSeconds, extra[nSeconds != 1] ); - else - Q_snprintf( pOut, outLen, "%d second%s", nSeconds, extra[nSeconds != 1] ); -} - - -void Q_mkdir (char *path) -{ -#if defined( _WIN32 ) || defined( WIN32 ) - if (_mkdir (path) != -1) - return; -#else - if (mkdir (path, 0777) != -1) - return; -#endif -// if (errno != EEXIST) - Error ("mkdir failed %s\n", path ); -} - -void CmdLib_InitFileSystem( const char *pFilename, int maxMemoryUsage ) -{ - FileSystem_Init( pFilename, maxMemoryUsage ); - if ( !g_pFileSystem ) - Error( "CmdLib_InitFileSystem failed." ); -} - -void CmdLib_TermFileSystem() -{ - FileSystem_Term(); -} - -CreateInterfaceFn CmdLib_GetFileSystemFactory() -{ - return FileSystem_GetFactory(); -} - - -/* -============ -FileTime - -returns -1 if not present -============ -*/ -int FileTime (char *path) -{ - struct stat buf; - - if (stat (path,&buf) == -1) - return -1; - - return buf.st_mtime; -} - - - -/* -============== -COM_Parse - -Parse a token out of a string -============== -*/ -char *COM_Parse (char *data) -{ - return (char*)ParseFile( data, com_token, NULL ); -} - - -/* -============================================================================= - - MISC FUNCTIONS - -============================================================================= -*/ - - -/* -================= -CheckParm - -Checks for the given parameter in the program's command line arguments -Returns the argument number (1 to argc-1) or 0 if not present -================= -*/ -int CheckParm (char *check) -{ - int i; - - for (i = 1;iSize( f ); -} - - -FileHandle_t SafeOpenWrite ( const char *filename ) -{ - FileHandle_t f = g_pFileSystem->Open(filename, "wb"); - - if (!f) - { - //Error ("Error opening %s: %s",filename,strerror(errno)); - // BUGBUG: No way to get equivalent of errno from IFileSystem! - Error ("Error opening %s! (Check for write enable)\n",filename); - } - - return f; -} - -#define MAX_CMDLIB_BASE_PATHS 10 -static char g_pBasePaths[MAX_CMDLIB_BASE_PATHS][MAX_PATH]; -static int g_NumBasePaths = 0; - -void CmdLib_AddBasePath( const char *pPath ) -{ -// printf( "CmdLib_AddBasePath( \"%s\" )\n", pPath ); - if( g_NumBasePaths < MAX_CMDLIB_BASE_PATHS ) - { - Q_strncpy( g_pBasePaths[g_NumBasePaths], pPath, MAX_PATH ); - Q_FixSlashes( g_pBasePaths[g_NumBasePaths] ); - g_NumBasePaths++; - } - else - { - Assert( 0 ); - } -} - -bool CmdLib_HasBasePath( const char *pFileName_, int &pathLength ) -{ - char *pFileName = ( char * )_alloca( strlen( pFileName_ ) + 1 ); - strcpy( pFileName, pFileName_ ); - Q_FixSlashes( pFileName ); - pathLength = 0; - int i; - for( i = 0; i < g_NumBasePaths; i++ ) - { - // see if we can rip the base off of the filename. - if( Q_strncasecmp( g_pBasePaths[i], pFileName, strlen( g_pBasePaths[i] ) ) == 0 ) - { - pathLength = strlen( g_pBasePaths[i] ); - return true; - } - } - return false; -} - -int CmdLib_GetNumBasePaths( void ) -{ - return g_NumBasePaths; -} - -const char *CmdLib_GetBasePath( int i ) -{ - Assert( i >= 0 && i < g_NumBasePaths ); - return g_pBasePaths[i]; -} - - -//----------------------------------------------------------------------------- -// Like ExpandPath but expands the path for each base path like SafeOpenRead -//----------------------------------------------------------------------------- -int CmdLib_ExpandWithBasePaths( CUtlVector< CUtlString > &expandedPathList, const char *pszPath ) -{ - int nPathLength = 0; - - pszPath = ExpandPath( const_cast< char * >( pszPath ) ); // Kind of redundant but it's how CmdLib_HasBasePath needs things - - if ( CmdLib_HasBasePath( pszPath, nPathLength ) ) - { - pszPath = pszPath + nPathLength; - for ( int i = 0; i < CmdLib_GetNumBasePaths(); ++i ) - { - CUtlString &expandedPath = expandedPathList[ expandedPathList.AddToTail( CmdLib_GetBasePath( i ) ) ]; - expandedPath += pszPath; - } - } - else - { - expandedPathList.AddToTail( pszPath ); - } - - return expandedPathList.Count(); -} - - -FileHandle_t SafeOpenRead( const char *filename ) -{ - int pathLength; - FileHandle_t f = 0; - if( CmdLib_HasBasePath( filename, pathLength ) ) - { - filename = filename + pathLength; - int i; - for( i = 0; i < g_NumBasePaths; i++ ) - { - char tmp[MAX_PATH]; - strcpy( tmp, g_pBasePaths[i] ); - strcat( tmp, filename ); - f = g_pFileSystem->Open( tmp, "rb" ); - if( f ) - { - return f; - } - } - Error ("Error opening %s\n",filename ); - return f; - } - else - { - f = g_pFileSystem->Open( filename, "rb" ); - if ( !f ) - Error ("Error opening %s",filename ); - - return f; - } -} - -void SafeRead( FileHandle_t f, void *buffer, int count) -{ - if ( g_pFileSystem->Read (buffer, count, f) != (size_t)count) - Error ("File read failure"); -} - - -void SafeWrite ( FileHandle_t f, void *buffer, int count) -{ - if (g_pFileSystem->Write (buffer, count, f) != (size_t)count) - Error ("File write failure"); -} - - -/* -============== -FileExists -============== -*/ -qboolean FileExists ( const char *filename ) -{ - FileHandle_t hFile = g_pFileSystem->Open( filename, "rb" ); - if ( hFile == FILESYSTEM_INVALID_HANDLE ) - { - return false; - } - else - { - g_pFileSystem->Close( hFile ); - return true; - } -} - -/* -============== -LoadFile -============== -*/ -int LoadFile ( const char *filename, void **bufferptr ) -{ - int length = 0; - void *buffer; - - FileHandle_t f = SafeOpenRead (filename); - if ( FILESYSTEM_INVALID_HANDLE != f ) - { - length = Q_filelength (f); - buffer = malloc (length+1); - ((char *)buffer)[length] = 0; - SafeRead (f, buffer, length); - g_pFileSystem->Close (f); - *bufferptr = buffer; - } - else - { - *bufferptr = NULL; - } - return length; -} - - - -/* -============== -SaveFile -============== -*/ -void SaveFile ( const char *filename, void *buffer, int count ) -{ - FileHandle_t f = SafeOpenWrite (filename); - SafeWrite (f, buffer, count); - g_pFileSystem->Close (f); -} - -/* -==================== -Extract file parts -==================== -*/ -// FIXME: should include the slash, otherwise -// backing to an empty path will be wrong when appending a slash - - - -/* -============== -ParseNum / ParseHex -============== -*/ -int ParseHex (char *hex) -{ - char *str; - int num; - - num = 0; - str = hex; - - while (*str) - { - num <<= 4; - if (*str >= '0' && *str <= '9') - num += *str-'0'; - else if (*str >= 'a' && *str <= 'f') - num += 10 + *str-'a'; - else if (*str >= 'A' && *str <= 'F') - num += 10 + *str-'A'; - else - Error ("Bad hex number: %s",hex); - str++; - } - - return num; -} - - -int ParseNum (char *str) -{ - if (str[0] == '$') - return ParseHex (str+1); - if (str[0] == '0' && str[1] == 'x') - return ParseHex (str+2); - return atol (str); -} - -/* -============ -CreatePath -============ -*/ -void CreatePath (char *path) -{ - char *ofs, c; - - // strip the drive - if (path[1] == ':') - path += 2; - - for (ofs = path+1 ; *ofs ; ofs++) - { - c = *ofs; - if (c == '/' || c == '\\') - { // create the directory - *ofs = 0; - Q_mkdir (path); - *ofs = c; - } - } -} - -//----------------------------------------------------------------------------- -// Creates a path, path may already exist -//----------------------------------------------------------------------------- -#if defined( _WIN32 ) || defined( WIN32 ) -void SafeCreatePath( char *path ) -{ - char *ptr; - - // skip past the drive path, but don't strip - if ( path[1] == ':' ) - { - ptr = strchr( path, '\\' ); - } - else - { - ptr = path; - } - while ( ptr ) - { - ptr = strchr( ptr+1, '\\' ); - if ( ptr ) - { - *ptr = '\0'; - _mkdir( path ); - *ptr = '\\'; - } - } -} -#endif - -/* -============ -QCopyFile - - Used to archive source files -============ -*/ -void QCopyFile (char *from, char *to) -{ - void *buffer; - int length; - - length = LoadFile (from, &buffer); - CreatePath (to); - SaveFile (to, buffer, length); - free (buffer); -} - - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// ----------------------- +// cmdlib.c +// ----------------------- +#include "tier0/platform.h" +#ifdef IS_WINDOWS_PC +#include +#endif +#include "cmdlib.h" +#include +#include +#include "tier1/strtools.h" +#ifdef _WIN32 +#include +#endif +#include "utlvector.h" +#include "filesystem_helpers.h" +#include "utllinkedlist.h" +#include "tier0/icommandline.h" +#include "KeyValues.h" +#include "filesystem_tools.h" + +#if defined( MPI ) + + #include "vmpi.h" + #include "vmpi_tools_shared.h" + +#endif + + +#if defined( _WIN32 ) || defined( WIN32 ) +#include +#endif + +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif + +// set these before calling CheckParm +int myargc; +char **myargv; + +char com_token[1024]; + +qboolean archive; +char archivedir[1024]; + +FileHandle_t g_pLogFile = 0; + +CUtlLinkedList g_CleanupFunctions; +CUtlLinkedList g_ExtraSpewHooks; + +bool g_bStopOnExit = false; +void (*g_ExtraSpewHook)(const char*) = NULL; + +#if defined( _WIN32 ) || defined( WIN32 ) + +void CmdLib_FPrintf( FileHandle_t hFile, const char *pFormat, ... ) +{ + static CUtlVector buf; + if ( buf.Count() == 0 ) + buf.SetCount( 1024 ); + + va_list marker; + va_start( marker, pFormat ); + + while ( 1 ) + { + int ret = Q_vsnprintf( buf.Base(), buf.Count(), pFormat, marker ); + if ( ret >= 0 ) + { + // Write the string. + g_pFileSystem->Write( buf.Base(), ret, hFile ); + + break; + } + else + { + // Make the buffer larger. + int newSize = buf.Count() * 2; + buf.SetCount( newSize ); + if ( buf.Count() != newSize ) + { + Error( "CmdLib_FPrintf: can't allocate space for text." ); + } + } + } + + va_end( marker ); +} + +char* CmdLib_FGets( char *pOut, int outSize, FileHandle_t hFile ) +{ + int iCur=0; + for ( ; iCur < (outSize-1); iCur++ ) + { + char c; + if ( !g_pFileSystem->Read( &c, 1, hFile ) ) + { + if ( iCur == 0 ) + return NULL; + else + break; + } + + pOut[iCur] = c; + if ( c == '\n' ) + break; + + if ( c == EOF ) + { + if ( iCur == 0 ) + return NULL; + else + break; + } + } + + pOut[iCur] = 0; + return pOut; +} + +#if !defined( _X360 ) +#include +#endif + +// This pauses before exiting if they use -StopOnExit. Useful for debugging. +class CExitStopper +{ +public: + ~CExitStopper() + { + if ( g_bStopOnExit ) + { + Warning( "\nPress any key to quit.\n" ); + getch(); + } + } +} g_ExitStopper; + + +static unsigned short g_InitialColor = 0xFFFF; +static unsigned short g_LastColor = 0xFFFF; +static unsigned short g_BadColor = 0xFFFF; +static WORD g_BackgroundFlags = 0xFFFF; +static void GetInitialColors( ) +{ +#if !defined( _X360 ) + // Get the old background attributes. + CONSOLE_SCREEN_BUFFER_INFO oldInfo; + GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &oldInfo ); + g_InitialColor = g_LastColor = oldInfo.wAttributes & (FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY); + g_BackgroundFlags = oldInfo.wAttributes & (BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE|BACKGROUND_INTENSITY); + + g_BadColor = 0; + if (g_BackgroundFlags & BACKGROUND_RED) + g_BadColor |= FOREGROUND_RED; + if (g_BackgroundFlags & BACKGROUND_GREEN) + g_BadColor |= FOREGROUND_GREEN; + if (g_BackgroundFlags & BACKGROUND_BLUE) + g_BadColor |= FOREGROUND_BLUE; + if (g_BackgroundFlags & BACKGROUND_INTENSITY) + g_BadColor |= FOREGROUND_INTENSITY; +#endif +} + +WORD SetConsoleTextColor( int red, int green, int blue, int intensity ) +{ + WORD ret = g_LastColor; +#if !defined( _X360 ) + + g_LastColor = 0; + if( red ) g_LastColor |= FOREGROUND_RED; + if( green ) g_LastColor |= FOREGROUND_GREEN; + if( blue ) g_LastColor |= FOREGROUND_BLUE; + if( intensity ) g_LastColor |= FOREGROUND_INTENSITY; + + // Just use the initial color if there's a match... + if (g_LastColor == g_BadColor) + g_LastColor = g_InitialColor; + + SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), g_LastColor | g_BackgroundFlags ); +#endif + return ret; +} + +void RestoreConsoleTextColor( WORD color ) +{ +#if !defined( _X360 ) + SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), color | g_BackgroundFlags ); + g_LastColor = color; +#endif +} + + +#if defined( CMDLIB_NODBGLIB ) + +// This can go away when everything is in bin. +void Error( char const *pMsg, ... ) +{ + va_list marker; + va_start( marker, pMsg ); + vprintf( pMsg, marker ); + va_end( marker ); + + exit( -1 ); +} + +#else + +CRITICAL_SECTION g_SpewCS; +bool g_bSpewCSInitted = false; +bool g_bSuppressPrintfOutput = false; + +SpewRetval_t CmdLib_SpewOutputFunc( SpewType_t type, char const *pMsg ) +{ + // Hopefully two threads won't call this simultaneously right at the start! + if ( !g_bSpewCSInitted ) + { + InitializeCriticalSection( &g_SpewCS ); + g_bSpewCSInitted = true; + } + + WORD old; + SpewRetval_t retVal; + + EnterCriticalSection( &g_SpewCS ); + { + if (( type == SPEW_MESSAGE ) || (type == SPEW_LOG )) + { + Color c = *GetSpewOutputColor(); + if ( c.r() != 255 || c.g() != 255 || c.b() != 255 ) + { + // custom color + old = SetConsoleTextColor( c.r(), c.g(), c.b(), c.a() ); + } + else + { + old = SetConsoleTextColor( 1, 1, 1, 0 ); + } + retVal = SPEW_CONTINUE; + } + else if( type == SPEW_WARNING ) + { + old = SetConsoleTextColor( 1, 1, 0, 1 ); + retVal = SPEW_CONTINUE; + } + else if( type == SPEW_ASSERT ) + { + old = SetConsoleTextColor( 1, 0, 0, 1 ); + retVal = SPEW_DEBUGGER; + +#ifdef MPI + // VMPI workers don't want to bring up dialogs and suchlike. + // They need to have a special function installed to handle + // the exceptions and write the minidumps. + // Install the function after VMPI_Init with a call: + // SetupToolsMinidumpHandler( VMPI_ExceptionFilter ); + if ( g_bUseMPI && !g_bMPIMaster && !Plat_IsInDebugSession() ) + { + // Generating an exception and letting the + // installed handler handle it + ::RaiseException + ( + 0, // dwExceptionCode + EXCEPTION_NONCONTINUABLE, // dwExceptionFlags + 0, // nNumberOfArguments, + NULL // const ULONG_PTR* lpArguments + ); + + // Never get here (non-continuable exception) + + VMPI_HandleCrash( pMsg, NULL, true ); + exit( 0 ); + } +#endif + } + else if( type == SPEW_ERROR ) + { + old = SetConsoleTextColor( 1, 0, 0, 1 ); + retVal = SPEW_ABORT; // doesn't matter.. we exit below so we can return an errorlevel (which dbg.dll doesn't do). + } + else + { + old = SetConsoleTextColor( 1, 1, 1, 1 ); + retVal = SPEW_CONTINUE; + } + + if ( !g_bSuppressPrintfOutput || type == SPEW_ERROR ) + printf( "%s", pMsg ); + + OutputDebugString( pMsg ); + + if ( type == SPEW_ERROR ) + { + printf( "\n" ); + OutputDebugString( "\n" ); + } + + if( g_pLogFile ) + { + CmdLib_FPrintf( g_pLogFile, "%s", pMsg ); + g_pFileSystem->Flush( g_pLogFile ); + } + + // Dispatch to other spew hooks. + FOR_EACH_LL( g_ExtraSpewHooks, i ) + g_ExtraSpewHooks[i]( pMsg ); + + RestoreConsoleTextColor( old ); + } + LeaveCriticalSection( &g_SpewCS ); + + if ( type == SPEW_ERROR ) + { + CmdLib_Exit( 1 ); + } + + return retVal; +} + + +void InstallSpewFunction() +{ + setvbuf( stdout, NULL, _IONBF, 0 ); + setvbuf( stderr, NULL, _IONBF, 0 ); + + SpewOutputFunc( CmdLib_SpewOutputFunc ); + GetInitialColors(); +} + + +void InstallExtraSpewHook( SpewHookFn pFn ) +{ + g_ExtraSpewHooks.AddToTail( pFn ); +} + +#if 0 +void CmdLib_AllocError( unsigned long size ) +{ + Error( "Error trying to allocate %d bytes.\n", size ); +} + + +int CmdLib_NewHandler( size_t size ) +{ + CmdLib_AllocError( size ); + return 0; +} +#endif + +void InstallAllocationFunctions() +{ +// _set_new_mode( 1 ); // so if malloc() fails, we exit. +// _set_new_handler( CmdLib_NewHandler ); +} + +void SetSpewFunctionLogFile( char const *pFilename ) +{ + Assert( (!g_pLogFile) ); + g_pLogFile = g_pFileSystem->Open( pFilename, "a" ); + + Assert( g_pLogFile ); + if (!g_pLogFile) + Error("Can't create LogFile:\"%s\"\n", pFilename ); + + CmdLib_FPrintf( g_pLogFile, "\n\n\n" ); +} + + +void CloseSpewFunctionLogFile() +{ + if ( g_pFileSystem && g_pLogFile ) + { + g_pFileSystem->Close( g_pLogFile ); + g_pLogFile = FILESYSTEM_INVALID_HANDLE; + } +} + + +void CmdLib_AtCleanup( CleanupFn pFn ) +{ + g_CleanupFunctions.AddToTail( pFn ); +} + + +void CmdLib_Cleanup() +{ + CloseSpewFunctionLogFile(); + + CmdLib_TermFileSystem(); + + FOR_EACH_LL( g_CleanupFunctions, i ) + g_CleanupFunctions[i](); + +#if defined( MPI ) + // Unfortunately, when you call exit(), even if you have things registered with atexit(), + // threads go into a seemingly undefined state where GetExitCodeThread gives STILL_ACTIVE + // and WaitForSingleObject will stall forever on the thread. Because of this, we must cleanup + // everything that uses threads before exiting. + VMPI_Finalize(); +#endif +} + + +void CmdLib_Exit( int exitCode ) +{ + TerminateProcess( GetCurrentProcess(), 1 ); +} + + + +#endif + +#endif + + + + +/* +=================== +ExpandWildcards + +Mimic unix command line expansion +=================== +*/ +#define MAX_EX_ARGC 1024 +int ex_argc; +char *ex_argv[MAX_EX_ARGC]; +#if defined( _WIN32 ) && !defined( _X360 ) +#include "io.h" +void ExpandWildcards (int *argc, char ***argv) +{ + struct _finddata_t fileinfo; + int handle; + int i; + char filename[1024]; + char filebase[1024]; + char *path; + + ex_argc = 0; + for (i=0 ; i<*argc ; i++) + { + path = (*argv)[i]; + if ( path[0] == '-' + || ( !strstr(path, "*") && !strstr(path, "?") ) ) + { + ex_argv[ex_argc++] = path; + continue; + } + + handle = _findfirst (path, &fileinfo); + if (handle == -1) + return; + + Q_ExtractFilePath (path, filebase, sizeof( filebase )); + + do + { + sprintf (filename, "%s%s", filebase, fileinfo.name); + ex_argv[ex_argc++] = copystring (filename); + } while (_findnext( handle, &fileinfo ) != -1); + + _findclose (handle); + } + + *argc = ex_argc; + *argv = ex_argv; +} +#else +void ExpandWildcards (int *argc, char ***argv) +{ +} +#endif + + +// only printf if in verbose mode +qboolean verbose = false; +void qprintf (const char *format, ...) +{ + if (!verbose) + return; + + va_list argptr; + va_start (argptr,format); + + char str[2048]; + Q_vsnprintf( str, sizeof(str), format, argptr ); + +#if defined( CMDLIB_NODBGLIB ) + printf( "%s", str ); +#else + Msg( "%s", str ); +#endif + + va_end (argptr); +} + + +// ---------------------------------------------------------------------------------------------------- // +// Helpers. +// ---------------------------------------------------------------------------------------------------- // + +static void CmdLib_getwd( char *out, int outSize ) +{ +#if defined( _WIN32 ) || defined( WIN32 ) + _getcwd( out, outSize ); + Q_strncat( out, "\\", outSize, COPY_ALL_CHARACTERS ); +#else + getcwd(out, outSize); + strcat(out, "/"); +#endif + Q_FixSlashes( out ); +} + +char *ExpandArg (char *path) +{ + static char full[1024]; + + if (path[0] != '/' && path[0] != '\\' && path[1] != ':') + { + CmdLib_getwd (full, sizeof( full )); + Q_strncat (full, path, sizeof( full ), COPY_ALL_CHARACTERS); + } + else + Q_strncpy (full, path, sizeof( full )); + return full; +} + + +char *ExpandPath (char *path) +{ + static char full[1024]; + if (path[0] == '/' || path[0] == '\\' || path[1] == ':') + return path; + sprintf (full, "%s%s", qdir, path); + return full; +} + + + +char *copystring(const char *s) +{ + char *b; + b = (char *)malloc(strlen(s)+1); + strcpy (b, s); + return b; +} + + +void GetHourMinuteSeconds( int nInputSeconds, int &nHours, int &nMinutes, int &nSeconds ) +{ +} + + +void GetHourMinuteSecondsString( int nInputSeconds, char *pOut, int outLen ) +{ + int nMinutes = nInputSeconds / 60; + int nSeconds = nInputSeconds - nMinutes * 60; + int nHours = nMinutes / 60; + nMinutes -= nHours * 60; + + const char *extra[2] = { "", "s" }; + + if ( nHours > 0 ) + Q_snprintf( pOut, outLen, "%d hour%s, %d minute%s, %d second%s", nHours, extra[nHours != 1], nMinutes, extra[nMinutes != 1], nSeconds, extra[nSeconds != 1] ); + else if ( nMinutes > 0 ) + Q_snprintf( pOut, outLen, "%d minute%s, %d second%s", nMinutes, extra[nMinutes != 1], nSeconds, extra[nSeconds != 1] ); + else + Q_snprintf( pOut, outLen, "%d second%s", nSeconds, extra[nSeconds != 1] ); +} + + +void Q_mkdir (char *path) +{ +#if defined( _WIN32 ) || defined( WIN32 ) + if (_mkdir (path) != -1) + return; +#else + if (mkdir (path, 0777) != -1) + return; +#endif +// if (errno != EEXIST) + Error ("mkdir failed %s\n", path ); +} + +void CmdLib_InitFileSystem( const char *pFilename, int maxMemoryUsage ) +{ + FileSystem_Init( pFilename, maxMemoryUsage ); + if ( !g_pFileSystem ) + Error( "CmdLib_InitFileSystem failed." ); +} + +void CmdLib_TermFileSystem() +{ + FileSystem_Term(); +} + +CreateInterfaceFn CmdLib_GetFileSystemFactory() +{ + return FileSystem_GetFactory(); +} + + +/* +============ +FileTime + +returns -1 if not present +============ +*/ +int FileTime (char *path) +{ + struct stat buf; + + if (stat (path,&buf) == -1) + return -1; + + return buf.st_mtime; +} + + + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char *data) +{ + return (char*)ParseFile( data, com_token, NULL ); +} + + +/* +============================================================================= + + MISC FUNCTIONS + +============================================================================= +*/ + + +/* +================= +CheckParm + +Checks for the given parameter in the program's command line arguments +Returns the argument number (1 to argc-1) or 0 if not present +================= +*/ +int CheckParm (char *check) +{ + int i; + + for (i = 1;iSize( f ); +} + + +FileHandle_t SafeOpenWrite ( const char *filename ) +{ + FileHandle_t f = g_pFileSystem->Open(filename, "wb"); + + if (!f) + { + //Error ("Error opening %s: %s",filename,strerror(errno)); + // BUGBUG: No way to get equivalent of errno from IFileSystem! + Error ("Error opening %s! (Check for write enable)\n",filename); + } + + return f; +} + +#define MAX_CMDLIB_BASE_PATHS 10 +static char g_pBasePaths[MAX_CMDLIB_BASE_PATHS][MAX_PATH]; +static int g_NumBasePaths = 0; + +void CmdLib_AddBasePath( const char *pPath ) +{ +// printf( "CmdLib_AddBasePath( \"%s\" )\n", pPath ); + if( g_NumBasePaths < MAX_CMDLIB_BASE_PATHS ) + { + Q_strncpy( g_pBasePaths[g_NumBasePaths], pPath, MAX_PATH ); + Q_FixSlashes( g_pBasePaths[g_NumBasePaths] ); + g_NumBasePaths++; + } + else + { + Assert( 0 ); + } +} + +bool CmdLib_HasBasePath( const char *pFileName_, int &pathLength ) +{ + char *pFileName = ( char * )_alloca( strlen( pFileName_ ) + 1 ); + strcpy( pFileName, pFileName_ ); + Q_FixSlashes( pFileName ); + pathLength = 0; + int i; + for( i = 0; i < g_NumBasePaths; i++ ) + { + // see if we can rip the base off of the filename. + if( Q_strncasecmp( g_pBasePaths[i], pFileName, strlen( g_pBasePaths[i] ) ) == 0 ) + { + pathLength = strlen( g_pBasePaths[i] ); + return true; + } + } + return false; +} + +int CmdLib_GetNumBasePaths( void ) +{ + return g_NumBasePaths; +} + +const char *CmdLib_GetBasePath( int i ) +{ + Assert( i >= 0 && i < g_NumBasePaths ); + return g_pBasePaths[i]; +} + + +//----------------------------------------------------------------------------- +// Like ExpandPath but expands the path for each base path like SafeOpenRead +//----------------------------------------------------------------------------- +int CmdLib_ExpandWithBasePaths( CUtlVector< CUtlString > &expandedPathList, const char *pszPath ) +{ + int nPathLength = 0; + + pszPath = ExpandPath( const_cast< char * >( pszPath ) ); // Kind of redundant but it's how CmdLib_HasBasePath needs things + + if ( CmdLib_HasBasePath( pszPath, nPathLength ) ) + { + pszPath = pszPath + nPathLength; + for ( int i = 0; i < CmdLib_GetNumBasePaths(); ++i ) + { + CUtlString &expandedPath = expandedPathList[ expandedPathList.AddToTail( CmdLib_GetBasePath( i ) ) ]; + expandedPath += pszPath; + } + } + else + { + expandedPathList.AddToTail( pszPath ); + } + + return expandedPathList.Count(); +} + + +FileHandle_t SafeOpenRead( const char *filename ) +{ + int pathLength; + FileHandle_t f = 0; + if( CmdLib_HasBasePath( filename, pathLength ) ) + { + filename = filename + pathLength; + int i; + for( i = 0; i < g_NumBasePaths; i++ ) + { + char tmp[MAX_PATH]; + strcpy( tmp, g_pBasePaths[i] ); + strcat( tmp, filename ); + f = g_pFileSystem->Open( tmp, "rb" ); + if( f ) + { + return f; + } + } + Error ("Error opening %s\n",filename ); + return f; + } + else + { + f = g_pFileSystem->Open( filename, "rb" ); + if ( !f ) + Error ("Error opening %s",filename ); + + return f; + } +} + +void SafeRead( FileHandle_t f, void *buffer, int count) +{ + if ( g_pFileSystem->Read (buffer, count, f) != (size_t)count) + Error ("File read failure"); +} + + +void SafeWrite ( FileHandle_t f, void *buffer, int count) +{ + if (g_pFileSystem->Write (buffer, count, f) != (size_t)count) + Error ("File write failure"); +} + + +/* +============== +FileExists +============== +*/ +qboolean FileExists ( const char *filename ) +{ + FileHandle_t hFile = g_pFileSystem->Open( filename, "rb" ); + if ( hFile == FILESYSTEM_INVALID_HANDLE ) + { + return false; + } + else + { + g_pFileSystem->Close( hFile ); + return true; + } +} + +/* +============== +LoadFile +============== +*/ +int LoadFile ( const char *filename, void **bufferptr ) +{ + int length = 0; + void *buffer; + + FileHandle_t f = SafeOpenRead (filename); + if ( FILESYSTEM_INVALID_HANDLE != f ) + { + length = Q_filelength (f); + buffer = malloc (length+1); + ((char *)buffer)[length] = 0; + SafeRead (f, buffer, length); + g_pFileSystem->Close (f); + *bufferptr = buffer; + } + else + { + *bufferptr = NULL; + } + return length; +} + + + +/* +============== +SaveFile +============== +*/ +void SaveFile ( const char *filename, void *buffer, int count ) +{ + FileHandle_t f = SafeOpenWrite (filename); + SafeWrite (f, buffer, count); + g_pFileSystem->Close (f); +} + +/* +==================== +Extract file parts +==================== +*/ +// FIXME: should include the slash, otherwise +// backing to an empty path will be wrong when appending a slash + + + +/* +============== +ParseNum / ParseHex +============== +*/ +int ParseHex (char *hex) +{ + char *str; + int num; + + num = 0; + str = hex; + + while (*str) + { + num <<= 4; + if (*str >= '0' && *str <= '9') + num += *str-'0'; + else if (*str >= 'a' && *str <= 'f') + num += 10 + *str-'a'; + else if (*str >= 'A' && *str <= 'F') + num += 10 + *str-'A'; + else + Error ("Bad hex number: %s",hex); + str++; + } + + return num; +} + + +int ParseNum (char *str) +{ + if (str[0] == '$') + return ParseHex (str+1); + if (str[0] == '0' && str[1] == 'x') + return ParseHex (str+2); + return atol (str); +} + +/* +============ +CreatePath +============ +*/ +void CreatePath (char *path) +{ + char *ofs, c; + + // strip the drive + if (path[1] == ':') + path += 2; + + for (ofs = path+1 ; *ofs ; ofs++) + { + c = *ofs; + if (c == '/' || c == '\\') + { // create the directory + *ofs = 0; + Q_mkdir (path); + *ofs = c; + } + } +} + +//----------------------------------------------------------------------------- +// Creates a path, path may already exist +//----------------------------------------------------------------------------- +#if defined( _WIN32 ) || defined( WIN32 ) +void SafeCreatePath( char *path ) +{ + char *ptr; + + // skip past the drive path, but don't strip + if ( path[1] == ':' ) + { + ptr = strchr( path, '\\' ); + } + else + { + ptr = path; + } + while ( ptr ) + { + ptr = strchr( ptr+1, '\\' ); + if ( ptr ) + { + *ptr = '\0'; + _mkdir( path ); + *ptr = '\\'; + } + } +} +#endif + +/* +============ +QCopyFile + + Used to archive source files +============ +*/ +void QCopyFile (char *from, char *to) +{ + void *buffer; + int length; + + length = LoadFile (from, &buffer); + CreatePath (to); + SaveFile (to, buffer, length); + free (buffer); +} + + + diff --git a/mp/src/utils/common/cmdlib.h b/mp/src/utils/common/cmdlib.h index 50fa9d20..5b5b2c7c 100644 --- a/mp/src/utils/common/cmdlib.h +++ b/mp/src/utils/common/cmdlib.h @@ -1,178 +1,178 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// - -#ifndef CMDLIB_H -#define CMDLIB_H - -#ifdef _WIN32 -#pragma once -#endif - -// cmdlib.h - -#include "basetypes.h" - -// This can go away when everything is in bin. -#if defined( CMDLIB_NODBGLIB ) - void Error( PRINTF_FORMAT_STRING char const *pMsg, ... ); -#else - #include "tier0/dbg.h" -#endif - -#include -#include -#include -#include -#include -#include -#include -#include "filesystem.h" -#include "filesystem_tools.h" -#include "tier1/utlstring.h" - - -// Tools should use this as the read path ID. It'll look into the paths specified by gameinfo.txt -#define TOOLS_READ_PATH_ID "GAME" - - -// Tools should use this to fprintf data to files. -void CmdLib_FPrintf( FileHandle_t hFile, PRINTF_FORMAT_STRING const char *pFormat, ... ); -char* CmdLib_FGets( char *pOut, int outSize, FileHandle_t hFile ); - - -// This can be set so Msg() sends output to hook functions (like the VMPI MySQL database), -// but doesn't actually printf the output. -extern bool g_bSuppressPrintfOutput; - -extern IBaseFileSystem *g_pFileSystem; - -// These call right into the functions in filesystem_tools.h -void CmdLib_InitFileSystem( const char *pFilename, int maxMemoryUsage = 0 ); -void CmdLib_TermFileSystem(); // GracefulExit calls this. -CreateInterfaceFn CmdLib_GetFileSystemFactory(); - - -#ifdef _WIN32 -#pragma warning(disable : 4244) // MIPS -#pragma warning(disable : 4136) // X86 -#pragma warning(disable : 4051) // ALPHA - -#pragma warning(disable : 4018) // signed/unsigned mismatch -#pragma warning(disable : 4305) // truncate from double to float - -#pragma warning(disable : 4389) // singned/unsigned mismatch in == -#pragma warning(disable: 4512) // assignment operator could not be generated -#endif - - -// the dec offsetof macro doesnt work very well... -#define myoffsetof(type,identifier) offsetof( type, identifier ) - - -// set these before calling CheckParm -extern int myargc; -extern char **myargv; - -int Q_filelength (FileHandle_t f); -int FileTime (char *path); - -void Q_mkdir( char *path ); - -char *ExpandArg (char *path); // expand relative to CWD -char *ExpandPath (char *path); // expand relative to gamedir - -char *ExpandPathAndArchive (char *path); - -// Fills in pOut with "X hours, Y minutes, Z seconds". Leaves out hours or minutes if they're zero. -void GetHourMinuteSecondsString( int nInputSeconds, char *pOut, int outLen ); - - - -int CheckParm (char *check); - -FileHandle_t SafeOpenWrite ( const char *filename ); -FileHandle_t SafeOpenRead ( const char *filename ); -void SafeRead( FileHandle_t f, void *buffer, int count); -void SafeWrite( FileHandle_t f, void *buffer, int count); - -int LoadFile ( const char *filename, void **bufferptr ); -void SaveFile ( const char *filename, void *buffer, int count ); -qboolean FileExists ( const char *filename ); - -int ParseNum (char *str); - -// Do a printf in the specified color. -#define CP_ERROR stderr, 1, 0, 0, 1 // default colors.. -#define CP_WARNING stderr, 1, 1, 0, 1 -#define CP_STARTUP stdout, 0, 1, 1, 1 -#define CP_NOTIFY stdout, 1, 1, 1, 1 -void ColorPrintf( FILE *pFile, bool red, bool green, bool blue, bool intensity, PRINTF_FORMAT_STRING char const *pFormat, ... ); - -// Initialize spew output. -void InstallSpewFunction(); - -// This registers an extra callback for spew output. -typedef void (*SpewHookFn)( const char * ); -void InstallExtraSpewHook( SpewHookFn pFn ); - -// Install allocation hooks so we error out if an allocation can't happen. -void InstallAllocationFunctions(); - -// This shuts down mgrs that use threads gracefully. If you just call exit(), the threads can -// get in a state where you can't tell if they are shutdown or not, and it can stall forever. -typedef void (*CleanupFn)(); -void CmdLib_AtCleanup( CleanupFn pFn ); // register a callback when Cleanup() is called. -void CmdLib_Cleanup(); -void CmdLib_Exit( int exitCode ); // Use this to cleanup and call exit(). - -// entrypoint if chaining spew functions -SpewRetval_t CmdLib_SpewOutputFunc( SpewType_t type, char const *pMsg ); -unsigned short SetConsoleTextColor( int red, int green, int blue, int intensity ); -void RestoreConsoleTextColor( unsigned short color ); - -// Append all spew output to the specified file. -void SetSpewFunctionLogFile( char const *pFilename ); - -char *COM_Parse (char *data); - -extern char com_token[1024]; - -char *copystring(const char *s); - -void CreatePath( char *path ); -void QCopyFile( char *from, char *to ); -void SafeCreatePath( char *path ); - -extern qboolean archive; -extern char archivedir[1024]; - -extern qboolean verbose; - -void qprintf( PRINTF_FORMAT_STRING const char *format, ... ); - -void ExpandWildcards (int *argc, char ***argv); - -void CmdLib_AddBasePath( const char *pBasePath ); -bool CmdLib_HasBasePath( const char *pFileName, int &pathLength ); -int CmdLib_GetNumBasePaths( void ); -const char *CmdLib_GetBasePath( int i ); -// Like ExpandPath but expands the path for each base path like SafeOpenRead -int CmdLib_ExpandWithBasePaths( CUtlVector< CUtlString > &expandedPathList, const char *pszPath ); - -extern bool g_bStopOnExit; - -// for compression routines -typedef struct -{ - byte *data; - int count; -} cblock_t; - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef CMDLIB_H +#define CMDLIB_H + +#ifdef _WIN32 +#pragma once +#endif + +// cmdlib.h + +#include "basetypes.h" + +// This can go away when everything is in bin. +#if defined( CMDLIB_NODBGLIB ) + void Error( PRINTF_FORMAT_STRING char const *pMsg, ... ); +#else + #include "tier0/dbg.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "filesystem.h" +#include "filesystem_tools.h" +#include "tier1/utlstring.h" + + +// Tools should use this as the read path ID. It'll look into the paths specified by gameinfo.txt +#define TOOLS_READ_PATH_ID "GAME" + + +// Tools should use this to fprintf data to files. +void CmdLib_FPrintf( FileHandle_t hFile, PRINTF_FORMAT_STRING const char *pFormat, ... ); +char* CmdLib_FGets( char *pOut, int outSize, FileHandle_t hFile ); + + +// This can be set so Msg() sends output to hook functions (like the VMPI MySQL database), +// but doesn't actually printf the output. +extern bool g_bSuppressPrintfOutput; + +extern IBaseFileSystem *g_pFileSystem; + +// These call right into the functions in filesystem_tools.h +void CmdLib_InitFileSystem( const char *pFilename, int maxMemoryUsage = 0 ); +void CmdLib_TermFileSystem(); // GracefulExit calls this. +CreateInterfaceFn CmdLib_GetFileSystemFactory(); + + +#ifdef _WIN32 +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4305) // truncate from double to float + +#pragma warning(disable : 4389) // singned/unsigned mismatch in == +#pragma warning(disable: 4512) // assignment operator could not be generated +#endif + + +// the dec offsetof macro doesnt work very well... +#define myoffsetof(type,identifier) offsetof( type, identifier ) + + +// set these before calling CheckParm +extern int myargc; +extern char **myargv; + +int Q_filelength (FileHandle_t f); +int FileTime (char *path); + +void Q_mkdir( char *path ); + +char *ExpandArg (char *path); // expand relative to CWD +char *ExpandPath (char *path); // expand relative to gamedir + +char *ExpandPathAndArchive (char *path); + +// Fills in pOut with "X hours, Y minutes, Z seconds". Leaves out hours or minutes if they're zero. +void GetHourMinuteSecondsString( int nInputSeconds, char *pOut, int outLen ); + + + +int CheckParm (char *check); + +FileHandle_t SafeOpenWrite ( const char *filename ); +FileHandle_t SafeOpenRead ( const char *filename ); +void SafeRead( FileHandle_t f, void *buffer, int count); +void SafeWrite( FileHandle_t f, void *buffer, int count); + +int LoadFile ( const char *filename, void **bufferptr ); +void SaveFile ( const char *filename, void *buffer, int count ); +qboolean FileExists ( const char *filename ); + +int ParseNum (char *str); + +// Do a printf in the specified color. +#define CP_ERROR stderr, 1, 0, 0, 1 // default colors.. +#define CP_WARNING stderr, 1, 1, 0, 1 +#define CP_STARTUP stdout, 0, 1, 1, 1 +#define CP_NOTIFY stdout, 1, 1, 1, 1 +void ColorPrintf( FILE *pFile, bool red, bool green, bool blue, bool intensity, PRINTF_FORMAT_STRING char const *pFormat, ... ); + +// Initialize spew output. +void InstallSpewFunction(); + +// This registers an extra callback for spew output. +typedef void (*SpewHookFn)( const char * ); +void InstallExtraSpewHook( SpewHookFn pFn ); + +// Install allocation hooks so we error out if an allocation can't happen. +void InstallAllocationFunctions(); + +// This shuts down mgrs that use threads gracefully. If you just call exit(), the threads can +// get in a state where you can't tell if they are shutdown or not, and it can stall forever. +typedef void (*CleanupFn)(); +void CmdLib_AtCleanup( CleanupFn pFn ); // register a callback when Cleanup() is called. +void CmdLib_Cleanup(); +void CmdLib_Exit( int exitCode ); // Use this to cleanup and call exit(). + +// entrypoint if chaining spew functions +SpewRetval_t CmdLib_SpewOutputFunc( SpewType_t type, char const *pMsg ); +unsigned short SetConsoleTextColor( int red, int green, int blue, int intensity ); +void RestoreConsoleTextColor( unsigned short color ); + +// Append all spew output to the specified file. +void SetSpewFunctionLogFile( char const *pFilename ); + +char *COM_Parse (char *data); + +extern char com_token[1024]; + +char *copystring(const char *s); + +void CreatePath( char *path ); +void QCopyFile( char *from, char *to ); +void SafeCreatePath( char *path ); + +extern qboolean archive; +extern char archivedir[1024]; + +extern qboolean verbose; + +void qprintf( PRINTF_FORMAT_STRING const char *format, ... ); + +void ExpandWildcards (int *argc, char ***argv); + +void CmdLib_AddBasePath( const char *pBasePath ); +bool CmdLib_HasBasePath( const char *pFileName, int &pathLength ); +int CmdLib_GetNumBasePaths( void ); +const char *CmdLib_GetBasePath( int i ); +// Like ExpandPath but expands the path for each base path like SafeOpenRead +int CmdLib_ExpandWithBasePaths( CUtlVector< CUtlString > &expandedPathList, const char *pszPath ); + +extern bool g_bStopOnExit; + +// for compression routines +typedef struct +{ + byte *data; + int count; +} cblock_t; + + #endif // CMDLIB_H \ No newline at end of file diff --git a/mp/src/utils/common/consolewnd.cpp b/mp/src/utils/common/consolewnd.cpp index 8802e39e..6201409a 100644 --- a/mp/src/utils/common/consolewnd.cpp +++ b/mp/src/utils/common/consolewnd.cpp @@ -1,333 +1,333 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include -#include "consolewnd.h" - - -#pragma warning( disable : 4311 ) // warning C4311: 'reinterpret_cast' : pointer truncation from 'CConsoleWnd *const ' to 'LONG' -#pragma warning( disable : 4312 ) // warning C4312: 'type cast' : conversion from 'LONG' to 'CConsoleWnd *' of greater size - -#define EDITCONTROL_BORDER_SIZE 5 - - -// ------------------------------------------------------------------------------------------------ // -// Functions to manage the console window. -// ------------------------------------------------------------------------------------------------ // - -class CConsoleWnd : public IConsoleWnd -{ -public: - CConsoleWnd(); - ~CConsoleWnd(); - - bool Init( void *hInstance, int dialogResourceID, int editControlID, bool bVisible ); - void Term(); - - virtual void Release(); - - virtual void SetVisible( bool bVisible ); - virtual bool IsVisible() const; - - virtual void PrintToConsole( const char *pMsg ); - virtual void SetTitle( const char *pTitle ); - - virtual void SetDeleteOnClose( bool bDelete ); - - -private: - - int WindowProc( - HWND hwndDlg, // handle to dialog box - UINT uMsg, // message - WPARAM wParam, // first message parameter - LPARAM lParam // second message parameter - ); - - static int CALLBACK StaticWindowProc( - HWND hwndDlg, // handle to dialog box - UINT uMsg, // message - WPARAM wParam, // first message parameter - LPARAM lParam // second message parameter - ); - - void RepositionEditControl(); - - -private: - - HWND m_hWnd; - HWND m_hEditControl; - bool m_bVisible; - bool m_bDeleteOnClose; - int m_nCurrentChars; -}; - - -CConsoleWnd::CConsoleWnd() -{ - m_hWnd = m_hEditControl = NULL; - m_bVisible = false; - m_bDeleteOnClose = false; - m_nCurrentChars = 0; -} - - -CConsoleWnd::~CConsoleWnd() -{ - Term(); -} - -bool CConsoleWnd::Init( void *hInstance, int dialogResourceID, int editControlID, bool bVisible ) -{ - // Create the window. - m_hWnd = CreateDialog( - (HINSTANCE)hInstance, - MAKEINTRESOURCE( dialogResourceID ), - NULL, - &CConsoleWnd::StaticWindowProc ); - - if ( !m_hWnd ) - return false; - - SetWindowLong( m_hWnd, GWL_USERDATA, reinterpret_cast< LONG >( this ) ); - if ( bVisible ) - ShowWindow( m_hWnd, SW_SHOW ); - - // Get a handle to the edit control. - m_hEditControl = GetDlgItem( m_hWnd, editControlID ); - if ( !m_hEditControl ) - return false; - - RepositionEditControl(); - - m_bVisible = bVisible; - return true; -} - - -void CConsoleWnd::Term() -{ - if ( m_hWnd ) - { - DestroyWindow( m_hWnd ); - m_hWnd = NULL; - } -} - - -void CConsoleWnd::Release() -{ - delete this; -} - - -void CConsoleWnd::SetVisible( bool bVisible ) -{ - ShowWindow( m_hWnd, bVisible ? SW_RESTORE : SW_HIDE ); - - if ( bVisible ) - { - ShowWindow( m_hWnd, SW_SHOW ); - SetWindowPos( m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE ); - UpdateWindow( m_hWnd ); - - int nLen = (int)SendMessage( m_hEditControl, EM_GETLIMITTEXT, 0, 0 ); - SendMessage( m_hEditControl, EM_SETSEL, nLen, nLen ); - } - else - { - SetWindowPos( m_hWnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_NOOWNERZORDER ); - } - - m_bVisible = bVisible; -} - - -bool CConsoleWnd::IsVisible() const -{ - return m_bVisible; -} - - -void CConsoleWnd::PrintToConsole( const char *pMsg ) -{ - if ( m_nCurrentChars >= 16*1024 ) - { - // Clear the edit control otherwise it'll stop outputting anything. - m_nCurrentChars = 0; - - int nLen = (int)SendMessage( m_hEditControl, EM_GETLIMITTEXT, 0, 0 ); - SendMessage( m_hEditControl, EM_SETSEL, 0, nLen ); - SendMessage( m_hEditControl, EM_REPLACESEL, FALSE, (LPARAM)"" ); - } - - FormatAndSendToEditControl( m_hEditControl, pMsg ); - m_nCurrentChars += (int)strlen( pMsg ); -} - - -void CConsoleWnd::SetTitle( const char *pTitle ) -{ - SetWindowText( m_hWnd, pTitle ); -} - - -int CConsoleWnd::WindowProc( - HWND hwndDlg, // handle to dialog box - UINT uMsg, // message - WPARAM wParam, // first message parameter - LPARAM lParam // second message parameter - ) -{ - lParam = lParam; // avoid compiler warning - - if ( hwndDlg != m_hWnd ) - return false; - - switch ( uMsg ) - { - case WM_SYSCOMMAND: - { - if ( wParam == SC_CLOSE ) - { - if ( m_bDeleteOnClose ) - { - Release(); - } - else - { - SetVisible( false ); - return true; - } - } - } - break; - - case WM_SHOWWINDOW: - { - m_bVisible = (wParam != 0); - } - break; - - case WM_SIZE: - case WM_INITDIALOG: - { - RepositionEditControl(); - } - break; - } - - return false; -} - - -int CConsoleWnd::StaticWindowProc( - HWND hwndDlg, // handle to dialog box - UINT uMsg, // message - WPARAM wParam, // first message parameter - LPARAM lParam // second message parameter - ) -{ - CConsoleWnd *pDlg = (CConsoleWnd*)GetWindowLong( hwndDlg, GWL_USERDATA ); - if ( pDlg ) - return pDlg->WindowProc( hwndDlg, uMsg, wParam, lParam ); - else - return false; -} - - -void CConsoleWnd::RepositionEditControl() -{ - RECT rcMain; - GetClientRect( m_hWnd, &rcMain ); - - RECT rcNew; - rcNew.left = rcMain.left + EDITCONTROL_BORDER_SIZE; - rcNew.right = rcMain.right - EDITCONTROL_BORDER_SIZE; - rcNew.top = rcMain.top + EDITCONTROL_BORDER_SIZE; - rcNew.bottom = rcMain.bottom - EDITCONTROL_BORDER_SIZE; - - SetWindowPos( - m_hEditControl, - NULL, - rcNew.left, - rcNew.top, - rcNew.right - rcNew.left, - rcNew.bottom - rcNew.top, - SWP_NOZORDER ); -} - - -void CConsoleWnd::SetDeleteOnClose( bool bDelete ) -{ - m_bDeleteOnClose = bDelete; -} - - -// ------------------------------------------------------------------------------------ // -// Module interface. -// ------------------------------------------------------------------------------------ // - -void SendToEditControl( HWND hEditControl, const char *pText ) -{ - int nLen = (int)SendMessage( hEditControl, EM_GETLIMITTEXT, 0, 0 ); - SendMessage( hEditControl, EM_SETSEL, nLen, nLen ); - SendMessage( hEditControl, EM_REPLACESEL, FALSE, (LPARAM)pText ); -} - - -void FormatAndSendToEditControl( void *hWnd, const char *pText ) -{ - HWND hEditControl = (HWND)hWnd; - - // Translate \n to \r\n. - char outMsg[1024]; - const char *pIn = pText; - char *pOut = outMsg; - while ( *pIn ) - { - if ( *pIn == '\n' ) - { - *pOut = '\r'; - pOut++; - } - *pOut = *pIn; - - ++pIn; - ++pOut; - - if ( pOut - outMsg >= 1020 ) - { - *pOut = 0; - SendToEditControl( hEditControl, outMsg ); - pOut = outMsg; - } - } - *pOut = 0; - SendToEditControl( hEditControl, outMsg ); -} - - -IConsoleWnd* CreateConsoleWnd( void *hInstance, int dialogResourceID, int editControlID, bool bVisible ) -{ - CConsoleWnd *pWnd = new CConsoleWnd; - - if ( pWnd->Init( hInstance, dialogResourceID, editControlID, bVisible ) ) - { - return pWnd; - } - else - { - pWnd->Release(); - return NULL; - } -} - - - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include +#include "consolewnd.h" + + +#pragma warning( disable : 4311 ) // warning C4311: 'reinterpret_cast' : pointer truncation from 'CConsoleWnd *const ' to 'LONG' +#pragma warning( disable : 4312 ) // warning C4312: 'type cast' : conversion from 'LONG' to 'CConsoleWnd *' of greater size + +#define EDITCONTROL_BORDER_SIZE 5 + + +// ------------------------------------------------------------------------------------------------ // +// Functions to manage the console window. +// ------------------------------------------------------------------------------------------------ // + +class CConsoleWnd : public IConsoleWnd +{ +public: + CConsoleWnd(); + ~CConsoleWnd(); + + bool Init( void *hInstance, int dialogResourceID, int editControlID, bool bVisible ); + void Term(); + + virtual void Release(); + + virtual void SetVisible( bool bVisible ); + virtual bool IsVisible() const; + + virtual void PrintToConsole( const char *pMsg ); + virtual void SetTitle( const char *pTitle ); + + virtual void SetDeleteOnClose( bool bDelete ); + + +private: + + int WindowProc( + HWND hwndDlg, // handle to dialog box + UINT uMsg, // message + WPARAM wParam, // first message parameter + LPARAM lParam // second message parameter + ); + + static int CALLBACK StaticWindowProc( + HWND hwndDlg, // handle to dialog box + UINT uMsg, // message + WPARAM wParam, // first message parameter + LPARAM lParam // second message parameter + ); + + void RepositionEditControl(); + + +private: + + HWND m_hWnd; + HWND m_hEditControl; + bool m_bVisible; + bool m_bDeleteOnClose; + int m_nCurrentChars; +}; + + +CConsoleWnd::CConsoleWnd() +{ + m_hWnd = m_hEditControl = NULL; + m_bVisible = false; + m_bDeleteOnClose = false; + m_nCurrentChars = 0; +} + + +CConsoleWnd::~CConsoleWnd() +{ + Term(); +} + +bool CConsoleWnd::Init( void *hInstance, int dialogResourceID, int editControlID, bool bVisible ) +{ + // Create the window. + m_hWnd = CreateDialog( + (HINSTANCE)hInstance, + MAKEINTRESOURCE( dialogResourceID ), + NULL, + &CConsoleWnd::StaticWindowProc ); + + if ( !m_hWnd ) + return false; + + SetWindowLong( m_hWnd, GWL_USERDATA, reinterpret_cast< LONG >( this ) ); + if ( bVisible ) + ShowWindow( m_hWnd, SW_SHOW ); + + // Get a handle to the edit control. + m_hEditControl = GetDlgItem( m_hWnd, editControlID ); + if ( !m_hEditControl ) + return false; + + RepositionEditControl(); + + m_bVisible = bVisible; + return true; +} + + +void CConsoleWnd::Term() +{ + if ( m_hWnd ) + { + DestroyWindow( m_hWnd ); + m_hWnd = NULL; + } +} + + +void CConsoleWnd::Release() +{ + delete this; +} + + +void CConsoleWnd::SetVisible( bool bVisible ) +{ + ShowWindow( m_hWnd, bVisible ? SW_RESTORE : SW_HIDE ); + + if ( bVisible ) + { + ShowWindow( m_hWnd, SW_SHOW ); + SetWindowPos( m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE ); + UpdateWindow( m_hWnd ); + + int nLen = (int)SendMessage( m_hEditControl, EM_GETLIMITTEXT, 0, 0 ); + SendMessage( m_hEditControl, EM_SETSEL, nLen, nLen ); + } + else + { + SetWindowPos( m_hWnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_NOOWNERZORDER ); + } + + m_bVisible = bVisible; +} + + +bool CConsoleWnd::IsVisible() const +{ + return m_bVisible; +} + + +void CConsoleWnd::PrintToConsole( const char *pMsg ) +{ + if ( m_nCurrentChars >= 16*1024 ) + { + // Clear the edit control otherwise it'll stop outputting anything. + m_nCurrentChars = 0; + + int nLen = (int)SendMessage( m_hEditControl, EM_GETLIMITTEXT, 0, 0 ); + SendMessage( m_hEditControl, EM_SETSEL, 0, nLen ); + SendMessage( m_hEditControl, EM_REPLACESEL, FALSE, (LPARAM)"" ); + } + + FormatAndSendToEditControl( m_hEditControl, pMsg ); + m_nCurrentChars += (int)strlen( pMsg ); +} + + +void CConsoleWnd::SetTitle( const char *pTitle ) +{ + SetWindowText( m_hWnd, pTitle ); +} + + +int CConsoleWnd::WindowProc( + HWND hwndDlg, // handle to dialog box + UINT uMsg, // message + WPARAM wParam, // first message parameter + LPARAM lParam // second message parameter + ) +{ + lParam = lParam; // avoid compiler warning + + if ( hwndDlg != m_hWnd ) + return false; + + switch ( uMsg ) + { + case WM_SYSCOMMAND: + { + if ( wParam == SC_CLOSE ) + { + if ( m_bDeleteOnClose ) + { + Release(); + } + else + { + SetVisible( false ); + return true; + } + } + } + break; + + case WM_SHOWWINDOW: + { + m_bVisible = (wParam != 0); + } + break; + + case WM_SIZE: + case WM_INITDIALOG: + { + RepositionEditControl(); + } + break; + } + + return false; +} + + +int CConsoleWnd::StaticWindowProc( + HWND hwndDlg, // handle to dialog box + UINT uMsg, // message + WPARAM wParam, // first message parameter + LPARAM lParam // second message parameter + ) +{ + CConsoleWnd *pDlg = (CConsoleWnd*)GetWindowLong( hwndDlg, GWL_USERDATA ); + if ( pDlg ) + return pDlg->WindowProc( hwndDlg, uMsg, wParam, lParam ); + else + return false; +} + + +void CConsoleWnd::RepositionEditControl() +{ + RECT rcMain; + GetClientRect( m_hWnd, &rcMain ); + + RECT rcNew; + rcNew.left = rcMain.left + EDITCONTROL_BORDER_SIZE; + rcNew.right = rcMain.right - EDITCONTROL_BORDER_SIZE; + rcNew.top = rcMain.top + EDITCONTROL_BORDER_SIZE; + rcNew.bottom = rcMain.bottom - EDITCONTROL_BORDER_SIZE; + + SetWindowPos( + m_hEditControl, + NULL, + rcNew.left, + rcNew.top, + rcNew.right - rcNew.left, + rcNew.bottom - rcNew.top, + SWP_NOZORDER ); +} + + +void CConsoleWnd::SetDeleteOnClose( bool bDelete ) +{ + m_bDeleteOnClose = bDelete; +} + + +// ------------------------------------------------------------------------------------ // +// Module interface. +// ------------------------------------------------------------------------------------ // + +void SendToEditControl( HWND hEditControl, const char *pText ) +{ + int nLen = (int)SendMessage( hEditControl, EM_GETLIMITTEXT, 0, 0 ); + SendMessage( hEditControl, EM_SETSEL, nLen, nLen ); + SendMessage( hEditControl, EM_REPLACESEL, FALSE, (LPARAM)pText ); +} + + +void FormatAndSendToEditControl( void *hWnd, const char *pText ) +{ + HWND hEditControl = (HWND)hWnd; + + // Translate \n to \r\n. + char outMsg[1024]; + const char *pIn = pText; + char *pOut = outMsg; + while ( *pIn ) + { + if ( *pIn == '\n' ) + { + *pOut = '\r'; + pOut++; + } + *pOut = *pIn; + + ++pIn; + ++pOut; + + if ( pOut - outMsg >= 1020 ) + { + *pOut = 0; + SendToEditControl( hEditControl, outMsg ); + pOut = outMsg; + } + } + *pOut = 0; + SendToEditControl( hEditControl, outMsg ); +} + + +IConsoleWnd* CreateConsoleWnd( void *hInstance, int dialogResourceID, int editControlID, bool bVisible ) +{ + CConsoleWnd *pWnd = new CConsoleWnd; + + if ( pWnd->Init( hInstance, dialogResourceID, editControlID, bVisible ) ) + { + return pWnd; + } + else + { + pWnd->Release(); + return NULL; + } +} + + + + diff --git a/mp/src/utils/common/consolewnd.h b/mp/src/utils/common/consolewnd.h index 4572ff57..92649e2f 100644 --- a/mp/src/utils/common/consolewnd.h +++ b/mp/src/utils/common/consolewnd.h @@ -1,45 +1,45 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef CONSOLEWND_H -#define CONSOLEWND_H -#ifdef _WIN32 -#pragma once -#endif - - -class IConsoleWnd -{ -public: - virtual void Release() = 0; - - // Print a message to the console. - virtual void PrintToConsole( const char *pMsg ) = 0; - - // Set the window title. - virtual void SetTitle( const char *pTitle ) = 0; - - // Show and hide the console window. - virtual void SetVisible( bool bVisible ) = 0; - virtual bool IsVisible() const = 0; - - // Normally, the window just hides itself when closed. You can use this to make the window - // automatically go away when they close it. - virtual void SetDeleteOnClose( bool bDelete ) = 0; -}; - - -// Utility functions. - -// This converts adds \r's where necessary and sends the text to the edit control. -void FormatAndSendToEditControl( void *hWnd, const char *pText ); - - -IConsoleWnd* CreateConsoleWnd( void *hInstance, int dialogResourceID, int editControlID, bool bVisible ); - - -#endif // CONSOLEWND_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CONSOLEWND_H +#define CONSOLEWND_H +#ifdef _WIN32 +#pragma once +#endif + + +class IConsoleWnd +{ +public: + virtual void Release() = 0; + + // Print a message to the console. + virtual void PrintToConsole( const char *pMsg ) = 0; + + // Set the window title. + virtual void SetTitle( const char *pTitle ) = 0; + + // Show and hide the console window. + virtual void SetVisible( bool bVisible ) = 0; + virtual bool IsVisible() const = 0; + + // Normally, the window just hides itself when closed. You can use this to make the window + // automatically go away when they close it. + virtual void SetDeleteOnClose( bool bDelete ) = 0; +}; + + +// Utility functions. + +// This converts adds \r's where necessary and sends the text to the edit control. +void FormatAndSendToEditControl( void *hWnd, const char *pText ); + + +IConsoleWnd* CreateConsoleWnd( void *hInstance, int dialogResourceID, int editControlID, bool bVisible ); + + +#endif // CONSOLEWND_H diff --git a/mp/src/utils/common/filesystem_tools.cpp b/mp/src/utils/common/filesystem_tools.cpp index 9714c57a..b1306c59 100644 --- a/mp/src/utils/common/filesystem_tools.cpp +++ b/mp/src/utils/common/filesystem_tools.cpp @@ -1,209 +1,209 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//===========================================================================// - -#if defined( _WIN32 ) && !defined( _X360 ) -#include -#include -#include // _chmod -#elif _LINUX -#include -#endif - -#include -#include -#include "tier1/strtools.h" -#include "filesystem_tools.h" -#include "tier0/icommandline.h" -#include "KeyValues.h" -#include "tier2/tier2.h" - -#ifdef MPI - #include "vmpi.h" - #include "vmpi_tools_shared.h" - #include "vmpi_filesystem.h" -#endif - -// memdbgon must be the last include file in a .cpp file!!! -#include - - -// ---------------------------------------------------------------------------------------------------- // -// Module interface. -// ---------------------------------------------------------------------------------------------------- // - -IBaseFileSystem *g_pFileSystem = NULL; - -// These are only used for tools that need the search paths that the engine's file system provides. -CSysModule *g_pFullFileSystemModule = NULL; - -// --------------------------------------------------------------------------- -// -// These are the base paths that everything will be referenced relative to (textures especially) -// All of these directories include the trailing slash -// -// --------------------------------------------------------------------------- - -// This is the the path of the initial source file (relative to the cwd) -char qdir[1024]; - -// This is the base engine + mod-specific game dir (e.g. "c:\tf2\mytfmod\") -char gamedir[1024]; - -void FileSystem_SetupStandardDirectories( const char *pFilename, const char *pGameInfoPath ) -{ - // Set qdir. - if ( !pFilename ) - { - pFilename = "."; - } - - Q_MakeAbsolutePath( qdir, sizeof( qdir ), pFilename, NULL ); - Q_StripFilename( qdir ); - Q_strlower( qdir ); - if ( qdir[0] != 0 ) - { - Q_AppendSlash( qdir, sizeof( qdir ) ); - } - - // Set gamedir. - Q_MakeAbsolutePath( gamedir, sizeof( gamedir ), pGameInfoPath ); - Q_AppendSlash( gamedir, sizeof( gamedir ) ); -} - - -bool FileSystem_Init_Normal( const char *pFilename, FSInitType_t initType, bool bOnlyUseDirectoryName ) -{ - if ( initType == FS_INIT_FULL ) - { - // First, get the name of the module - char fileSystemDLLName[MAX_PATH]; - bool bSteam; - if ( FileSystem_GetFileSystemDLLName( fileSystemDLLName, MAX_PATH, bSteam ) != FS_OK ) - return false; - - // If we're under Steam we need extra setup to let us find the proper modules - FileSystem_SetupSteamInstallPath(); - - // Next, load the module, call Connect/Init. - CFSLoadModuleInfo loadModuleInfo; - loadModuleInfo.m_pFileSystemDLLName = fileSystemDLLName; - loadModuleInfo.m_pDirectoryName = pFilename; - loadModuleInfo.m_bOnlyUseDirectoryName = bOnlyUseDirectoryName; - loadModuleInfo.m_ConnectFactory = Sys_GetFactoryThis(); - loadModuleInfo.m_bSteam = bSteam; - loadModuleInfo.m_bToolsMode = true; - if ( FileSystem_LoadFileSystemModule( loadModuleInfo ) != FS_OK ) - return false; - - // Next, mount the content - CFSMountContentInfo mountContentInfo; - mountContentInfo.m_pDirectoryName= loadModuleInfo.m_GameInfoPath; - mountContentInfo.m_pFileSystem = loadModuleInfo.m_pFileSystem; - mountContentInfo.m_bToolsMode = true; - if ( FileSystem_MountContent( mountContentInfo ) != FS_OK ) - return false; - - // Finally, load the search paths. - CFSSearchPathsInit searchPathsInit; - searchPathsInit.m_pDirectoryName = loadModuleInfo.m_GameInfoPath; - searchPathsInit.m_pFileSystem = loadModuleInfo.m_pFileSystem; - if ( FileSystem_LoadSearchPaths( searchPathsInit ) != FS_OK ) - return false; - - // Store the data we got from filesystem_init. - g_pFileSystem = g_pFullFileSystem = loadModuleInfo.m_pFileSystem; - g_pFullFileSystemModule = loadModuleInfo.m_pModule; - - FileSystem_AddSearchPath_Platform( g_pFullFileSystem, loadModuleInfo.m_GameInfoPath ); - - FileSystem_SetupStandardDirectories( pFilename, loadModuleInfo.m_GameInfoPath ); - } - else - { - if ( !Sys_LoadInterface( - "filesystem_stdio", - FILESYSTEM_INTERFACE_VERSION, - &g_pFullFileSystemModule, - (void**)&g_pFullFileSystem ) ) - { - return false; - } - - if ( g_pFullFileSystem->Init() != INIT_OK ) - return false; - - g_pFullFileSystem->RemoveAllSearchPaths(); - g_pFullFileSystem->AddSearchPath( "../platform", "PLATFORM" ); - g_pFullFileSystem->AddSearchPath( ".", "GAME" ); - - g_pFileSystem = g_pFullFileSystem; - } - - return true; -} - - -bool FileSystem_Init( const char *pBSPFilename, int maxMemoryUsage, FSInitType_t initType, bool bOnlyUseFilename ) -{ - Assert( CommandLine()->GetCmdLine() != NULL ); // Should have called CreateCmdLine by now. - - // If this app uses VMPI, then let VMPI intercept all filesystem calls. -#if defined( MPI ) - if ( g_bUseMPI ) - { - if ( g_bMPIMaster ) - { - if ( !FileSystem_Init_Normal( pBSPFilename, initType, bOnlyUseFilename ) ) - return false; - - g_pFileSystem = g_pFullFileSystem = VMPI_FileSystem_Init( maxMemoryUsage, g_pFullFileSystem ); - SendQDirInfo(); - } - else - { - g_pFileSystem = g_pFullFileSystem = VMPI_FileSystem_Init( maxMemoryUsage, NULL ); - RecvQDirInfo(); - } - return true; - } -#endif - - return FileSystem_Init_Normal( pBSPFilename, initType, bOnlyUseFilename ); -} - - -void FileSystem_Term() -{ -#if defined( MPI ) - if ( g_bUseMPI ) - { - g_pFileSystem = g_pFullFileSystem = VMPI_FileSystem_Term(); - } -#endif - - if ( g_pFullFileSystem ) - { - g_pFullFileSystem->Shutdown(); - g_pFullFileSystem = NULL; - g_pFileSystem = NULL; - } - - if ( g_pFullFileSystemModule ) - { - Sys_UnloadModule( g_pFullFileSystemModule ); - g_pFullFileSystemModule = NULL; - } -} - - -CreateInterfaceFn FileSystem_GetFactory() -{ -#if defined( MPI ) - if ( g_bUseMPI ) - return VMPI_FileSystem_GetFactory(); -#endif - return Sys_GetFactory( g_pFullFileSystemModule ); -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#if defined( _WIN32 ) && !defined( _X360 ) +#include +#include +#include // _chmod +#elif _LINUX +#include +#endif + +#include +#include +#include "tier1/strtools.h" +#include "filesystem_tools.h" +#include "tier0/icommandline.h" +#include "KeyValues.h" +#include "tier2/tier2.h" + +#ifdef MPI + #include "vmpi.h" + #include "vmpi_tools_shared.h" + #include "vmpi_filesystem.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include + + +// ---------------------------------------------------------------------------------------------------- // +// Module interface. +// ---------------------------------------------------------------------------------------------------- // + +IBaseFileSystem *g_pFileSystem = NULL; + +// These are only used for tools that need the search paths that the engine's file system provides. +CSysModule *g_pFullFileSystemModule = NULL; + +// --------------------------------------------------------------------------- +// +// These are the base paths that everything will be referenced relative to (textures especially) +// All of these directories include the trailing slash +// +// --------------------------------------------------------------------------- + +// This is the the path of the initial source file (relative to the cwd) +char qdir[1024]; + +// This is the base engine + mod-specific game dir (e.g. "c:\tf2\mytfmod\") +char gamedir[1024]; + +void FileSystem_SetupStandardDirectories( const char *pFilename, const char *pGameInfoPath ) +{ + // Set qdir. + if ( !pFilename ) + { + pFilename = "."; + } + + Q_MakeAbsolutePath( qdir, sizeof( qdir ), pFilename, NULL ); + Q_StripFilename( qdir ); + Q_strlower( qdir ); + if ( qdir[0] != 0 ) + { + Q_AppendSlash( qdir, sizeof( qdir ) ); + } + + // Set gamedir. + Q_MakeAbsolutePath( gamedir, sizeof( gamedir ), pGameInfoPath ); + Q_AppendSlash( gamedir, sizeof( gamedir ) ); +} + + +bool FileSystem_Init_Normal( const char *pFilename, FSInitType_t initType, bool bOnlyUseDirectoryName ) +{ + if ( initType == FS_INIT_FULL ) + { + // First, get the name of the module + char fileSystemDLLName[MAX_PATH]; + bool bSteam; + if ( FileSystem_GetFileSystemDLLName( fileSystemDLLName, MAX_PATH, bSteam ) != FS_OK ) + return false; + + // If we're under Steam we need extra setup to let us find the proper modules + FileSystem_SetupSteamInstallPath(); + + // Next, load the module, call Connect/Init. + CFSLoadModuleInfo loadModuleInfo; + loadModuleInfo.m_pFileSystemDLLName = fileSystemDLLName; + loadModuleInfo.m_pDirectoryName = pFilename; + loadModuleInfo.m_bOnlyUseDirectoryName = bOnlyUseDirectoryName; + loadModuleInfo.m_ConnectFactory = Sys_GetFactoryThis(); + loadModuleInfo.m_bSteam = bSteam; + loadModuleInfo.m_bToolsMode = true; + if ( FileSystem_LoadFileSystemModule( loadModuleInfo ) != FS_OK ) + return false; + + // Next, mount the content + CFSMountContentInfo mountContentInfo; + mountContentInfo.m_pDirectoryName= loadModuleInfo.m_GameInfoPath; + mountContentInfo.m_pFileSystem = loadModuleInfo.m_pFileSystem; + mountContentInfo.m_bToolsMode = true; + if ( FileSystem_MountContent( mountContentInfo ) != FS_OK ) + return false; + + // Finally, load the search paths. + CFSSearchPathsInit searchPathsInit; + searchPathsInit.m_pDirectoryName = loadModuleInfo.m_GameInfoPath; + searchPathsInit.m_pFileSystem = loadModuleInfo.m_pFileSystem; + if ( FileSystem_LoadSearchPaths( searchPathsInit ) != FS_OK ) + return false; + + // Store the data we got from filesystem_init. + g_pFileSystem = g_pFullFileSystem = loadModuleInfo.m_pFileSystem; + g_pFullFileSystemModule = loadModuleInfo.m_pModule; + + FileSystem_AddSearchPath_Platform( g_pFullFileSystem, loadModuleInfo.m_GameInfoPath ); + + FileSystem_SetupStandardDirectories( pFilename, loadModuleInfo.m_GameInfoPath ); + } + else + { + if ( !Sys_LoadInterface( + "filesystem_stdio", + FILESYSTEM_INTERFACE_VERSION, + &g_pFullFileSystemModule, + (void**)&g_pFullFileSystem ) ) + { + return false; + } + + if ( g_pFullFileSystem->Init() != INIT_OK ) + return false; + + g_pFullFileSystem->RemoveAllSearchPaths(); + g_pFullFileSystem->AddSearchPath( "../platform", "PLATFORM" ); + g_pFullFileSystem->AddSearchPath( ".", "GAME" ); + + g_pFileSystem = g_pFullFileSystem; + } + + return true; +} + + +bool FileSystem_Init( const char *pBSPFilename, int maxMemoryUsage, FSInitType_t initType, bool bOnlyUseFilename ) +{ + Assert( CommandLine()->GetCmdLine() != NULL ); // Should have called CreateCmdLine by now. + + // If this app uses VMPI, then let VMPI intercept all filesystem calls. +#if defined( MPI ) + if ( g_bUseMPI ) + { + if ( g_bMPIMaster ) + { + if ( !FileSystem_Init_Normal( pBSPFilename, initType, bOnlyUseFilename ) ) + return false; + + g_pFileSystem = g_pFullFileSystem = VMPI_FileSystem_Init( maxMemoryUsage, g_pFullFileSystem ); + SendQDirInfo(); + } + else + { + g_pFileSystem = g_pFullFileSystem = VMPI_FileSystem_Init( maxMemoryUsage, NULL ); + RecvQDirInfo(); + } + return true; + } +#endif + + return FileSystem_Init_Normal( pBSPFilename, initType, bOnlyUseFilename ); +} + + +void FileSystem_Term() +{ +#if defined( MPI ) + if ( g_bUseMPI ) + { + g_pFileSystem = g_pFullFileSystem = VMPI_FileSystem_Term(); + } +#endif + + if ( g_pFullFileSystem ) + { + g_pFullFileSystem->Shutdown(); + g_pFullFileSystem = NULL; + g_pFileSystem = NULL; + } + + if ( g_pFullFileSystemModule ) + { + Sys_UnloadModule( g_pFullFileSystemModule ); + g_pFullFileSystemModule = NULL; + } +} + + +CreateInterfaceFn FileSystem_GetFactory() +{ +#if defined( MPI ) + if ( g_bUseMPI ) + return VMPI_FileSystem_GetFactory(); +#endif + return Sys_GetFactory( g_pFullFileSystemModule ); +} diff --git a/mp/src/utils/common/filesystem_tools.h b/mp/src/utils/common/filesystem_tools.h index 09db7b3e..ada82ad5 100644 --- a/mp/src/utils/common/filesystem_tools.h +++ b/mp/src/utils/common/filesystem_tools.h @@ -1,59 +1,59 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//===========================================================================// - -#ifndef FILESYSTEM_TOOLS_H -#define FILESYSTEM_TOOLS_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "filesystem.h" -#include "filesystem_init.h" - - -// This is the the path of the initial source file -extern char qdir[1024]; - -// This is the base engine + mod-specific game dir (e.g. "d:\tf2\mytfmod\") -extern char gamedir[1024]; - - -// ---------------------------------------------------------------------------------------- // -// Filesystem initialization. -// ---------------------------------------------------------------------------------------- // - -enum FSInitType_t -{ - FS_INIT_FULL, // Load gameinfo.txt, maybe use filesystem_steam, and setup search paths. - FS_INIT_COMPATIBILITY_MODE // Load filesystem_stdio and that's it. -}; - -// -// Initializes qdir, and gamedir. Also initializes the VMPI filesystem if MPI is defined. -// -// pFilename can be NULL if you want to rely on vproject and qproject. If it's specified, FileSystem_Init -// will go up directories from pFilename looking for gameinfo.txt (if vproject isn't specified). -// -// If bOnlyUseFilename is true, then it won't use any alternative methods of finding the vproject dir -// (ie: it won't use -game or -vproject or the vproject env var or qproject). -// -bool FileSystem_Init( const char *pFilename, int maxMemoryUsage=0, FSInitType_t initType=FS_INIT_FULL, bool bOnlyUseFilename=false ); -void FileSystem_Term(); - -// Used to connect app-framework based console apps to the filesystem tools -void FileSystem_SetupStandardDirectories( const char *pFilename, const char *pGameInfoPath ); - -CreateInterfaceFn FileSystem_GetFactory( void ); - - -extern IBaseFileSystem *g_pFileSystem; -extern IFileSystem *g_pFullFileSystem; // NOTE: this is here when VMPI is being used, but a VMPI app can - // ONLY use LoadModule/UnloadModule. - - -#endif // FILESYSTEM_TOOLS_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#ifndef FILESYSTEM_TOOLS_H +#define FILESYSTEM_TOOLS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "filesystem.h" +#include "filesystem_init.h" + + +// This is the the path of the initial source file +extern char qdir[1024]; + +// This is the base engine + mod-specific game dir (e.g. "d:\tf2\mytfmod\") +extern char gamedir[1024]; + + +// ---------------------------------------------------------------------------------------- // +// Filesystem initialization. +// ---------------------------------------------------------------------------------------- // + +enum FSInitType_t +{ + FS_INIT_FULL, // Load gameinfo.txt, maybe use filesystem_steam, and setup search paths. + FS_INIT_COMPATIBILITY_MODE // Load filesystem_stdio and that's it. +}; + +// +// Initializes qdir, and gamedir. Also initializes the VMPI filesystem if MPI is defined. +// +// pFilename can be NULL if you want to rely on vproject and qproject. If it's specified, FileSystem_Init +// will go up directories from pFilename looking for gameinfo.txt (if vproject isn't specified). +// +// If bOnlyUseFilename is true, then it won't use any alternative methods of finding the vproject dir +// (ie: it won't use -game or -vproject or the vproject env var or qproject). +// +bool FileSystem_Init( const char *pFilename, int maxMemoryUsage=0, FSInitType_t initType=FS_INIT_FULL, bool bOnlyUseFilename=false ); +void FileSystem_Term(); + +// Used to connect app-framework based console apps to the filesystem tools +void FileSystem_SetupStandardDirectories( const char *pFilename, const char *pGameInfoPath ); + +CreateInterfaceFn FileSystem_GetFactory( void ); + + +extern IBaseFileSystem *g_pFileSystem; +extern IFileSystem *g_pFullFileSystem; // NOTE: this is here when VMPI is being used, but a VMPI app can + // ONLY use LoadModule/UnloadModule. + + +#endif // FILESYSTEM_TOOLS_H diff --git a/mp/src/utils/common/map_shared.cpp b/mp/src/utils/common/map_shared.cpp index f1e970c0..e33e20cb 100644 --- a/mp/src/utils/common/map_shared.cpp +++ b/mp/src/utils/common/map_shared.cpp @@ -1,136 +1,136 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "map_shared.h" -#include "bsplib.h" -#include "cmdlib.h" - - -CMapError g_MapError; -int g_nMapFileVersion; - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *szKey - -// *szValue - -// *pLoadEntity - -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadEntityKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity) -{ - if (!stricmp(szKey, "classname")) - { - if (!stricmp(szValue, "func_detail")) - { - pLoadEntity->nBaseContents = CONTENTS_DETAIL; - } - else if (!stricmp(szValue, "func_ladder")) - { - pLoadEntity->nBaseContents = CONTENTS_LADDER; - } - else if (!stricmp(szValue, "func_water")) - { - pLoadEntity->nBaseContents = CONTENTS_WATER; - } - } - else if (!stricmp(szKey, "id")) - { - // UNDONE: flag entity errors by ID instead of index - //g_MapError.EntityState( atoi( szValue ) ); - // rename this field since DME code uses this name - SetKeyValue( pLoadEntity->pEntity, "hammerid", szValue ); - return(ChunkFile_Ok); - } - else if( !stricmp( szKey, "mapversion" ) ) - { - // .vmf map revision number - g_MapRevision = atoi( szValue ); - SetKeyValue( pLoadEntity->pEntity, szKey, szValue ); - return ( ChunkFile_Ok ); - } - - SetKeyValue( pLoadEntity->pEntity, szKey, szValue ); - - return(ChunkFile_Ok); -} - - -static ChunkFileResult_t LoadEntityCallback( CChunkFile *pFile, int nParam ) -{ - if (num_entities == MAX_MAP_ENTITIES) - { - // Exits. - g_MapError.ReportError ("num_entities == MAX_MAP_ENTITIES"); - } - - entity_t *mapent = &entities[num_entities]; - num_entities++; - memset(mapent, 0, sizeof(*mapent)); - mapent->numbrushes = 0; - - LoadEntity_t LoadEntity; - LoadEntity.pEntity = mapent; - - // No default flags/contents - LoadEntity.nBaseFlags = 0; - LoadEntity.nBaseContents = 0; - - // - // Read the entity chunk. - // - ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadEntityKeyCallback, &LoadEntity); - - return eResult; -} - - -bool LoadEntsFromMapFile( char const *pFilename ) -{ - // - // Dummy this up for the texture handling. This can be removed when old .MAP file - // support is removed. - // - g_nMapFileVersion = 400; - - // - // Open the file. - // - CChunkFile File; - ChunkFileResult_t eResult = File.Open( pFilename, ChunkFile_Read ); - - if(eResult == ChunkFile_Ok) - { - num_entities = 0; - - // - // Set up handlers for the subchunks that we are interested in. - // - CChunkHandlerMap Handlers; - Handlers.AddHandler("entity", (ChunkHandler_t)LoadEntityCallback, 0); - - File.PushHandlers(&Handlers); - - // - // Read the sub-chunks. We ignore keys in the root of the file. - // - while (eResult == ChunkFile_Ok) - { - eResult = File.ReadChunk(); - } - - File.PopHandlers(); - return true; - } - else - { - Error("Error in LoadEntsFromMapFile (in-memory file): %s.\n", File.GetErrorText(eResult)); - return false; - } -} - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "map_shared.h" +#include "bsplib.h" +#include "cmdlib.h" + + +CMapError g_MapError; +int g_nMapFileVersion; + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pLoadEntity - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadEntityKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity) +{ + if (!stricmp(szKey, "classname")) + { + if (!stricmp(szValue, "func_detail")) + { + pLoadEntity->nBaseContents = CONTENTS_DETAIL; + } + else if (!stricmp(szValue, "func_ladder")) + { + pLoadEntity->nBaseContents = CONTENTS_LADDER; + } + else if (!stricmp(szValue, "func_water")) + { + pLoadEntity->nBaseContents = CONTENTS_WATER; + } + } + else if (!stricmp(szKey, "id")) + { + // UNDONE: flag entity errors by ID instead of index + //g_MapError.EntityState( atoi( szValue ) ); + // rename this field since DME code uses this name + SetKeyValue( pLoadEntity->pEntity, "hammerid", szValue ); + return(ChunkFile_Ok); + } + else if( !stricmp( szKey, "mapversion" ) ) + { + // .vmf map revision number + g_MapRevision = atoi( szValue ); + SetKeyValue( pLoadEntity->pEntity, szKey, szValue ); + return ( ChunkFile_Ok ); + } + + SetKeyValue( pLoadEntity->pEntity, szKey, szValue ); + + return(ChunkFile_Ok); +} + + +static ChunkFileResult_t LoadEntityCallback( CChunkFile *pFile, int nParam ) +{ + if (num_entities == MAX_MAP_ENTITIES) + { + // Exits. + g_MapError.ReportError ("num_entities == MAX_MAP_ENTITIES"); + } + + entity_t *mapent = &entities[num_entities]; + num_entities++; + memset(mapent, 0, sizeof(*mapent)); + mapent->numbrushes = 0; + + LoadEntity_t LoadEntity; + LoadEntity.pEntity = mapent; + + // No default flags/contents + LoadEntity.nBaseFlags = 0; + LoadEntity.nBaseContents = 0; + + // + // Read the entity chunk. + // + ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadEntityKeyCallback, &LoadEntity); + + return eResult; +} + + +bool LoadEntsFromMapFile( char const *pFilename ) +{ + // + // Dummy this up for the texture handling. This can be removed when old .MAP file + // support is removed. + // + g_nMapFileVersion = 400; + + // + // Open the file. + // + CChunkFile File; + ChunkFileResult_t eResult = File.Open( pFilename, ChunkFile_Read ); + + if(eResult == ChunkFile_Ok) + { + num_entities = 0; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler("entity", (ChunkHandler_t)LoadEntityCallback, 0); + + File.PushHandlers(&Handlers); + + // + // Read the sub-chunks. We ignore keys in the root of the file. + // + while (eResult == ChunkFile_Ok) + { + eResult = File.ReadChunk(); + } + + File.PopHandlers(); + return true; + } + else + { + Error("Error in LoadEntsFromMapFile (in-memory file): %s.\n", File.GetErrorText(eResult)); + return false; + } +} + + diff --git a/mp/src/utils/common/map_shared.h b/mp/src/utils/common/map_shared.h index 08e443a0..5f2b2b62 100644 --- a/mp/src/utils/common/map_shared.h +++ b/mp/src/utils/common/map_shared.h @@ -1,91 +1,91 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef MAP_SHARED_H -#define MAP_SHARED_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "ChunkFile.h" -#include "bsplib.h" -#include "cmdlib.h" - - -struct LoadEntity_t -{ - entity_t *pEntity; - int nID; - int nBaseFlags; - int nBaseContents; -}; - - -class CMapError -{ -public: - - void BrushState( int brushID ) - { - m_brushID = brushID; - } - - void BrushSide( int side ) - { - m_sideIndex = side; - } - - void TextureState( const char *pTextureName ) - { - Q_strncpy( m_textureName, pTextureName, sizeof( m_textureName ) ); - } - - void ClearState( void ) - { - BrushState( 0 ); - BrushSide( 0 ); - TextureState( "Not a Parse error!" ); - } - - //----------------------------------------------------------------------------- - // Purpose: Hook the map parse errors and report brush/ent/texture state - // Input : *pErrorString - - //----------------------------------------------------------------------------- - void ReportError( const char *pErrorString ) - { - Error( "Brush %i: %s\nSide %i\nTexture: %s\n", m_brushID, pErrorString, m_sideIndex, m_textureName ); - } - - //----------------------------------------------------------------------------- - // Purpose: Hook the map parse errors and report brush/ent/texture state without exiting. - // Input : pWarningString - - //----------------------------------------------------------------------------- - void ReportWarning( const char *pWarningString ) - { - printf( "Brush %i, Side %i: %s\n", m_brushID, m_sideIndex, pWarningString ); - } - -private: - - int m_brushID; - int m_sideIndex; - char m_textureName[80]; -}; - - -extern CMapError g_MapError; -extern int g_nMapFileVersion; - - -// Shared mapload code. -ChunkFileResult_t LoadEntityKeyCallback( const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity ); - -// Used by VRAD incremental lighting - only load ents from the file and -// fill in the global entities/num_entities array. -bool LoadEntsFromMapFile( char const *pFilename ); - -#endif // MAP_SHARED_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MAP_SHARED_H +#define MAP_SHARED_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "ChunkFile.h" +#include "bsplib.h" +#include "cmdlib.h" + + +struct LoadEntity_t +{ + entity_t *pEntity; + int nID; + int nBaseFlags; + int nBaseContents; +}; + + +class CMapError +{ +public: + + void BrushState( int brushID ) + { + m_brushID = brushID; + } + + void BrushSide( int side ) + { + m_sideIndex = side; + } + + void TextureState( const char *pTextureName ) + { + Q_strncpy( m_textureName, pTextureName, sizeof( m_textureName ) ); + } + + void ClearState( void ) + { + BrushState( 0 ); + BrushSide( 0 ); + TextureState( "Not a Parse error!" ); + } + + //----------------------------------------------------------------------------- + // Purpose: Hook the map parse errors and report brush/ent/texture state + // Input : *pErrorString - + //----------------------------------------------------------------------------- + void ReportError( const char *pErrorString ) + { + Error( "Brush %i: %s\nSide %i\nTexture: %s\n", m_brushID, pErrorString, m_sideIndex, m_textureName ); + } + + //----------------------------------------------------------------------------- + // Purpose: Hook the map parse errors and report brush/ent/texture state without exiting. + // Input : pWarningString - + //----------------------------------------------------------------------------- + void ReportWarning( const char *pWarningString ) + { + printf( "Brush %i, Side %i: %s\n", m_brushID, m_sideIndex, pWarningString ); + } + +private: + + int m_brushID; + int m_sideIndex; + char m_textureName[80]; +}; + + +extern CMapError g_MapError; +extern int g_nMapFileVersion; + + +// Shared mapload code. +ChunkFileResult_t LoadEntityKeyCallback( const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity ); + +// Used by VRAD incremental lighting - only load ents from the file and +// fill in the global entities/num_entities array. +bool LoadEntsFromMapFile( char const *pFilename ); + +#endif // MAP_SHARED_H diff --git a/mp/src/utils/common/movie.h b/mp/src/utils/common/movie.h index 78ba92fb..f9055296 100644 --- a/mp/src/utils/common/movie.h +++ b/mp/src/utils/common/movie.h @@ -1,34 +1,34 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#ifndef _MOVIE_H_ -#define _MOVIE_H_ - -/* - movie.h - - definitions and such for dumping screen shots to make a movie -*/ - -typedef struct -{ - unsigned long tag; - unsigned long size; -} movieblockheader_t; - - -typedef struct -{ - short width; - short height; - short depth; -} movieframe_t; - - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#ifndef _MOVIE_H_ +#define _MOVIE_H_ + +/* + movie.h + + definitions and such for dumping screen shots to make a movie +*/ + +typedef struct +{ + unsigned long tag; + unsigned long size; +} movieblockheader_t; + + +typedef struct +{ + short width; + short height; + short depth; +} movieframe_t; + + + #endif _MOVIE_H_ \ No newline at end of file diff --git a/mp/src/utils/common/mpi_stats.cpp b/mp/src/utils/common/mpi_stats.cpp index 8d9cc5e7..f5840cea 100644 --- a/mp/src/utils/common/mpi_stats.cpp +++ b/mp/src/utils/common/mpi_stats.cpp @@ -1,839 +1,839 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -// Nasty headers! -#include "MySqlDatabase.h" -#include "tier1/strtools.h" -#include "vmpi.h" -#include "vmpi_dispatch.h" -#include "mpi_stats.h" -#include "cmdlib.h" -#include "imysqlwrapper.h" -#include "threadhelpers.h" -#include "vmpi_tools_shared.h" -#include "tier0/icommandline.h" - -/* - --- MySQL code to create the databases, create the users, and set access privileges. --- You only need to ever run this once. - -create database vrad; - -use mysql; - -create user vrad_worker; -create user vmpi_browser; - --- This updates the "user" table, which is checked when someone tries to connect to the database. -grant select,insert,update on vrad.* to vrad_worker; -grant select on vrad.* to vmpi_browser; -flush privileges; - -/* - --- SQL code to (re)create the tables. - --- Master generates a unique job ID (in job_master_start) and sends it to workers. --- Each worker (and the master) make a job_worker_start, link it to the primary job ID, --- get their own unique ID, which represents that process in that job. --- All JobWorkerID fields link to the JobWorkerID field in job_worker_start. - --- NOTE: do a "use vrad" or "use vvis" first, depending on the DB you want to create. - - -use vrad; - - -drop table job_master_start; -create table job_master_start ( - JobID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, index id( JobID, MachineName(5) ), - BSPFilename TINYTEXT NOT NULL, - StartTime TIMESTAMP NOT NULL, - MachineName TEXT NOT NULL, - RunningTimeMS INTEGER UNSIGNED NOT NULL, - NumWorkers INTEGER UNSIGNED NOT NULL default 0 - ); - -drop table job_master_end; -create table job_master_end ( - JobID INTEGER UNSIGNED NOT NULL, PRIMARY KEY ( JobID ), - NumWorkersConnected SMALLINT UNSIGNED NOT NULL, - NumWorkersDisconnected SMALLINT UNSIGNED NOT NULL, - ErrorText TEXT NOT NULL - ); - -drop table job_worker_start; -create table job_worker_start ( - JobWorkerID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, - index index_jobid( JobID ), - index index_jobworkerid( JobWorkerID ), - - JobID INTEGER UNSIGNED NOT NULL, -- links to job_master_start::JobID - IsMaster BOOL NOT NULL, -- Set to 1 if this "worker" is the master process. - RunningTimeMS INTEGER UNSIGNED NOT NULL default 0, - MachineName TEXT NOT NULL, - WorkerState SMALLINT UNSIGNED NOT NULL default 0, -- 0 = disconnected, 1 = connected - NumWorkUnits INTEGER UNSIGNED NOT NULL default 0, -- how many work units this worker has completed - CurrentStage TINYTEXT NOT NULL, -- which compile stage is it on - Thread0WU INTEGER NOT NULL default 0, -- which WU thread 0 is on - Thread1WU INTEGER NOT NULL default 0, -- which WU thread 1 is on - Thread2WU INTEGER NOT NULL default 0, -- which WU thread 2 is on - Thread3WU INTEGER NOT NULL default 0 -- which WU thread 3 is on - ); - -drop table text_messages; -create table text_messages ( - JobWorkerID INTEGER UNSIGNED NOT NULL, index id( JobWorkerID, MessageIndex ), - MessageIndex INTEGER UNSIGNED NOT NULL, - Text TEXT NOT NULL - ); - -drop table graph_entry; -create table graph_entry ( - JobWorkerID INTEGER UNSIGNED NOT NULL, index id( JobWorkerID ), - MSSinceJobStart INTEGER UNSIGNED NOT NULL, - BytesSent INTEGER UNSIGNED NOT NULL, - BytesReceived INTEGER UNSIGNED NOT NULL - ); - -drop table events; -create table events ( - JobWorkerID INTEGER UNSIGNED NOT NULL, index id( JobWorkerID ), - Text TEXT NOT NULL - ); -*/ - - - -// Stats set by the app. -int g_nWorkersConnected = 0; -int g_nWorkersDisconnected = 0; - - -DWORD g_StatsStartTime; - -CMySqlDatabase *g_pDB = NULL; - -IMySQL *g_pSQL = NULL; -CSysModule *g_hMySQLDLL = NULL; - -char g_BSPFilename[256]; - -bool g_bMaster = false; -unsigned long g_JobPrimaryID = 0; // This represents this job, but doesn't link to a particular machine. -unsigned long g_JobWorkerID = 0; // A unique key in the DB that represents this machine in this job. -char g_MachineName[MAX_COMPUTERNAME_LENGTH+1] = {0}; - -unsigned long g_CurrentMessageIndex = 0; - - -HANDLE g_hPerfThread = NULL; -DWORD g_PerfThreadID = 0xFEFEFEFE; -HANDLE g_hPerfThreadExitEvent = NULL; - -// These are set by the app and they go into the database. -extern uint64 g_ThreadWUs[4]; - -extern uint64 VMPI_GetNumWorkUnitsCompleted( int iProc ); - - -// ---------------------------------------------------------------------------------------------------- // -// This is a helper class to build queries like the stream IO. -// ---------------------------------------------------------------------------------------------------- // - -class CMySQLQuery -{ -friend class CMySQL; - -public: - // This is like a sprintf, but it will grow the string as necessary. - void Format( const char *pFormat, ... ); - - int Execute( IMySQL *pDB ); - -private: - CUtlVector m_QueryText; -}; - - -void CMySQLQuery::Format( const char *pFormat, ... ) -{ - #define QUERYTEXT_GROWSIZE 1024 - - // This keeps growing the buffer and calling _vsnprintf until the buffer is - // large enough to hold all the data. - m_QueryText.SetSize( QUERYTEXT_GROWSIZE ); - while ( 1 ) - { - va_list marker; - va_start( marker, pFormat ); - int ret = _vsnprintf( m_QueryText.Base(), m_QueryText.Count(), pFormat, marker ); - va_end( marker ); - - if ( ret < 0 ) - { - m_QueryText.SetSize( m_QueryText.Count() + QUERYTEXT_GROWSIZE ); - } - else - { - m_QueryText[ m_QueryText.Count() - 1 ] = 0; - break; - } - } -} - - -int CMySQLQuery::Execute( IMySQL *pDB ) -{ - int ret = pDB->Execute( m_QueryText.Base() ); - m_QueryText.Purge(); - return ret; -} - - - -// ---------------------------------------------------------------------------------------------------- // -// This inserts the necessary backslashes in front of backslashes or quote characters. -// ---------------------------------------------------------------------------------------------------- // - -char* FormatStringForSQL( const char *pText ) -{ - // First, count the quotes in the string. We need to put a backslash in front of each one. - int nChars = 0; - const char *pCur = pText; - while ( *pCur != 0 ) - { - if ( *pCur == '\"' || *pCur == '\\' ) - ++nChars; - - ++pCur; - ++nChars; - } - - pCur = pText; - char *pRetVal = new char[nChars+1]; - for ( int i=0; i < nChars; ) - { - if ( *pCur == '\"' || *pCur == '\\' ) - pRetVal[i++] = '\\'; - - pRetVal[i++] = *pCur; - ++pCur; - } - pRetVal[nChars] = 0; - - return pRetVal; -} - - - -// -------------------------------------------------------------------------------- // -// Commands to add data to the database. -// -------------------------------------------------------------------------------- // -class CSQLDBCommandBase : public ISQLDBCommand -{ -public: - virtual ~CSQLDBCommandBase() - { - } - - virtual void deleteThis() - { - delete this; - } -}; - -class CSQLDBCommand_WorkerStats : public CSQLDBCommandBase -{ -public: - virtual int RunCommand() - { - int nCurConnections = VMPI_GetCurrentNumberOfConnections(); - - - // Update the NumWorkers entry. - char query[2048]; - Q_snprintf( query, sizeof( query ), "update job_master_start set NumWorkers=%d where JobID=%lu", - nCurConnections, - g_JobPrimaryID ); - g_pSQL->Execute( query ); - - - // Update the job_master_worker_stats stuff. - for ( int i=1; i < nCurConnections; i++ ) - { - unsigned long jobWorkerID = VMPI_GetJobWorkerID( i ); - - if ( jobWorkerID != 0xFFFFFFFF ) - { - Q_snprintf( query, sizeof( query ), "update " - "job_worker_start set WorkerState=%d, NumWorkUnits=%d where JobWorkerID=%lu", - VMPI_IsProcConnected( i ), - (int) VMPI_GetNumWorkUnitsCompleted( i ), - VMPI_GetJobWorkerID( i ) - ); - g_pSQL->Execute( query ); - } - } - return 1; - } -}; - -class CSQLDBCommand_JobMasterEnd : public CSQLDBCommandBase -{ -public: - - virtual int RunCommand() - { - CMySQLQuery query; - query.Format( "insert into job_master_end values ( %lu, %d, %d, \"no errors\" )", g_JobPrimaryID, g_nWorkersConnected, g_nWorkersDisconnected ); - query.Execute( g_pSQL ); - - // Now set RunningTimeMS. - unsigned long runningTimeMS = GetTickCount() - g_StatsStartTime; - query.Format( "update job_master_start set RunningTimeMS=%lu where JobID=%lu", runningTimeMS, g_JobPrimaryID ); - query.Execute( g_pSQL ); - return 1; - } -}; - - -void UpdateJobWorkerRunningTime() -{ - unsigned long runningTimeMS = GetTickCount() - g_StatsStartTime; - - char curStage[256]; - VMPI_GetCurrentStage( curStage, sizeof( curStage ) ); - - CMySQLQuery query; - query.Format( "update job_worker_start set RunningTimeMS=%lu, CurrentStage=\"%s\", " - "Thread0WU=%d, Thread1WU=%d, Thread2WU=%d, Thread3WU=%d where JobWorkerID=%lu", - runningTimeMS, - curStage, - (int) g_ThreadWUs[0], - (int) g_ThreadWUs[1], - (int) g_ThreadWUs[2], - (int) g_ThreadWUs[3], - g_JobWorkerID ); - query.Execute( g_pSQL ); -} - - -class CSQLDBCommand_GraphEntry : public CSQLDBCommandBase -{ -public: - - CSQLDBCommand_GraphEntry( DWORD msTime, DWORD nBytesSent, DWORD nBytesReceived ) - { - m_msTime = msTime; - m_nBytesSent = nBytesSent; - m_nBytesReceived = nBytesReceived; - } - - virtual int RunCommand() - { - CMySQLQuery query; - query.Format( "insert into graph_entry (JobWorkerID, MSSinceJobStart, BytesSent, BytesReceived) " - "values ( %lu, %lu, %lu, %lu )", - g_JobWorkerID, - m_msTime, - m_nBytesSent, - m_nBytesReceived ); - - query.Execute( g_pSQL ); - - UpdateJobWorkerRunningTime(); - - ++g_CurrentMessageIndex; - return 1; - } - - DWORD m_nBytesSent; - DWORD m_nBytesReceived; - DWORD m_msTime; -}; - - - -class CSQLDBCommand_TextMessage : public CSQLDBCommandBase -{ -public: - - CSQLDBCommand_TextMessage( const char *pText ) - { - m_pText = FormatStringForSQL( pText ); - } - - virtual ~CSQLDBCommand_TextMessage() - { - delete [] m_pText; - } - - virtual int RunCommand() - { - CMySQLQuery query; - query.Format( "insert into text_messages (JobWorkerID, MessageIndex, Text) values ( %lu, %lu, \"%s\" )", g_JobWorkerID, g_CurrentMessageIndex, m_pText ); - query.Execute( g_pSQL ); - - ++g_CurrentMessageIndex; - return 1; - } - - char *m_pText; -}; - - -// -------------------------------------------------------------------------------- // -// Internal helpers. -// -------------------------------------------------------------------------------- // - -// This is the spew output before it has connected to the MySQL database. -CCriticalSection g_SpewTextCS; -CUtlVector g_SpewText( 1024 ); - - -void VMPI_Stats_SpewHook( const char *pMsg ) -{ - CCriticalSectionLock csLock( &g_SpewTextCS ); - csLock.Lock(); - - // Queue the text up so we can send it to the DB right away when we connect. - g_SpewText.AddMultipleToTail( strlen( pMsg ), pMsg ); -} - - -void PerfThread_SendSpewText() -{ - // Send the spew text to the database. - CCriticalSectionLock csLock( &g_SpewTextCS ); - csLock.Lock(); - - if ( g_SpewText.Count() > 0 ) - { - g_SpewText.AddToTail( 0 ); - - if ( g_bMPI_StatsTextOutput ) - { - g_pDB->AddCommandToQueue( new CSQLDBCommand_TextMessage( g_SpewText.Base() ), NULL ); - } - else - { - // Just show one message in the vmpi_job_watch window to let them know that they need - // to use a command line option to get the output. - static bool bFirst = true; - if ( bFirst ) - { - char msg[512]; - V_snprintf( msg, sizeof( msg ), "%s not enabled", VMPI_GetParamString( mpi_Stats_TextOutput ) ); - bFirst = false; - g_pDB->AddCommandToQueue( new CSQLDBCommand_TextMessage( msg ), NULL ); - } - } - - g_SpewText.RemoveAll(); - } - - csLock.Unlock(); -} - - -void PerfThread_AddGraphEntry( DWORD startTicks, DWORD &lastSent, DWORD &lastReceived ) -{ - // Send the graph entry with data transmission info. - DWORD curSent = g_nBytesSent + g_nMulticastBytesSent; - DWORD curReceived = g_nBytesReceived + g_nMulticastBytesReceived; - - g_pDB->AddCommandToQueue( - new CSQLDBCommand_GraphEntry( - GetTickCount() - startTicks, - curSent - lastSent, - curReceived - lastReceived ), - NULL ); - - lastSent = curSent; - lastReceived = curReceived; -} - - -// This function adds a graph_entry into the database periodically. -DWORD WINAPI PerfThreadFn( LPVOID pParameter ) -{ - DWORD lastSent = 0; - DWORD lastReceived = 0; - DWORD startTicks = GetTickCount(); - - while ( WaitForSingleObject( g_hPerfThreadExitEvent, 1000 ) != WAIT_OBJECT_0 ) - { - PerfThread_AddGraphEntry( startTicks, lastSent, lastReceived ); - - // Send updates for text output. - PerfThread_SendSpewText(); - - // If we're the master, update all the worker stats. - if ( g_bMaster ) - { - g_pDB->AddCommandToQueue( - new CSQLDBCommand_WorkerStats, - NULL ); - } - } - - // Add the remaining text and one last graph entry (which will include the current stage info). - PerfThread_SendSpewText(); - PerfThread_AddGraphEntry( startTicks, lastSent, lastReceived ); - - SetEvent( g_hPerfThreadExitEvent ); - return 0; -} - - -// -------------------------------------------------------------------------------- // -// VMPI_Stats interface. -// -------------------------------------------------------------------------------- // - -void VMPI_Stats_InstallSpewHook() -{ - InstallExtraSpewHook( VMPI_Stats_SpewHook ); -} - - -void UnloadMySQLWrapper() -{ - if ( g_hMySQLDLL ) - { - if ( g_pSQL ) - { - g_pSQL->Release(); - g_pSQL = NULL; - } - - Sys_UnloadModule( g_hMySQLDLL ); - g_hMySQLDLL = NULL; - } -} - - -bool LoadMySQLWrapper( - const char *pHostName, - const char *pDBName, - const char *pUserName - ) -{ - UnloadMySQLWrapper(); - - // Load the DLL and the interface. - if ( !Sys_LoadInterface( "mysql_wrapper", MYSQL_WRAPPER_VERSION_NAME, &g_hMySQLDLL, (void**)&g_pSQL ) ) - return false; - - // Try to init the database. - if ( !g_pSQL->InitMySQL( pDBName, pHostName, pUserName ) ) - { - UnloadMySQLWrapper(); - return false; - } - - return true; -} - - -bool VMPI_Stats_Init_Master( - const char *pHostName, - const char *pDBName, - const char *pUserName, - const char *pBSPFilename, - unsigned long *pDBJobID ) -{ - Assert( !g_pDB ); - - g_bMaster = true; - - // Connect the database. - g_pDB = new CMySqlDatabase; - if ( !g_pDB || !g_pDB->Initialize() || !LoadMySQLWrapper( pHostName, pDBName, pUserName ) ) - { - delete g_pDB; - g_pDB = NULL; - return false; - } - - DWORD size = sizeof( g_MachineName ); - GetComputerName( g_MachineName, &size ); - - // Create the job_master_start row. - Q_FileBase( pBSPFilename, g_BSPFilename, sizeof( g_BSPFilename ) ); - - g_JobPrimaryID = 0; - CMySQLQuery query; - query.Format( "insert into job_master_start ( BSPFilename, StartTime, MachineName, RunningTimeMS ) values ( \"%s\", null, \"%s\", %lu )", g_BSPFilename, g_MachineName, RUNNINGTIME_MS_SENTINEL ); - query.Execute( g_pSQL ); - - g_JobPrimaryID = g_pSQL->InsertID(); - if ( g_JobPrimaryID == 0 ) - { - delete g_pDB; - g_pDB = NULL; - return false; - } - - - // Now init the worker portion. - *pDBJobID = g_JobPrimaryID; - return VMPI_Stats_Init_Worker( NULL, NULL, NULL, g_JobPrimaryID ); -} - - - -bool VMPI_Stats_Init_Worker( const char *pHostName, const char *pDBName, const char *pUserName, unsigned long DBJobID ) -{ - g_StatsStartTime = GetTickCount(); - - // If pDBServerName is null, then we're the master and we just want to make the job_worker_start entry. - if ( pHostName ) - { - Assert( !g_pDB ); - - // Connect the database. - g_pDB = new CMySqlDatabase; - if ( !g_pDB || !g_pDB->Initialize() || !LoadMySQLWrapper( pHostName, pDBName, pUserName ) ) - { - delete g_pDB; - g_pDB = NULL; - return false; - } - - // Get our machine name to store in the database. - DWORD size = sizeof( g_MachineName ); - GetComputerName( g_MachineName, &size ); - } - - - g_JobPrimaryID = DBJobID; - g_JobWorkerID = 0; - - CMySQLQuery query; - query.Format( "insert into job_worker_start ( JobID, CurrentStage, IsMaster, MachineName ) values ( %lu, \"none\", %d, \"%s\" )", - g_JobPrimaryID, g_bMaster, g_MachineName ); - query.Execute( g_pSQL ); - - g_JobWorkerID = g_pSQL->InsertID(); - if ( g_JobWorkerID == 0 ) - { - delete g_pDB; - g_pDB = NULL; - return false; - } - - // Now create a thread that samples perf data and stores it in the database. - g_hPerfThreadExitEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); - g_hPerfThread = CreateThread( - NULL, - 0, - PerfThreadFn, - NULL, - 0, - &g_PerfThreadID ); - - return true; -} - - -void VMPI_Stats_Term() -{ - if ( !g_pDB ) - return; - - // Stop the thread. - SetEvent( g_hPerfThreadExitEvent ); - WaitForSingleObject( g_hPerfThread, INFINITE ); - - CloseHandle( g_hPerfThreadExitEvent ); - g_hPerfThreadExitEvent = NULL; - - CloseHandle( g_hPerfThread ); - g_hPerfThread = NULL; - - if ( g_bMaster ) - { - // (Write a job_master_end entry here). - g_pDB->AddCommandToQueue( new CSQLDBCommand_JobMasterEnd, NULL ); - } - - // Wait for up to a second for the DB to finish writing its data. - DWORD startTime = GetTickCount(); - while ( GetTickCount() - startTime < 1000 ) - { - if ( g_pDB->QueriesInOutQueue() == 0 ) - break; - } - - delete g_pDB; - g_pDB = NULL; - - UnloadMySQLWrapper(); -} - - -static bool ReadStringFromFile( FILE *fp, char *pStr, int strSize ) -{ - int i=0; - for ( i; i < strSize-2; i++ ) - { - if ( fread( &pStr[i], 1, 1, fp ) != 1 || - pStr[i] == '\n' ) - { - break; - } - } - - pStr[i] = 0; - return i != 0; -} - - -// This looks for pDBInfoFilename in the same path as pBaseExeFilename. -// The file has 3 lines: machine name (with database), database name, username -void GetDBInfo( const char *pDBInfoFilename, CDBInfo *pInfo ) -{ - char baseExeFilename[512]; - if ( !GetModuleFileName( GetModuleHandle( NULL ), baseExeFilename, sizeof( baseExeFilename ) ) ) - Error( "GetModuleFileName failed." ); - - // Look for the info file in the same directory as the exe. - char dbInfoFilename[512]; - Q_strncpy( dbInfoFilename, baseExeFilename, sizeof( dbInfoFilename ) ); - Q_StripFilename( dbInfoFilename ); - - if ( dbInfoFilename[0] == 0 ) - Q_strncpy( dbInfoFilename, ".", sizeof( dbInfoFilename ) ); - - Q_strncat( dbInfoFilename, "/", sizeof( dbInfoFilename ), COPY_ALL_CHARACTERS ); - Q_strncat( dbInfoFilename, pDBInfoFilename, sizeof( dbInfoFilename ), COPY_ALL_CHARACTERS ); - - FILE *fp = fopen( dbInfoFilename, "rt" ); - if ( !fp ) - { - Error( "Can't open %s for database info.\n", dbInfoFilename ); - } - - if ( !ReadStringFromFile( fp, pInfo->m_HostName, sizeof( pInfo->m_HostName ) ) || - !ReadStringFromFile( fp, pInfo->m_DBName, sizeof( pInfo->m_DBName ) ) || - !ReadStringFromFile( fp, pInfo->m_UserName, sizeof( pInfo->m_UserName ) ) - ) - { - Error( "%s is not a valid database info file.\n", dbInfoFilename ); - } - - fclose( fp ); -} - - -void RunJobWatchApp( char *pCmdLine ) -{ - STARTUPINFO si; - memset( &si, 0, sizeof( si ) ); - si.cb = sizeof( si ); - - PROCESS_INFORMATION pi; - memset( &pi, 0, sizeof( pi ) ); - - // Working directory should be the same as our exe's directory. - char dirName[512]; - if ( GetModuleFileName( NULL, dirName, sizeof( dirName ) ) != 0 ) - { - char *s1 = V_strrchr( dirName, '\\' ); - char *s2 = V_strrchr( dirName, '/' ); - if ( s1 || s2 ) - { - // Get rid of the last slash. - s1 = max( s1, s2 ); - s1[0] = 0; - - if ( !CreateProcess( - NULL, - pCmdLine, - NULL, // security - NULL, - TRUE, - 0, // flags - NULL, // environment - dirName, // current directory - &si, - &pi ) ) - { - Warning( "%s - error launching '%s'\n", VMPI_GetParamString( mpi_Job_Watch ), pCmdLine ); - } - } - } -} - - -void StatsDB_InitStatsDatabase( - int argc, - char **argv, - const char *pDBInfoFilename ) -{ - // Did they disable the stats database? - if ( !g_bMPI_Stats && !VMPI_IsParamUsed( mpi_Job_Watch ) ) - return; - - unsigned long jobPrimaryID; - - // Now open the DB. - if ( g_bMPIMaster ) - { - CDBInfo dbInfo; - GetDBInfo( pDBInfoFilename, &dbInfo ); - - if ( !VMPI_Stats_Init_Master( dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName, argv[argc-1], &jobPrimaryID ) ) - { - Warning( "VMPI_Stats_Init_Master( %s, %s, %s ) failed.\n", dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName ); - - // Tell the workers not to use stats. - dbInfo.m_HostName[0] = 0; - } - - char cmdLine[2048]; - Q_snprintf( cmdLine, sizeof( cmdLine ), "vmpi_job_watch -JobID %d", jobPrimaryID ); - - Msg( "\nTo watch this job, run this command line:\n%s\n\n", cmdLine ); - - if ( VMPI_IsParamUsed( mpi_Job_Watch ) ) - { - // Convenience thing to automatically launch the job watch for this job. - RunJobWatchApp( cmdLine ); - } - - // Send the database info to all the workers. - SendDBInfo( &dbInfo, jobPrimaryID ); - } - else - { - // Wait to get DB info so we can connect to the MySQL database. - CDBInfo dbInfo; - unsigned long jobPrimaryID; - RecvDBInfo( &dbInfo, &jobPrimaryID ); - - if ( dbInfo.m_HostName[0] != 0 ) - { - if ( !VMPI_Stats_Init_Worker( dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName, jobPrimaryID ) ) - Error( "VMPI_Stats_Init_Worker( %s, %s, %s, %d ) failed.\n", dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName, jobPrimaryID ); - } - } -} - - -unsigned long StatsDB_GetUniqueJobID() -{ - return g_JobPrimaryID; -} - - -unsigned long VMPI_Stats_GetJobWorkerID() -{ - return g_JobWorkerID; +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// Nasty headers! +#include "MySqlDatabase.h" +#include "tier1/strtools.h" +#include "vmpi.h" +#include "vmpi_dispatch.h" +#include "mpi_stats.h" +#include "cmdlib.h" +#include "imysqlwrapper.h" +#include "threadhelpers.h" +#include "vmpi_tools_shared.h" +#include "tier0/icommandline.h" + +/* + +-- MySQL code to create the databases, create the users, and set access privileges. +-- You only need to ever run this once. + +create database vrad; + +use mysql; + +create user vrad_worker; +create user vmpi_browser; + +-- This updates the "user" table, which is checked when someone tries to connect to the database. +grant select,insert,update on vrad.* to vrad_worker; +grant select on vrad.* to vmpi_browser; +flush privileges; + +/* + +-- SQL code to (re)create the tables. + +-- Master generates a unique job ID (in job_master_start) and sends it to workers. +-- Each worker (and the master) make a job_worker_start, link it to the primary job ID, +-- get their own unique ID, which represents that process in that job. +-- All JobWorkerID fields link to the JobWorkerID field in job_worker_start. + +-- NOTE: do a "use vrad" or "use vvis" first, depending on the DB you want to create. + + +use vrad; + + +drop table job_master_start; +create table job_master_start ( + JobID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, index id( JobID, MachineName(5) ), + BSPFilename TINYTEXT NOT NULL, + StartTime TIMESTAMP NOT NULL, + MachineName TEXT NOT NULL, + RunningTimeMS INTEGER UNSIGNED NOT NULL, + NumWorkers INTEGER UNSIGNED NOT NULL default 0 + ); + +drop table job_master_end; +create table job_master_end ( + JobID INTEGER UNSIGNED NOT NULL, PRIMARY KEY ( JobID ), + NumWorkersConnected SMALLINT UNSIGNED NOT NULL, + NumWorkersDisconnected SMALLINT UNSIGNED NOT NULL, + ErrorText TEXT NOT NULL + ); + +drop table job_worker_start; +create table job_worker_start ( + JobWorkerID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, + index index_jobid( JobID ), + index index_jobworkerid( JobWorkerID ), + + JobID INTEGER UNSIGNED NOT NULL, -- links to job_master_start::JobID + IsMaster BOOL NOT NULL, -- Set to 1 if this "worker" is the master process. + RunningTimeMS INTEGER UNSIGNED NOT NULL default 0, + MachineName TEXT NOT NULL, + WorkerState SMALLINT UNSIGNED NOT NULL default 0, -- 0 = disconnected, 1 = connected + NumWorkUnits INTEGER UNSIGNED NOT NULL default 0, -- how many work units this worker has completed + CurrentStage TINYTEXT NOT NULL, -- which compile stage is it on + Thread0WU INTEGER NOT NULL default 0, -- which WU thread 0 is on + Thread1WU INTEGER NOT NULL default 0, -- which WU thread 1 is on + Thread2WU INTEGER NOT NULL default 0, -- which WU thread 2 is on + Thread3WU INTEGER NOT NULL default 0 -- which WU thread 3 is on + ); + +drop table text_messages; +create table text_messages ( + JobWorkerID INTEGER UNSIGNED NOT NULL, index id( JobWorkerID, MessageIndex ), + MessageIndex INTEGER UNSIGNED NOT NULL, + Text TEXT NOT NULL + ); + +drop table graph_entry; +create table graph_entry ( + JobWorkerID INTEGER UNSIGNED NOT NULL, index id( JobWorkerID ), + MSSinceJobStart INTEGER UNSIGNED NOT NULL, + BytesSent INTEGER UNSIGNED NOT NULL, + BytesReceived INTEGER UNSIGNED NOT NULL + ); + +drop table events; +create table events ( + JobWorkerID INTEGER UNSIGNED NOT NULL, index id( JobWorkerID ), + Text TEXT NOT NULL + ); +*/ + + + +// Stats set by the app. +int g_nWorkersConnected = 0; +int g_nWorkersDisconnected = 0; + + +DWORD g_StatsStartTime; + +CMySqlDatabase *g_pDB = NULL; + +IMySQL *g_pSQL = NULL; +CSysModule *g_hMySQLDLL = NULL; + +char g_BSPFilename[256]; + +bool g_bMaster = false; +unsigned long g_JobPrimaryID = 0; // This represents this job, but doesn't link to a particular machine. +unsigned long g_JobWorkerID = 0; // A unique key in the DB that represents this machine in this job. +char g_MachineName[MAX_COMPUTERNAME_LENGTH+1] = {0}; + +unsigned long g_CurrentMessageIndex = 0; + + +HANDLE g_hPerfThread = NULL; +DWORD g_PerfThreadID = 0xFEFEFEFE; +HANDLE g_hPerfThreadExitEvent = NULL; + +// These are set by the app and they go into the database. +extern uint64 g_ThreadWUs[4]; + +extern uint64 VMPI_GetNumWorkUnitsCompleted( int iProc ); + + +// ---------------------------------------------------------------------------------------------------- // +// This is a helper class to build queries like the stream IO. +// ---------------------------------------------------------------------------------------------------- // + +class CMySQLQuery +{ +friend class CMySQL; + +public: + // This is like a sprintf, but it will grow the string as necessary. + void Format( const char *pFormat, ... ); + + int Execute( IMySQL *pDB ); + +private: + CUtlVector m_QueryText; +}; + + +void CMySQLQuery::Format( const char *pFormat, ... ) +{ + #define QUERYTEXT_GROWSIZE 1024 + + // This keeps growing the buffer and calling _vsnprintf until the buffer is + // large enough to hold all the data. + m_QueryText.SetSize( QUERYTEXT_GROWSIZE ); + while ( 1 ) + { + va_list marker; + va_start( marker, pFormat ); + int ret = _vsnprintf( m_QueryText.Base(), m_QueryText.Count(), pFormat, marker ); + va_end( marker ); + + if ( ret < 0 ) + { + m_QueryText.SetSize( m_QueryText.Count() + QUERYTEXT_GROWSIZE ); + } + else + { + m_QueryText[ m_QueryText.Count() - 1 ] = 0; + break; + } + } +} + + +int CMySQLQuery::Execute( IMySQL *pDB ) +{ + int ret = pDB->Execute( m_QueryText.Base() ); + m_QueryText.Purge(); + return ret; +} + + + +// ---------------------------------------------------------------------------------------------------- // +// This inserts the necessary backslashes in front of backslashes or quote characters. +// ---------------------------------------------------------------------------------------------------- // + +char* FormatStringForSQL( const char *pText ) +{ + // First, count the quotes in the string. We need to put a backslash in front of each one. + int nChars = 0; + const char *pCur = pText; + while ( *pCur != 0 ) + { + if ( *pCur == '\"' || *pCur == '\\' ) + ++nChars; + + ++pCur; + ++nChars; + } + + pCur = pText; + char *pRetVal = new char[nChars+1]; + for ( int i=0; i < nChars; ) + { + if ( *pCur == '\"' || *pCur == '\\' ) + pRetVal[i++] = '\\'; + + pRetVal[i++] = *pCur; + ++pCur; + } + pRetVal[nChars] = 0; + + return pRetVal; +} + + + +// -------------------------------------------------------------------------------- // +// Commands to add data to the database. +// -------------------------------------------------------------------------------- // +class CSQLDBCommandBase : public ISQLDBCommand +{ +public: + virtual ~CSQLDBCommandBase() + { + } + + virtual void deleteThis() + { + delete this; + } +}; + +class CSQLDBCommand_WorkerStats : public CSQLDBCommandBase +{ +public: + virtual int RunCommand() + { + int nCurConnections = VMPI_GetCurrentNumberOfConnections(); + + + // Update the NumWorkers entry. + char query[2048]; + Q_snprintf( query, sizeof( query ), "update job_master_start set NumWorkers=%d where JobID=%lu", + nCurConnections, + g_JobPrimaryID ); + g_pSQL->Execute( query ); + + + // Update the job_master_worker_stats stuff. + for ( int i=1; i < nCurConnections; i++ ) + { + unsigned long jobWorkerID = VMPI_GetJobWorkerID( i ); + + if ( jobWorkerID != 0xFFFFFFFF ) + { + Q_snprintf( query, sizeof( query ), "update " + "job_worker_start set WorkerState=%d, NumWorkUnits=%d where JobWorkerID=%lu", + VMPI_IsProcConnected( i ), + (int) VMPI_GetNumWorkUnitsCompleted( i ), + VMPI_GetJobWorkerID( i ) + ); + g_pSQL->Execute( query ); + } + } + return 1; + } +}; + +class CSQLDBCommand_JobMasterEnd : public CSQLDBCommandBase +{ +public: + + virtual int RunCommand() + { + CMySQLQuery query; + query.Format( "insert into job_master_end values ( %lu, %d, %d, \"no errors\" )", g_JobPrimaryID, g_nWorkersConnected, g_nWorkersDisconnected ); + query.Execute( g_pSQL ); + + // Now set RunningTimeMS. + unsigned long runningTimeMS = GetTickCount() - g_StatsStartTime; + query.Format( "update job_master_start set RunningTimeMS=%lu where JobID=%lu", runningTimeMS, g_JobPrimaryID ); + query.Execute( g_pSQL ); + return 1; + } +}; + + +void UpdateJobWorkerRunningTime() +{ + unsigned long runningTimeMS = GetTickCount() - g_StatsStartTime; + + char curStage[256]; + VMPI_GetCurrentStage( curStage, sizeof( curStage ) ); + + CMySQLQuery query; + query.Format( "update job_worker_start set RunningTimeMS=%lu, CurrentStage=\"%s\", " + "Thread0WU=%d, Thread1WU=%d, Thread2WU=%d, Thread3WU=%d where JobWorkerID=%lu", + runningTimeMS, + curStage, + (int) g_ThreadWUs[0], + (int) g_ThreadWUs[1], + (int) g_ThreadWUs[2], + (int) g_ThreadWUs[3], + g_JobWorkerID ); + query.Execute( g_pSQL ); +} + + +class CSQLDBCommand_GraphEntry : public CSQLDBCommandBase +{ +public: + + CSQLDBCommand_GraphEntry( DWORD msTime, DWORD nBytesSent, DWORD nBytesReceived ) + { + m_msTime = msTime; + m_nBytesSent = nBytesSent; + m_nBytesReceived = nBytesReceived; + } + + virtual int RunCommand() + { + CMySQLQuery query; + query.Format( "insert into graph_entry (JobWorkerID, MSSinceJobStart, BytesSent, BytesReceived) " + "values ( %lu, %lu, %lu, %lu )", + g_JobWorkerID, + m_msTime, + m_nBytesSent, + m_nBytesReceived ); + + query.Execute( g_pSQL ); + + UpdateJobWorkerRunningTime(); + + ++g_CurrentMessageIndex; + return 1; + } + + DWORD m_nBytesSent; + DWORD m_nBytesReceived; + DWORD m_msTime; +}; + + + +class CSQLDBCommand_TextMessage : public CSQLDBCommandBase +{ +public: + + CSQLDBCommand_TextMessage( const char *pText ) + { + m_pText = FormatStringForSQL( pText ); + } + + virtual ~CSQLDBCommand_TextMessage() + { + delete [] m_pText; + } + + virtual int RunCommand() + { + CMySQLQuery query; + query.Format( "insert into text_messages (JobWorkerID, MessageIndex, Text) values ( %lu, %lu, \"%s\" )", g_JobWorkerID, g_CurrentMessageIndex, m_pText ); + query.Execute( g_pSQL ); + + ++g_CurrentMessageIndex; + return 1; + } + + char *m_pText; +}; + + +// -------------------------------------------------------------------------------- // +// Internal helpers. +// -------------------------------------------------------------------------------- // + +// This is the spew output before it has connected to the MySQL database. +CCriticalSection g_SpewTextCS; +CUtlVector g_SpewText( 1024 ); + + +void VMPI_Stats_SpewHook( const char *pMsg ) +{ + CCriticalSectionLock csLock( &g_SpewTextCS ); + csLock.Lock(); + + // Queue the text up so we can send it to the DB right away when we connect. + g_SpewText.AddMultipleToTail( strlen( pMsg ), pMsg ); +} + + +void PerfThread_SendSpewText() +{ + // Send the spew text to the database. + CCriticalSectionLock csLock( &g_SpewTextCS ); + csLock.Lock(); + + if ( g_SpewText.Count() > 0 ) + { + g_SpewText.AddToTail( 0 ); + + if ( g_bMPI_StatsTextOutput ) + { + g_pDB->AddCommandToQueue( new CSQLDBCommand_TextMessage( g_SpewText.Base() ), NULL ); + } + else + { + // Just show one message in the vmpi_job_watch window to let them know that they need + // to use a command line option to get the output. + static bool bFirst = true; + if ( bFirst ) + { + char msg[512]; + V_snprintf( msg, sizeof( msg ), "%s not enabled", VMPI_GetParamString( mpi_Stats_TextOutput ) ); + bFirst = false; + g_pDB->AddCommandToQueue( new CSQLDBCommand_TextMessage( msg ), NULL ); + } + } + + g_SpewText.RemoveAll(); + } + + csLock.Unlock(); +} + + +void PerfThread_AddGraphEntry( DWORD startTicks, DWORD &lastSent, DWORD &lastReceived ) +{ + // Send the graph entry with data transmission info. + DWORD curSent = g_nBytesSent + g_nMulticastBytesSent; + DWORD curReceived = g_nBytesReceived + g_nMulticastBytesReceived; + + g_pDB->AddCommandToQueue( + new CSQLDBCommand_GraphEntry( + GetTickCount() - startTicks, + curSent - lastSent, + curReceived - lastReceived ), + NULL ); + + lastSent = curSent; + lastReceived = curReceived; +} + + +// This function adds a graph_entry into the database periodically. +DWORD WINAPI PerfThreadFn( LPVOID pParameter ) +{ + DWORD lastSent = 0; + DWORD lastReceived = 0; + DWORD startTicks = GetTickCount(); + + while ( WaitForSingleObject( g_hPerfThreadExitEvent, 1000 ) != WAIT_OBJECT_0 ) + { + PerfThread_AddGraphEntry( startTicks, lastSent, lastReceived ); + + // Send updates for text output. + PerfThread_SendSpewText(); + + // If we're the master, update all the worker stats. + if ( g_bMaster ) + { + g_pDB->AddCommandToQueue( + new CSQLDBCommand_WorkerStats, + NULL ); + } + } + + // Add the remaining text and one last graph entry (which will include the current stage info). + PerfThread_SendSpewText(); + PerfThread_AddGraphEntry( startTicks, lastSent, lastReceived ); + + SetEvent( g_hPerfThreadExitEvent ); + return 0; +} + + +// -------------------------------------------------------------------------------- // +// VMPI_Stats interface. +// -------------------------------------------------------------------------------- // + +void VMPI_Stats_InstallSpewHook() +{ + InstallExtraSpewHook( VMPI_Stats_SpewHook ); +} + + +void UnloadMySQLWrapper() +{ + if ( g_hMySQLDLL ) + { + if ( g_pSQL ) + { + g_pSQL->Release(); + g_pSQL = NULL; + } + + Sys_UnloadModule( g_hMySQLDLL ); + g_hMySQLDLL = NULL; + } +} + + +bool LoadMySQLWrapper( + const char *pHostName, + const char *pDBName, + const char *pUserName + ) +{ + UnloadMySQLWrapper(); + + // Load the DLL and the interface. + if ( !Sys_LoadInterface( "mysql_wrapper", MYSQL_WRAPPER_VERSION_NAME, &g_hMySQLDLL, (void**)&g_pSQL ) ) + return false; + + // Try to init the database. + if ( !g_pSQL->InitMySQL( pDBName, pHostName, pUserName ) ) + { + UnloadMySQLWrapper(); + return false; + } + + return true; +} + + +bool VMPI_Stats_Init_Master( + const char *pHostName, + const char *pDBName, + const char *pUserName, + const char *pBSPFilename, + unsigned long *pDBJobID ) +{ + Assert( !g_pDB ); + + g_bMaster = true; + + // Connect the database. + g_pDB = new CMySqlDatabase; + if ( !g_pDB || !g_pDB->Initialize() || !LoadMySQLWrapper( pHostName, pDBName, pUserName ) ) + { + delete g_pDB; + g_pDB = NULL; + return false; + } + + DWORD size = sizeof( g_MachineName ); + GetComputerName( g_MachineName, &size ); + + // Create the job_master_start row. + Q_FileBase( pBSPFilename, g_BSPFilename, sizeof( g_BSPFilename ) ); + + g_JobPrimaryID = 0; + CMySQLQuery query; + query.Format( "insert into job_master_start ( BSPFilename, StartTime, MachineName, RunningTimeMS ) values ( \"%s\", null, \"%s\", %lu )", g_BSPFilename, g_MachineName, RUNNINGTIME_MS_SENTINEL ); + query.Execute( g_pSQL ); + + g_JobPrimaryID = g_pSQL->InsertID(); + if ( g_JobPrimaryID == 0 ) + { + delete g_pDB; + g_pDB = NULL; + return false; + } + + + // Now init the worker portion. + *pDBJobID = g_JobPrimaryID; + return VMPI_Stats_Init_Worker( NULL, NULL, NULL, g_JobPrimaryID ); +} + + + +bool VMPI_Stats_Init_Worker( const char *pHostName, const char *pDBName, const char *pUserName, unsigned long DBJobID ) +{ + g_StatsStartTime = GetTickCount(); + + // If pDBServerName is null, then we're the master and we just want to make the job_worker_start entry. + if ( pHostName ) + { + Assert( !g_pDB ); + + // Connect the database. + g_pDB = new CMySqlDatabase; + if ( !g_pDB || !g_pDB->Initialize() || !LoadMySQLWrapper( pHostName, pDBName, pUserName ) ) + { + delete g_pDB; + g_pDB = NULL; + return false; + } + + // Get our machine name to store in the database. + DWORD size = sizeof( g_MachineName ); + GetComputerName( g_MachineName, &size ); + } + + + g_JobPrimaryID = DBJobID; + g_JobWorkerID = 0; + + CMySQLQuery query; + query.Format( "insert into job_worker_start ( JobID, CurrentStage, IsMaster, MachineName ) values ( %lu, \"none\", %d, \"%s\" )", + g_JobPrimaryID, g_bMaster, g_MachineName ); + query.Execute( g_pSQL ); + + g_JobWorkerID = g_pSQL->InsertID(); + if ( g_JobWorkerID == 0 ) + { + delete g_pDB; + g_pDB = NULL; + return false; + } + + // Now create a thread that samples perf data and stores it in the database. + g_hPerfThreadExitEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + g_hPerfThread = CreateThread( + NULL, + 0, + PerfThreadFn, + NULL, + 0, + &g_PerfThreadID ); + + return true; +} + + +void VMPI_Stats_Term() +{ + if ( !g_pDB ) + return; + + // Stop the thread. + SetEvent( g_hPerfThreadExitEvent ); + WaitForSingleObject( g_hPerfThread, INFINITE ); + + CloseHandle( g_hPerfThreadExitEvent ); + g_hPerfThreadExitEvent = NULL; + + CloseHandle( g_hPerfThread ); + g_hPerfThread = NULL; + + if ( g_bMaster ) + { + // (Write a job_master_end entry here). + g_pDB->AddCommandToQueue( new CSQLDBCommand_JobMasterEnd, NULL ); + } + + // Wait for up to a second for the DB to finish writing its data. + DWORD startTime = GetTickCount(); + while ( GetTickCount() - startTime < 1000 ) + { + if ( g_pDB->QueriesInOutQueue() == 0 ) + break; + } + + delete g_pDB; + g_pDB = NULL; + + UnloadMySQLWrapper(); +} + + +static bool ReadStringFromFile( FILE *fp, char *pStr, int strSize ) +{ + int i=0; + for ( i; i < strSize-2; i++ ) + { + if ( fread( &pStr[i], 1, 1, fp ) != 1 || + pStr[i] == '\n' ) + { + break; + } + } + + pStr[i] = 0; + return i != 0; +} + + +// This looks for pDBInfoFilename in the same path as pBaseExeFilename. +// The file has 3 lines: machine name (with database), database name, username +void GetDBInfo( const char *pDBInfoFilename, CDBInfo *pInfo ) +{ + char baseExeFilename[512]; + if ( !GetModuleFileName( GetModuleHandle( NULL ), baseExeFilename, sizeof( baseExeFilename ) ) ) + Error( "GetModuleFileName failed." ); + + // Look for the info file in the same directory as the exe. + char dbInfoFilename[512]; + Q_strncpy( dbInfoFilename, baseExeFilename, sizeof( dbInfoFilename ) ); + Q_StripFilename( dbInfoFilename ); + + if ( dbInfoFilename[0] == 0 ) + Q_strncpy( dbInfoFilename, ".", sizeof( dbInfoFilename ) ); + + Q_strncat( dbInfoFilename, "/", sizeof( dbInfoFilename ), COPY_ALL_CHARACTERS ); + Q_strncat( dbInfoFilename, pDBInfoFilename, sizeof( dbInfoFilename ), COPY_ALL_CHARACTERS ); + + FILE *fp = fopen( dbInfoFilename, "rt" ); + if ( !fp ) + { + Error( "Can't open %s for database info.\n", dbInfoFilename ); + } + + if ( !ReadStringFromFile( fp, pInfo->m_HostName, sizeof( pInfo->m_HostName ) ) || + !ReadStringFromFile( fp, pInfo->m_DBName, sizeof( pInfo->m_DBName ) ) || + !ReadStringFromFile( fp, pInfo->m_UserName, sizeof( pInfo->m_UserName ) ) + ) + { + Error( "%s is not a valid database info file.\n", dbInfoFilename ); + } + + fclose( fp ); +} + + +void RunJobWatchApp( char *pCmdLine ) +{ + STARTUPINFO si; + memset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + PROCESS_INFORMATION pi; + memset( &pi, 0, sizeof( pi ) ); + + // Working directory should be the same as our exe's directory. + char dirName[512]; + if ( GetModuleFileName( NULL, dirName, sizeof( dirName ) ) != 0 ) + { + char *s1 = V_strrchr( dirName, '\\' ); + char *s2 = V_strrchr( dirName, '/' ); + if ( s1 || s2 ) + { + // Get rid of the last slash. + s1 = max( s1, s2 ); + s1[0] = 0; + + if ( !CreateProcess( + NULL, + pCmdLine, + NULL, // security + NULL, + TRUE, + 0, // flags + NULL, // environment + dirName, // current directory + &si, + &pi ) ) + { + Warning( "%s - error launching '%s'\n", VMPI_GetParamString( mpi_Job_Watch ), pCmdLine ); + } + } + } +} + + +void StatsDB_InitStatsDatabase( + int argc, + char **argv, + const char *pDBInfoFilename ) +{ + // Did they disable the stats database? + if ( !g_bMPI_Stats && !VMPI_IsParamUsed( mpi_Job_Watch ) ) + return; + + unsigned long jobPrimaryID; + + // Now open the DB. + if ( g_bMPIMaster ) + { + CDBInfo dbInfo; + GetDBInfo( pDBInfoFilename, &dbInfo ); + + if ( !VMPI_Stats_Init_Master( dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName, argv[argc-1], &jobPrimaryID ) ) + { + Warning( "VMPI_Stats_Init_Master( %s, %s, %s ) failed.\n", dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName ); + + // Tell the workers not to use stats. + dbInfo.m_HostName[0] = 0; + } + + char cmdLine[2048]; + Q_snprintf( cmdLine, sizeof( cmdLine ), "vmpi_job_watch -JobID %d", jobPrimaryID ); + + Msg( "\nTo watch this job, run this command line:\n%s\n\n", cmdLine ); + + if ( VMPI_IsParamUsed( mpi_Job_Watch ) ) + { + // Convenience thing to automatically launch the job watch for this job. + RunJobWatchApp( cmdLine ); + } + + // Send the database info to all the workers. + SendDBInfo( &dbInfo, jobPrimaryID ); + } + else + { + // Wait to get DB info so we can connect to the MySQL database. + CDBInfo dbInfo; + unsigned long jobPrimaryID; + RecvDBInfo( &dbInfo, &jobPrimaryID ); + + if ( dbInfo.m_HostName[0] != 0 ) + { + if ( !VMPI_Stats_Init_Worker( dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName, jobPrimaryID ) ) + Error( "VMPI_Stats_Init_Worker( %s, %s, %s, %d ) failed.\n", dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName, jobPrimaryID ); + } + } +} + + +unsigned long StatsDB_GetUniqueJobID() +{ + return g_JobPrimaryID; +} + + +unsigned long VMPI_Stats_GetJobWorkerID() +{ + return g_JobWorkerID; } \ No newline at end of file diff --git a/mp/src/utils/common/mpi_stats.h b/mp/src/utils/common/mpi_stats.h index 10aa6162..841db3a2 100644 --- a/mp/src/utils/common/mpi_stats.h +++ b/mp/src/utils/common/mpi_stats.h @@ -1,59 +1,59 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef MPI_STATS_H -#define MPI_STATS_H -#ifdef _WIN32 -#pragma once -#endif - - -// The VMPI stats module reports a bunch of statistics to a MySQL server, and the -// stats can be used to trace and graph a compile session. -// - -// Call this as soon as possible to initialize spew hooks. -void VMPI_Stats_InstallSpewHook(); - -// -// pDBServerName is the hostname (or dotted IP address) of the MySQL server to connect to. -// pBSPFilename is the last argument on the command line. -// pMachineIP is the dotted IP address of this machine. -// jobID is an 8-byte unique identifier for this job. -// -bool VMPI_Stats_Init_Master( const char *pHostName, const char *pDBName, const char *pUserName, const char *pBSPFilename, unsigned long *pDBJobID ); -bool VMPI_Stats_Init_Worker( const char *pHostName, const char *pDBName, const char *pUserName, unsigned long DBJobID ); -void VMPI_Stats_Term(); - -// Add a generic text event to the database. -void VMPI_Stats_AddEventText( const char *pText ); - -class CDBInfo -{ -public: - char m_HostName[128]; - char m_DBName[128]; - char m_UserName[128]; -}; - -// If you're the master, this loads pDBInfoFilename, sends that info to the workers, and -// connects to the database. -// -// If you're a worker, this waits for the DB info, then connects to the database. -void StatsDB_InitStatsDatabase( - int argc, - char **argv, - const char *pDBInfoFilename ); - -// The database gives back a unique ID for the job. -unsigned long StatsDB_GetUniqueJobID(); - -// Get the worker ID (used for the JobWorkerID fields in the database). -unsigned long VMPI_Stats_GetJobWorkerID(); - - -#endif // MPI_STATS_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MPI_STATS_H +#define MPI_STATS_H +#ifdef _WIN32 +#pragma once +#endif + + +// The VMPI stats module reports a bunch of statistics to a MySQL server, and the +// stats can be used to trace and graph a compile session. +// + +// Call this as soon as possible to initialize spew hooks. +void VMPI_Stats_InstallSpewHook(); + +// +// pDBServerName is the hostname (or dotted IP address) of the MySQL server to connect to. +// pBSPFilename is the last argument on the command line. +// pMachineIP is the dotted IP address of this machine. +// jobID is an 8-byte unique identifier for this job. +// +bool VMPI_Stats_Init_Master( const char *pHostName, const char *pDBName, const char *pUserName, const char *pBSPFilename, unsigned long *pDBJobID ); +bool VMPI_Stats_Init_Worker( const char *pHostName, const char *pDBName, const char *pUserName, unsigned long DBJobID ); +void VMPI_Stats_Term(); + +// Add a generic text event to the database. +void VMPI_Stats_AddEventText( const char *pText ); + +class CDBInfo +{ +public: + char m_HostName[128]; + char m_DBName[128]; + char m_UserName[128]; +}; + +// If you're the master, this loads pDBInfoFilename, sends that info to the workers, and +// connects to the database. +// +// If you're a worker, this waits for the DB info, then connects to the database. +void StatsDB_InitStatsDatabase( + int argc, + char **argv, + const char *pDBInfoFilename ); + +// The database gives back a unique ID for the job. +unsigned long StatsDB_GetUniqueJobID(); + +// Get the worker ID (used for the JobWorkerID fields in the database). +unsigned long VMPI_Stats_GetJobWorkerID(); + + +#endif // MPI_STATS_H diff --git a/mp/src/utils/common/mstristrip.cpp b/mp/src/utils/common/mstristrip.cpp index 9e611f94..ad6ebdc8 100644 --- a/mp/src/utils/common/mstristrip.cpp +++ b/mp/src/utils/common/mstristrip.cpp @@ -1,930 +1,930 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -//----------------------------------------------------------------------------- -// FILE: TRISTRIP.CPP -// -// Desc: Xbox tristripper -// -// Copyright (c) 1999-2000 Microsoft Corporation. All rights reserved. -//----------------------------------------------------------------------------- - -// identifier was truncated to '255' characters in the debug information -#pragma warning(disable: 4786) -// conversion from 'double' to 'float' -#pragma warning(disable: 4244) -#pragma warning(disable: 4530) - -#include -#include -#include -#include -#include - -#include -#ifdef _DEBUG -#include -#endif - -#include "mstristrip.h" - -using namespace std; - -//========================================================================= -// structs -//========================================================================= -typedef vector STRIPVERTS; -typedef list STRIPLIST; -typedef WORD (*TRIANGLELIST)[3]; - -struct TRIANGLEINFO -{ - int neighbortri[3]; - int neighboredge[3]; -}; - -// return true if strip starts clockwise -inline bool FIsStripCW(const STRIPVERTS &stripvertices) -{ - // last index should have cw/ccw bool - return !!stripvertices[stripvertices.size() - 1]; -} - -// return length of strip -inline int StripLen(const STRIPVERTS &stripvertices) -{ - return (int)stripvertices.size() - 1; -} - -// free all stripverts and clear the striplist -inline void FreeStripListVerts(STRIPLIST *pstriplist) -{ - STRIPLIST::iterator istriplist = pstriplist->begin(); - while(istriplist != pstriplist->end()) - { - STRIPVERTS *pstripverts = *istriplist; - delete pstripverts; - pstriplist->erase(istriplist++); - } -} - -//========================================================================= -// main stripper class -//========================================================================= -class CStripper -{ -public: - // ctors/dtors - CStripper(int numtris, TRIANGLELIST ptriangles); - ~CStripper(); - - // initialize tri info - void InitTriangleInfo(int tri, int vert); - - // get maximum length strip from tri/vert - int CreateStrip(int tri, int vert, int maxlen, int *pswaps, - bool flookahead, bool fstartcw, int *pstriptris, int *pstripverts); - - // stripify entire mesh - void BuildStrips(STRIPLIST *pstriplist, int maxlen, bool flookahead); - - // blast strip indices to ppstripindices - int CreateManyStrips(STRIPLIST *pstriplist, WORD **ppstripindices); - int CreateLongStrip(STRIPLIST *pstriplist, WORD **ppstripindices); - - inline int GetNeighborCount(int tri) - { - int count = 0; - for(int vert = 0; vert < 3; vert++) - { - int neighbortri = m_ptriinfo[tri].neighbortri[vert]; - count += (neighbortri != -1) && !m_pused[neighbortri]; - } - return count; - } - - // from callee - int m_numtris; // # tris - TRIANGLELIST m_ptriangles; // trilist - - TRIANGLEINFO *m_ptriinfo; // tri edge, neighbor info - int *m_pused; // tri used flag -}; - -//========================================================================= -// vertex cache class -//========================================================================= -class CVertCache -{ -public: - CVertCache() - { Reset(); } - ~CVertCache() - {}; - - // reset cache - void Reset() - { - m_iCachePtr = 0; - m_cachehits = 0; - memset(m_rgCache, 0xff, sizeof(m_rgCache)); - } - - // add vertindex to cache - bool Add(int strip, int vertindex); - - int NumCacheHits() const - { return m_cachehits; } - -// enum { CACHE_SIZE = 10 }; - enum { CACHE_SIZE = 18 }; - -private: - int m_cachehits; // current # of cache hits - WORD m_rgCache[CACHE_SIZE]; // vertex cache - int m_rgCacheStrip[CACHE_SIZE]; // strip # which added vert - int m_iCachePtr; // fifo ptr -}; - -//========================================================================= -// Get maximum length of strip starting at tri/vert -//========================================================================= -int CStripper::CreateStrip(int tri, int vert, int maxlen, int *pswaps, - bool flookahead, bool fstartcw, int *pstriptris, int *pstripverts) -{ - *pswaps = 0; - - // this guy has already been used? - if(m_pused[tri]) - return 0; - - // mark tri as used - m_pused[tri] = 1; - - int swaps = 0; - - // add first tri info - pstriptris[0] = tri; - pstriptris[1] = tri; - pstriptris[2] = tri; - - if(fstartcw) - { - pstripverts[0] = (vert) % 3; - pstripverts[1] = (vert + 1) % 3; - pstripverts[2] = (vert + 2) % 3; - } - else - { - pstripverts[0] = (vert + 1) % 3; - pstripverts[1] = (vert + 0) % 3; - pstripverts[2] = (vert + 2) % 3; - } - fstartcw = !fstartcw; - - // get next tri information - int edge = (fstartcw ? vert + 2 : vert + 1) % 3; - int nexttri = m_ptriinfo[tri].neighbortri[edge]; - int nextvert = m_ptriinfo[tri].neighboredge[edge]; - - // start building the strip until we run out of room or indices - int stripcount; - for( stripcount = 3; stripcount < maxlen; stripcount++) - { - // dead end? - if(nexttri == -1 || m_pused[nexttri]) - break; - - // move to next tri - tri = nexttri; - vert = nextvert; - - // toggle orientation - fstartcw = !fstartcw; - - // find the next natural edge - int edge = (fstartcw ? vert + 2 : vert + 1) % 3; - nexttri = m_ptriinfo[tri].neighbortri[edge]; - nextvert = m_ptriinfo[tri].neighboredge[edge]; - - bool fswap = false; - if(nexttri == -1 || m_pused[nexttri]) - { - // if the next tri is a dead end - try swapping orientation - fswap = true; - } - else if(flookahead) - { - // try a swap and see who our new neighbor would be - int edgeswap = (fstartcw ? vert + 1 : vert + 2) % 3; - int nexttriswap = m_ptriinfo[tri].neighbortri[edgeswap]; - int nextvertswap = m_ptriinfo[tri].neighboredge[edgeswap]; - - if(nexttriswap != -1 && !m_pused[nexttriswap]) - { - assert(nexttri != -1); - - // if the swap neighbor has a lower count, change directions - if(GetNeighborCount(nexttriswap) < GetNeighborCount(nexttri)) - { - fswap = true; - } - else if(GetNeighborCount(nexttriswap) == GetNeighborCount(nexttri)) - { - // if they have the same number of neighbors - check their neighbors - edgeswap = (fstartcw ? nextvertswap + 2 : nextvertswap + 1) % 3; - nexttriswap = m_ptriinfo[nexttriswap].neighbortri[edgeswap]; - - int edge1 = (fstartcw ? nextvert + 1 : nextvert + 2) % 3; - int nexttri1 = m_ptriinfo[nexttri].neighbortri[edge1]; - - if(nexttri1 == -1 || m_pused[nexttri1]) - { - // natural winding order leads us to a dead end so turn - fswap = true; - } - else if(nexttriswap != -1 && !m_pused[nexttriswap]) - { - // check neighbor counts on both directions and swap if it's better - if(GetNeighborCount(nexttriswap) < GetNeighborCount(nexttri1)) - fswap = true; - } - } - } - } - - if(fswap) - { - // we've been told to change directions so make sure we actually can - // and then add the swap vertex - int edgeswap = (fstartcw ? vert + 1 : vert + 2) % 3; - nexttri = m_ptriinfo[tri].neighbortri[edgeswap]; - nextvert = m_ptriinfo[tri].neighboredge[edgeswap]; - - if(nexttri != -1 && !m_pused[nexttri]) - { - pstriptris[stripcount] = pstriptris[stripcount - 2]; - pstripverts[stripcount] = pstripverts[stripcount - 2]; - stripcount++; - swaps++; - fstartcw = !fstartcw; - } - } - - // record index information - pstriptris[stripcount] = tri; - pstripverts[stripcount] = (vert + 2) % 3; - - // mark triangle as used - m_pused[tri] = 1; - } - - // clear the used flags - for(int j = 2; j < stripcount; j++) - m_pused[pstriptris[j]] = 0; - - // return swap count and striplen - *pswaps = swaps; - return stripcount; -} - -//========================================================================= -// Given a striplist and current cache state, pick the best next strip -//========================================================================= -STRIPLIST::iterator FindBestCachedStrip(STRIPLIST *pstriplist, - const CVertCache &vertcachestate) -{ - if(pstriplist->empty()) - return pstriplist->end(); - - bool fFlipStrip = false; - int maxcachehits = -1; - STRIPLIST::iterator istriplistbest = pstriplist->begin(); - - int striplen = StripLen(**istriplistbest); - bool fstartcw = FIsStripCW(**istriplistbest); - - // go through all the other strips looking for the best caching - for(STRIPLIST::iterator istriplist = pstriplist->begin(); - istriplist != pstriplist->end(); - ++istriplist) - { - bool fFlip = false; - const STRIPVERTS &stripverts = **istriplist; - int striplennew = StripLen(stripverts); - - // check cache if this strip is the same type as us (ie: cw/odd) - if((FIsStripCW(stripverts) == fstartcw) && - ((striplen & 0x1) == (striplennew & 0x1))) - { - // copy current state of cache - CVertCache vertcachenew = vertcachestate; - - // figure out what this guy would do to our cache - for(int ivert = 0; ivert < striplennew; ivert++) - vertcachenew.Add(2, stripverts[ivert]); - - // even length strip - see if better cache hits reversed - if(!(striplennew & 0x1)) - { - CVertCache vertcacheflipped = vertcachestate; - - for(int ivert = StripLen(stripverts) - 1; ivert >= 0; ivert--) - vertcacheflipped.Add(2, stripverts[ivert]); - - if(vertcacheflipped.NumCacheHits() > vertcachenew.NumCacheHits()) - { - vertcachenew = vertcacheflipped; - fFlip = true; - } - } - - // record the best number of cache hits to date - int numcachehits = vertcachenew.NumCacheHits() - vertcachestate.NumCacheHits(); - if(numcachehits > maxcachehits) - { - maxcachehits = numcachehits; - istriplistbest = istriplist; - fFlipStrip = fFlip; - } - } - } - - if(fFlipStrip) - { - STRIPVERTS &stripverts = **istriplistbest; - STRIPVERTS::iterator vend = stripverts.end(); - - reverse(stripverts.begin(), --vend); - } - - // make sure we keep the list in order and always pull off - // the first dude. - if(istriplistbest != pstriplist->begin()) - swap(*istriplistbest, *pstriplist->begin()); - - return pstriplist->begin(); -} - - -//========================================================================= -// Don't merge the strips - just blast em into the stripbuffer one by one -// (useful for debugging) -//========================================================================= -int CStripper::CreateManyStrips(STRIPLIST *pstriplist, WORD **ppstripindices) -{ - // allow room for each of the strips size plus the final 0 - int indexcount = (int)pstriplist->size() + 1; - - // we're storing the strips in [size1 i1 i2 i3][size2 i4 i5 i6][0] format - STRIPLIST::iterator istriplist; - for( istriplist = pstriplist->begin(); istriplist != pstriplist->end(); ++istriplist) - { - // add striplength plus potential degenerate to swap ccw --> cw - indexcount += StripLen(**istriplist) + 1; - } - - // alloc the space for all this stuff - WORD *pstripindices = new WORD [indexcount]; - assert(pstripindices); - - CVertCache vertcache; - int numstripindices = 0; - - for(istriplist = pstriplist->begin(); - !pstriplist->empty(); - istriplist = FindBestCachedStrip(pstriplist, vertcache)) - { - const STRIPVERTS &stripverts = **istriplist; - - if(!FIsStripCW(stripverts)) - { - // add an extra index if it's ccw - pstripindices[numstripindices++] = StripLen(stripverts) + 1; - pstripindices[numstripindices++] = stripverts[0]; - } - else - { - // add the strip length - pstripindices[numstripindices++] = StripLen(stripverts); - } - - // add all the strip indices - for(int i = 0; i < StripLen(stripverts); i++) - { - pstripindices[numstripindices++] = stripverts[i]; - vertcache.Add(1, stripverts[i]); - } - - // free this guy and pop him off the list - delete &stripverts; - pstriplist->pop_front(); - } - - // add terminating zero - pstripindices[numstripindices++] = 0; - *ppstripindices = pstripindices; - - return numstripindices; -} - -//========================================================================= -// Merge striplist into one big uberlist with (hopefully) optimal caching -//========================================================================= -int CStripper::CreateLongStrip(STRIPLIST *pstriplist, WORD **ppstripindices) -{ - // allow room for one strip length plus a possible 3 extra indices per - // concatenated strip list plus the final 0 - int indexcount = ((int)pstriplist->size() * 3) + 2; - - // we're storing the strips in [size1 i1 i2 i3][size2 i4 i5 i6][0] format - STRIPLIST::iterator istriplist; - for( istriplist = pstriplist->begin(); istriplist != pstriplist->end(); ++istriplist) - { - indexcount += StripLen(**istriplist); - } - - // alloc the space for all this stuff - WORD *pstripindices = new WORD [indexcount]; - assert(pstripindices); - - CVertCache vertcache; - int numstripindices = 0; - - // add first strip - istriplist = pstriplist->begin(); - const STRIPVERTS &stripverts = **istriplist; - - // first strip should be cw - assert(FIsStripCW(stripverts)); - - for(int ivert = 0; ivert < StripLen(stripverts); ivert++) - { - pstripindices[numstripindices++] = stripverts[ivert]; - vertcache.Add(1, stripverts[ivert]); - } - - // kill first dude - delete &stripverts; - pstriplist->erase(istriplist); - - // add all the others - while(pstriplist->size()) - { - istriplist = FindBestCachedStrip(pstriplist, vertcache); - STRIPVERTS &stripverts = **istriplist; - short lastvert = pstripindices[numstripindices - 1]; - short firstvert = stripverts[0]; - - if(firstvert != lastvert) - { - // add degenerate from last strip - pstripindices[numstripindices++] = lastvert; - - // add degenerate from our strip - pstripindices[numstripindices++] = firstvert; - } - - // if we're not orientated correctly, we need to add a degenerate - if(FIsStripCW(stripverts) != !(numstripindices & 0x1)) - { - // This shouldn't happen - we're currently trying very hard - // to keep everything oriented correctly. - assert(false); - pstripindices[numstripindices++] = firstvert; - } - - // add these verts - for(int ivert = 0; ivert < StripLen(stripverts); ivert++) - { - pstripindices[numstripindices++] = stripverts[ivert]; - vertcache.Add(1, stripverts[ivert]); - } - - // free these guys - delete &stripverts; - pstriplist->erase(istriplist); - } - - *ppstripindices = pstripindices; - return numstripindices; -} - -//========================================================================= -// Build a (hopefully) optimal set of strips from a trilist -//========================================================================= -void CStripper::BuildStrips(STRIPLIST *pstriplist, int maxlen, bool flookahead) -{ - // temp indices storage - const int ctmpverts = 1024; - int pstripverts[ctmpverts + 1]; - int pstriptris[ctmpverts + 1]; - - assert(maxlen <= ctmpverts); - - // clear all the used flags for the tris - memset(m_pused, 0, sizeof(m_pused[0]) * m_numtris); - - bool fstartcw = true; - for(;;) - { - int besttri = 0; - int bestvert = 0; - float bestratio = 2.0f; - int bestneighborcount = INT_MAX; - - int tri; - for( tri = 0; tri < m_numtris; tri++) - { - // if used the continue - if(m_pused[tri]) - continue; - - // get the neighbor count - int curneightborcount = GetNeighborCount(tri); - assert(curneightborcount >= 0 && curneightborcount <= 3); - - // push all the singletons to the very end - if(!curneightborcount) - curneightborcount = 4; - - // if this guy has more neighbors than the current best - bail - if(curneightborcount > bestneighborcount) - continue; - - // try starting the strip with each of this tris verts - for(int vert = 0; vert < 3; vert++) - { - int swaps; - int len = CreateStrip(tri, vert, maxlen, &swaps, flookahead, - fstartcw, pstriptris, pstripverts); - assert(len); - - float ratio = (len == 3) ? 1.0f : (float)swaps / len; - - // check if this ratio is better than what we've already got for - // this neighborcount - if((curneightborcount < bestneighborcount) || - ((curneightborcount == bestneighborcount) && (ratio < bestratio))) - { - bestneighborcount = curneightborcount; - - besttri = tri; - bestvert = vert; - bestratio = ratio; - } - - } - } - - // no strips found? - if(bestneighborcount == INT_MAX) - break; - - // recreate this strip - int swaps; - int len = CreateStrip(besttri, bestvert, maxlen, - &swaps, flookahead, fstartcw, pstriptris, pstripverts); - assert(len); - - // mark the tris on the best strip as used - for(tri = 0; tri < len; tri++) - m_pused[pstriptris[tri]] = 1; - - // create a new STRIPVERTS and stuff in the indices - STRIPVERTS *pstripvertices = new STRIPVERTS(len + 1); - assert(pstripvertices); - - // store orientation in first entry - for(tri = 0; tri < len; tri++) - (*pstripvertices)[tri] = m_ptriangles[pstriptris[tri]][pstripverts[tri]]; - (*pstripvertices)[len] = fstartcw; - - // store the STRIPVERTS - pstriplist->push_back(pstripvertices); - - // if strip was odd - swap orientation - if((len & 0x1)) - fstartcw = !fstartcw; - } - -#ifdef _DEBUG - // make sure all tris are used - for(int t = 0; t < m_numtris; t++) - assert(m_pused[t]); -#endif -} - -//========================================================================= -// Guesstimate on the total index count for this list of strips -//========================================================================= -int EstimateStripCost(STRIPLIST *pstriplist) -{ - int count = 0; - - for(STRIPLIST::iterator istriplist = pstriplist->begin(); - istriplist != pstriplist->end(); - ++istriplist) - { - // add count of indices - count += StripLen(**istriplist); - } - - // assume 2 indices per strip to tack all these guys together - return count + ((int)pstriplist->size() - 1) * 2; -} - -//========================================================================= -// Initialize triangle information (edges, #neighbors, etc.) -//========================================================================= -void CStripper::InitTriangleInfo(int tri, int vert) -{ - WORD *ptriverts = &m_ptriangles[tri + 1][0]; - int vert1 = m_ptriangles[tri][(vert + 1) % 3]; - int vert2 = m_ptriangles[tri][vert]; - - for(int itri = tri + 1; itri < m_numtris; itri++, ptriverts += 3) - { - if(m_pused[itri] != 0x7) - { - for(int ivert = 0; ivert < 3; ivert++) - { - if((ptriverts[ivert] == vert1) && - (ptriverts[(ivert + 1) % 3] == vert2)) - { - // add the triangle info - m_ptriinfo[tri].neighbortri[vert] = itri; - m_ptriinfo[tri].neighboredge[vert] = ivert; - m_pused[tri] |= (1 << vert); - - m_ptriinfo[itri].neighbortri[ivert] = tri; - m_ptriinfo[itri].neighboredge[ivert] = vert; - m_pused[itri] |= (1 << ivert); - return; - } - } - } - } -} - -//========================================================================= -// CStripper ctor -//========================================================================= -CStripper::CStripper(int numtris, TRIANGLELIST ptriangles) -{ - // store trilist info - m_numtris = numtris; - m_ptriangles = ptriangles; - - m_pused = new int[numtris]; - assert(m_pused); - m_ptriinfo = new TRIANGLEINFO[numtris]; - assert(m_ptriinfo); - - // init triinfo - int itri; - for( itri = 0; itri < numtris; itri++) - { - m_ptriinfo[itri].neighbortri[0] = -1; - m_ptriinfo[itri].neighbortri[1] = -1; - m_ptriinfo[itri].neighbortri[2] = -1; - } - - // clear the used flag - memset(m_pused, 0, sizeof(m_pused[0]) * m_numtris); - - // go through all the triangles and find edges, neighbor counts - for(itri = 0; itri < numtris; itri++) - { - for(int ivert = 0; ivert < 3; ivert++) - { - if(!(m_pused[itri] & (1 << ivert))) - InitTriangleInfo(itri, ivert); - } - } - - // clear the used flags from InitTriangleInfo - memset(m_pused, 0, sizeof(m_pused[0]) * m_numtris); -} - -//========================================================================= -// CStripper dtor -//========================================================================= -CStripper::~CStripper() -{ - // free stuff - delete [] m_pused; - m_pused = NULL; - - delete [] m_ptriinfo; - m_ptriinfo = NULL; -} - -//========================================================================= -// Add an index to the cache - returns true if it was added, false otherwise -//========================================================================= -bool CVertCache::Add(int strip, int vertindex) -{ - // find index in cache - for(int iCache = 0; iCache < CACHE_SIZE; iCache++) - { - if(vertindex == m_rgCache[iCache]) - { - // if it's in the cache and it's from a different strip - // change the strip to the new one and count the cache hit - if(strip != m_rgCacheStrip[iCache]) - { - m_cachehits++; - m_rgCacheStrip[iCache] = strip; - return true; - } - - // we added this item to the cache earlier - carry on - return false; - } - } - - // not in cache, add vert and strip - m_rgCache[m_iCachePtr] = vertindex; - m_rgCacheStrip[m_iCachePtr] = strip; - m_iCachePtr = (m_iCachePtr + 1) % CACHE_SIZE; - return true; -} - -#ifdef _DEBUG -//========================================================================= -// Turn on c runtime leak checking, etc. -//========================================================================= -void EnableLeakChecking() -{ - int flCrtDbgFlags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); - - flCrtDbgFlags &= - ~(_CRTDBG_LEAK_CHECK_DF | - _CRTDBG_CHECK_ALWAYS_DF | - _CRTDBG_DELAY_FREE_MEM_DF); - - // always check for memory leaks - flCrtDbgFlags |= _CRTDBG_LEAK_CHECK_DF; - - // others you may / may not want to set - flCrtDbgFlags |= _CRTDBG_CHECK_ALWAYS_DF; - flCrtDbgFlags |= _CRTDBG_DELAY_FREE_MEM_DF; - - _CrtSetDbgFlag(flCrtDbgFlags); - - // all types of reports go via OutputDebugString - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG); - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); - _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); - - // big errors and asserts get their own assert window - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_WNDW); - _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_WNDW); - - // _CrtSetBreakAlloc(0); -} -#endif - -//========================================================================= -// Main Stripify routine -//========================================================================= -int Stripify(int numtris, WORD *ptriangles, int *pnumindices, WORD **ppstripindices) -{ - if(!numtris || !ptriangles) - return 0; - -#ifdef _DEBUG -// EnableLeakChecking(); -#endif - - CStripper stripper(numtris, (TRIANGLELIST)ptriangles); - - // map of various args to try stripifying mesh with - struct ARGMAP - { - int maxlen; // maximum length of strips - bool flookahead; // use sgi greedy lookahead (or not) - } rgargmap[] = - { - { 1024, true }, - { 1024, false }, - }; - static const int cargmaps = sizeof(rgargmap) / sizeof(rgargmap[0]); - STRIPLIST striplistbest; - int bestlistcost = 0; - - for(int imap = 0; imap < cargmaps; imap++) - { - STRIPLIST striplist; - - // build the strip with the various args - stripper.BuildStrips(&striplist, rgargmap[imap].maxlen, - rgargmap[imap].flookahead); - - // guesstimate the list cost and store it if it's good - int listcost = EstimateStripCost(&striplist); - if(!bestlistcost || (listcost < bestlistcost)) - { - // free the old best list - FreeStripListVerts(&striplistbest); - - // store the new best list - striplistbest = striplist; - bestlistcost = listcost; - assert(bestlistcost > 0); - } - else - { - FreeStripListVerts(&striplist); - } - } - -#ifdef NEVER - // Return the strips in [size1 i1 i2 i3][size2 i4 i5 i6]...[0] format - // Very useful for debugging... - return stripper.CreateManyStrips(&striplistbest, ppstripindices); -#endif // NEVER - - // return one big long strip - int numindices = stripper.CreateLongStrip(&striplistbest, ppstripindices); - - if(pnumindices) - *pnumindices = numindices; - return numindices; -} - -//========================================================================= -// Class used to vertices for locality of access. -//========================================================================= -struct SortEntry -{ -public: - int iFirstUsed; - int iOrigIndex; - - bool operator<(const SortEntry& rhs) const - { - return iFirstUsed < rhs.iFirstUsed; - } -}; - -//========================================================================= -// Reorder the vertices -//========================================================================= -void ComputeVertexPermutation(int numstripindices, WORD* pstripindices, - int* pnumverts, WORD** ppvertexpermutation) -{ - // Sort verts to maximize locality. - SortEntry* pSortTable = new SortEntry[*pnumverts]; - - // Fill in original index. - int i; - for( i = 0; i < *pnumverts; i++) - { - pSortTable[i].iOrigIndex = i; - pSortTable[i].iFirstUsed = -1; - } - - // Fill in first used flag. - for(i = 0; i < numstripindices; i++) - { - int index = pstripindices[i]; - - if(pSortTable[index].iFirstUsed == -1) - pSortTable[index].iFirstUsed = i; - } - - // Sort the table. - sort(pSortTable, pSortTable + *pnumverts); - - // Copy re-mapped to orignal vertex permutaion into output array. - *ppvertexpermutation = new WORD[*pnumverts]; - - for(i = 0; i < *pnumverts; i++) - { - (*ppvertexpermutation)[i] = pSortTable[i].iOrigIndex; - } - - // Build original to re-mapped permutation. - WORD* pInversePermutation = new WORD[numstripindices]; - - for(i = 0; i < *pnumverts; i++) - { - pInversePermutation[pSortTable[i].iOrigIndex] = i; - } - - // We need to remap indices as well. - for(i = 0; i < numstripindices; i++) - { - pstripindices[i] = pInversePermutation[pstripindices[i]]; - } - - delete[] pSortTable; - delete[] pInversePermutation; -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +//----------------------------------------------------------------------------- +// FILE: TRISTRIP.CPP +// +// Desc: Xbox tristripper +// +// Copyright (c) 1999-2000 Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- + +// identifier was truncated to '255' characters in the debug information +#pragma warning(disable: 4786) +// conversion from 'double' to 'float' +#pragma warning(disable: 4244) +#pragma warning(disable: 4530) + +#include +#include +#include +#include +#include + +#include +#ifdef _DEBUG +#include +#endif + +#include "mstristrip.h" + +using namespace std; + +//========================================================================= +// structs +//========================================================================= +typedef vector STRIPVERTS; +typedef list STRIPLIST; +typedef WORD (*TRIANGLELIST)[3]; + +struct TRIANGLEINFO +{ + int neighbortri[3]; + int neighboredge[3]; +}; + +// return true if strip starts clockwise +inline bool FIsStripCW(const STRIPVERTS &stripvertices) +{ + // last index should have cw/ccw bool + return !!stripvertices[stripvertices.size() - 1]; +} + +// return length of strip +inline int StripLen(const STRIPVERTS &stripvertices) +{ + return (int)stripvertices.size() - 1; +} + +// free all stripverts and clear the striplist +inline void FreeStripListVerts(STRIPLIST *pstriplist) +{ + STRIPLIST::iterator istriplist = pstriplist->begin(); + while(istriplist != pstriplist->end()) + { + STRIPVERTS *pstripverts = *istriplist; + delete pstripverts; + pstriplist->erase(istriplist++); + } +} + +//========================================================================= +// main stripper class +//========================================================================= +class CStripper +{ +public: + // ctors/dtors + CStripper(int numtris, TRIANGLELIST ptriangles); + ~CStripper(); + + // initialize tri info + void InitTriangleInfo(int tri, int vert); + + // get maximum length strip from tri/vert + int CreateStrip(int tri, int vert, int maxlen, int *pswaps, + bool flookahead, bool fstartcw, int *pstriptris, int *pstripverts); + + // stripify entire mesh + void BuildStrips(STRIPLIST *pstriplist, int maxlen, bool flookahead); + + // blast strip indices to ppstripindices + int CreateManyStrips(STRIPLIST *pstriplist, WORD **ppstripindices); + int CreateLongStrip(STRIPLIST *pstriplist, WORD **ppstripindices); + + inline int GetNeighborCount(int tri) + { + int count = 0; + for(int vert = 0; vert < 3; vert++) + { + int neighbortri = m_ptriinfo[tri].neighbortri[vert]; + count += (neighbortri != -1) && !m_pused[neighbortri]; + } + return count; + } + + // from callee + int m_numtris; // # tris + TRIANGLELIST m_ptriangles; // trilist + + TRIANGLEINFO *m_ptriinfo; // tri edge, neighbor info + int *m_pused; // tri used flag +}; + +//========================================================================= +// vertex cache class +//========================================================================= +class CVertCache +{ +public: + CVertCache() + { Reset(); } + ~CVertCache() + {}; + + // reset cache + void Reset() + { + m_iCachePtr = 0; + m_cachehits = 0; + memset(m_rgCache, 0xff, sizeof(m_rgCache)); + } + + // add vertindex to cache + bool Add(int strip, int vertindex); + + int NumCacheHits() const + { return m_cachehits; } + +// enum { CACHE_SIZE = 10 }; + enum { CACHE_SIZE = 18 }; + +private: + int m_cachehits; // current # of cache hits + WORD m_rgCache[CACHE_SIZE]; // vertex cache + int m_rgCacheStrip[CACHE_SIZE]; // strip # which added vert + int m_iCachePtr; // fifo ptr +}; + +//========================================================================= +// Get maximum length of strip starting at tri/vert +//========================================================================= +int CStripper::CreateStrip(int tri, int vert, int maxlen, int *pswaps, + bool flookahead, bool fstartcw, int *pstriptris, int *pstripverts) +{ + *pswaps = 0; + + // this guy has already been used? + if(m_pused[tri]) + return 0; + + // mark tri as used + m_pused[tri] = 1; + + int swaps = 0; + + // add first tri info + pstriptris[0] = tri; + pstriptris[1] = tri; + pstriptris[2] = tri; + + if(fstartcw) + { + pstripverts[0] = (vert) % 3; + pstripverts[1] = (vert + 1) % 3; + pstripverts[2] = (vert + 2) % 3; + } + else + { + pstripverts[0] = (vert + 1) % 3; + pstripverts[1] = (vert + 0) % 3; + pstripverts[2] = (vert + 2) % 3; + } + fstartcw = !fstartcw; + + // get next tri information + int edge = (fstartcw ? vert + 2 : vert + 1) % 3; + int nexttri = m_ptriinfo[tri].neighbortri[edge]; + int nextvert = m_ptriinfo[tri].neighboredge[edge]; + + // start building the strip until we run out of room or indices + int stripcount; + for( stripcount = 3; stripcount < maxlen; stripcount++) + { + // dead end? + if(nexttri == -1 || m_pused[nexttri]) + break; + + // move to next tri + tri = nexttri; + vert = nextvert; + + // toggle orientation + fstartcw = !fstartcw; + + // find the next natural edge + int edge = (fstartcw ? vert + 2 : vert + 1) % 3; + nexttri = m_ptriinfo[tri].neighbortri[edge]; + nextvert = m_ptriinfo[tri].neighboredge[edge]; + + bool fswap = false; + if(nexttri == -1 || m_pused[nexttri]) + { + // if the next tri is a dead end - try swapping orientation + fswap = true; + } + else if(flookahead) + { + // try a swap and see who our new neighbor would be + int edgeswap = (fstartcw ? vert + 1 : vert + 2) % 3; + int nexttriswap = m_ptriinfo[tri].neighbortri[edgeswap]; + int nextvertswap = m_ptriinfo[tri].neighboredge[edgeswap]; + + if(nexttriswap != -1 && !m_pused[nexttriswap]) + { + assert(nexttri != -1); + + // if the swap neighbor has a lower count, change directions + if(GetNeighborCount(nexttriswap) < GetNeighborCount(nexttri)) + { + fswap = true; + } + else if(GetNeighborCount(nexttriswap) == GetNeighborCount(nexttri)) + { + // if they have the same number of neighbors - check their neighbors + edgeswap = (fstartcw ? nextvertswap + 2 : nextvertswap + 1) % 3; + nexttriswap = m_ptriinfo[nexttriswap].neighbortri[edgeswap]; + + int edge1 = (fstartcw ? nextvert + 1 : nextvert + 2) % 3; + int nexttri1 = m_ptriinfo[nexttri].neighbortri[edge1]; + + if(nexttri1 == -1 || m_pused[nexttri1]) + { + // natural winding order leads us to a dead end so turn + fswap = true; + } + else if(nexttriswap != -1 && !m_pused[nexttriswap]) + { + // check neighbor counts on both directions and swap if it's better + if(GetNeighborCount(nexttriswap) < GetNeighborCount(nexttri1)) + fswap = true; + } + } + } + } + + if(fswap) + { + // we've been told to change directions so make sure we actually can + // and then add the swap vertex + int edgeswap = (fstartcw ? vert + 1 : vert + 2) % 3; + nexttri = m_ptriinfo[tri].neighbortri[edgeswap]; + nextvert = m_ptriinfo[tri].neighboredge[edgeswap]; + + if(nexttri != -1 && !m_pused[nexttri]) + { + pstriptris[stripcount] = pstriptris[stripcount - 2]; + pstripverts[stripcount] = pstripverts[stripcount - 2]; + stripcount++; + swaps++; + fstartcw = !fstartcw; + } + } + + // record index information + pstriptris[stripcount] = tri; + pstripverts[stripcount] = (vert + 2) % 3; + + // mark triangle as used + m_pused[tri] = 1; + } + + // clear the used flags + for(int j = 2; j < stripcount; j++) + m_pused[pstriptris[j]] = 0; + + // return swap count and striplen + *pswaps = swaps; + return stripcount; +} + +//========================================================================= +// Given a striplist and current cache state, pick the best next strip +//========================================================================= +STRIPLIST::iterator FindBestCachedStrip(STRIPLIST *pstriplist, + const CVertCache &vertcachestate) +{ + if(pstriplist->empty()) + return pstriplist->end(); + + bool fFlipStrip = false; + int maxcachehits = -1; + STRIPLIST::iterator istriplistbest = pstriplist->begin(); + + int striplen = StripLen(**istriplistbest); + bool fstartcw = FIsStripCW(**istriplistbest); + + // go through all the other strips looking for the best caching + for(STRIPLIST::iterator istriplist = pstriplist->begin(); + istriplist != pstriplist->end(); + ++istriplist) + { + bool fFlip = false; + const STRIPVERTS &stripverts = **istriplist; + int striplennew = StripLen(stripverts); + + // check cache if this strip is the same type as us (ie: cw/odd) + if((FIsStripCW(stripverts) == fstartcw) && + ((striplen & 0x1) == (striplennew & 0x1))) + { + // copy current state of cache + CVertCache vertcachenew = vertcachestate; + + // figure out what this guy would do to our cache + for(int ivert = 0; ivert < striplennew; ivert++) + vertcachenew.Add(2, stripverts[ivert]); + + // even length strip - see if better cache hits reversed + if(!(striplennew & 0x1)) + { + CVertCache vertcacheflipped = vertcachestate; + + for(int ivert = StripLen(stripverts) - 1; ivert >= 0; ivert--) + vertcacheflipped.Add(2, stripverts[ivert]); + + if(vertcacheflipped.NumCacheHits() > vertcachenew.NumCacheHits()) + { + vertcachenew = vertcacheflipped; + fFlip = true; + } + } + + // record the best number of cache hits to date + int numcachehits = vertcachenew.NumCacheHits() - vertcachestate.NumCacheHits(); + if(numcachehits > maxcachehits) + { + maxcachehits = numcachehits; + istriplistbest = istriplist; + fFlipStrip = fFlip; + } + } + } + + if(fFlipStrip) + { + STRIPVERTS &stripverts = **istriplistbest; + STRIPVERTS::iterator vend = stripverts.end(); + + reverse(stripverts.begin(), --vend); + } + + // make sure we keep the list in order and always pull off + // the first dude. + if(istriplistbest != pstriplist->begin()) + swap(*istriplistbest, *pstriplist->begin()); + + return pstriplist->begin(); +} + + +//========================================================================= +// Don't merge the strips - just blast em into the stripbuffer one by one +// (useful for debugging) +//========================================================================= +int CStripper::CreateManyStrips(STRIPLIST *pstriplist, WORD **ppstripindices) +{ + // allow room for each of the strips size plus the final 0 + int indexcount = (int)pstriplist->size() + 1; + + // we're storing the strips in [size1 i1 i2 i3][size2 i4 i5 i6][0] format + STRIPLIST::iterator istriplist; + for( istriplist = pstriplist->begin(); istriplist != pstriplist->end(); ++istriplist) + { + // add striplength plus potential degenerate to swap ccw --> cw + indexcount += StripLen(**istriplist) + 1; + } + + // alloc the space for all this stuff + WORD *pstripindices = new WORD [indexcount]; + assert(pstripindices); + + CVertCache vertcache; + int numstripindices = 0; + + for(istriplist = pstriplist->begin(); + !pstriplist->empty(); + istriplist = FindBestCachedStrip(pstriplist, vertcache)) + { + const STRIPVERTS &stripverts = **istriplist; + + if(!FIsStripCW(stripverts)) + { + // add an extra index if it's ccw + pstripindices[numstripindices++] = StripLen(stripverts) + 1; + pstripindices[numstripindices++] = stripverts[0]; + } + else + { + // add the strip length + pstripindices[numstripindices++] = StripLen(stripverts); + } + + // add all the strip indices + for(int i = 0; i < StripLen(stripverts); i++) + { + pstripindices[numstripindices++] = stripverts[i]; + vertcache.Add(1, stripverts[i]); + } + + // free this guy and pop him off the list + delete &stripverts; + pstriplist->pop_front(); + } + + // add terminating zero + pstripindices[numstripindices++] = 0; + *ppstripindices = pstripindices; + + return numstripindices; +} + +//========================================================================= +// Merge striplist into one big uberlist with (hopefully) optimal caching +//========================================================================= +int CStripper::CreateLongStrip(STRIPLIST *pstriplist, WORD **ppstripindices) +{ + // allow room for one strip length plus a possible 3 extra indices per + // concatenated strip list plus the final 0 + int indexcount = ((int)pstriplist->size() * 3) + 2; + + // we're storing the strips in [size1 i1 i2 i3][size2 i4 i5 i6][0] format + STRIPLIST::iterator istriplist; + for( istriplist = pstriplist->begin(); istriplist != pstriplist->end(); ++istriplist) + { + indexcount += StripLen(**istriplist); + } + + // alloc the space for all this stuff + WORD *pstripindices = new WORD [indexcount]; + assert(pstripindices); + + CVertCache vertcache; + int numstripindices = 0; + + // add first strip + istriplist = pstriplist->begin(); + const STRIPVERTS &stripverts = **istriplist; + + // first strip should be cw + assert(FIsStripCW(stripverts)); + + for(int ivert = 0; ivert < StripLen(stripverts); ivert++) + { + pstripindices[numstripindices++] = stripverts[ivert]; + vertcache.Add(1, stripverts[ivert]); + } + + // kill first dude + delete &stripverts; + pstriplist->erase(istriplist); + + // add all the others + while(pstriplist->size()) + { + istriplist = FindBestCachedStrip(pstriplist, vertcache); + STRIPVERTS &stripverts = **istriplist; + short lastvert = pstripindices[numstripindices - 1]; + short firstvert = stripverts[0]; + + if(firstvert != lastvert) + { + // add degenerate from last strip + pstripindices[numstripindices++] = lastvert; + + // add degenerate from our strip + pstripindices[numstripindices++] = firstvert; + } + + // if we're not orientated correctly, we need to add a degenerate + if(FIsStripCW(stripverts) != !(numstripindices & 0x1)) + { + // This shouldn't happen - we're currently trying very hard + // to keep everything oriented correctly. + assert(false); + pstripindices[numstripindices++] = firstvert; + } + + // add these verts + for(int ivert = 0; ivert < StripLen(stripverts); ivert++) + { + pstripindices[numstripindices++] = stripverts[ivert]; + vertcache.Add(1, stripverts[ivert]); + } + + // free these guys + delete &stripverts; + pstriplist->erase(istriplist); + } + + *ppstripindices = pstripindices; + return numstripindices; +} + +//========================================================================= +// Build a (hopefully) optimal set of strips from a trilist +//========================================================================= +void CStripper::BuildStrips(STRIPLIST *pstriplist, int maxlen, bool flookahead) +{ + // temp indices storage + const int ctmpverts = 1024; + int pstripverts[ctmpverts + 1]; + int pstriptris[ctmpverts + 1]; + + assert(maxlen <= ctmpverts); + + // clear all the used flags for the tris + memset(m_pused, 0, sizeof(m_pused[0]) * m_numtris); + + bool fstartcw = true; + for(;;) + { + int besttri = 0; + int bestvert = 0; + float bestratio = 2.0f; + int bestneighborcount = INT_MAX; + + int tri; + for( tri = 0; tri < m_numtris; tri++) + { + // if used the continue + if(m_pused[tri]) + continue; + + // get the neighbor count + int curneightborcount = GetNeighborCount(tri); + assert(curneightborcount >= 0 && curneightborcount <= 3); + + // push all the singletons to the very end + if(!curneightborcount) + curneightborcount = 4; + + // if this guy has more neighbors than the current best - bail + if(curneightborcount > bestneighborcount) + continue; + + // try starting the strip with each of this tris verts + for(int vert = 0; vert < 3; vert++) + { + int swaps; + int len = CreateStrip(tri, vert, maxlen, &swaps, flookahead, + fstartcw, pstriptris, pstripverts); + assert(len); + + float ratio = (len == 3) ? 1.0f : (float)swaps / len; + + // check if this ratio is better than what we've already got for + // this neighborcount + if((curneightborcount < bestneighborcount) || + ((curneightborcount == bestneighborcount) && (ratio < bestratio))) + { + bestneighborcount = curneightborcount; + + besttri = tri; + bestvert = vert; + bestratio = ratio; + } + + } + } + + // no strips found? + if(bestneighborcount == INT_MAX) + break; + + // recreate this strip + int swaps; + int len = CreateStrip(besttri, bestvert, maxlen, + &swaps, flookahead, fstartcw, pstriptris, pstripverts); + assert(len); + + // mark the tris on the best strip as used + for(tri = 0; tri < len; tri++) + m_pused[pstriptris[tri]] = 1; + + // create a new STRIPVERTS and stuff in the indices + STRIPVERTS *pstripvertices = new STRIPVERTS(len + 1); + assert(pstripvertices); + + // store orientation in first entry + for(tri = 0; tri < len; tri++) + (*pstripvertices)[tri] = m_ptriangles[pstriptris[tri]][pstripverts[tri]]; + (*pstripvertices)[len] = fstartcw; + + // store the STRIPVERTS + pstriplist->push_back(pstripvertices); + + // if strip was odd - swap orientation + if((len & 0x1)) + fstartcw = !fstartcw; + } + +#ifdef _DEBUG + // make sure all tris are used + for(int t = 0; t < m_numtris; t++) + assert(m_pused[t]); +#endif +} + +//========================================================================= +// Guesstimate on the total index count for this list of strips +//========================================================================= +int EstimateStripCost(STRIPLIST *pstriplist) +{ + int count = 0; + + for(STRIPLIST::iterator istriplist = pstriplist->begin(); + istriplist != pstriplist->end(); + ++istriplist) + { + // add count of indices + count += StripLen(**istriplist); + } + + // assume 2 indices per strip to tack all these guys together + return count + ((int)pstriplist->size() - 1) * 2; +} + +//========================================================================= +// Initialize triangle information (edges, #neighbors, etc.) +//========================================================================= +void CStripper::InitTriangleInfo(int tri, int vert) +{ + WORD *ptriverts = &m_ptriangles[tri + 1][0]; + int vert1 = m_ptriangles[tri][(vert + 1) % 3]; + int vert2 = m_ptriangles[tri][vert]; + + for(int itri = tri + 1; itri < m_numtris; itri++, ptriverts += 3) + { + if(m_pused[itri] != 0x7) + { + for(int ivert = 0; ivert < 3; ivert++) + { + if((ptriverts[ivert] == vert1) && + (ptriverts[(ivert + 1) % 3] == vert2)) + { + // add the triangle info + m_ptriinfo[tri].neighbortri[vert] = itri; + m_ptriinfo[tri].neighboredge[vert] = ivert; + m_pused[tri] |= (1 << vert); + + m_ptriinfo[itri].neighbortri[ivert] = tri; + m_ptriinfo[itri].neighboredge[ivert] = vert; + m_pused[itri] |= (1 << ivert); + return; + } + } + } + } +} + +//========================================================================= +// CStripper ctor +//========================================================================= +CStripper::CStripper(int numtris, TRIANGLELIST ptriangles) +{ + // store trilist info + m_numtris = numtris; + m_ptriangles = ptriangles; + + m_pused = new int[numtris]; + assert(m_pused); + m_ptriinfo = new TRIANGLEINFO[numtris]; + assert(m_ptriinfo); + + // init triinfo + int itri; + for( itri = 0; itri < numtris; itri++) + { + m_ptriinfo[itri].neighbortri[0] = -1; + m_ptriinfo[itri].neighbortri[1] = -1; + m_ptriinfo[itri].neighbortri[2] = -1; + } + + // clear the used flag + memset(m_pused, 0, sizeof(m_pused[0]) * m_numtris); + + // go through all the triangles and find edges, neighbor counts + for(itri = 0; itri < numtris; itri++) + { + for(int ivert = 0; ivert < 3; ivert++) + { + if(!(m_pused[itri] & (1 << ivert))) + InitTriangleInfo(itri, ivert); + } + } + + // clear the used flags from InitTriangleInfo + memset(m_pused, 0, sizeof(m_pused[0]) * m_numtris); +} + +//========================================================================= +// CStripper dtor +//========================================================================= +CStripper::~CStripper() +{ + // free stuff + delete [] m_pused; + m_pused = NULL; + + delete [] m_ptriinfo; + m_ptriinfo = NULL; +} + +//========================================================================= +// Add an index to the cache - returns true if it was added, false otherwise +//========================================================================= +bool CVertCache::Add(int strip, int vertindex) +{ + // find index in cache + for(int iCache = 0; iCache < CACHE_SIZE; iCache++) + { + if(vertindex == m_rgCache[iCache]) + { + // if it's in the cache and it's from a different strip + // change the strip to the new one and count the cache hit + if(strip != m_rgCacheStrip[iCache]) + { + m_cachehits++; + m_rgCacheStrip[iCache] = strip; + return true; + } + + // we added this item to the cache earlier - carry on + return false; + } + } + + // not in cache, add vert and strip + m_rgCache[m_iCachePtr] = vertindex; + m_rgCacheStrip[m_iCachePtr] = strip; + m_iCachePtr = (m_iCachePtr + 1) % CACHE_SIZE; + return true; +} + +#ifdef _DEBUG +//========================================================================= +// Turn on c runtime leak checking, etc. +//========================================================================= +void EnableLeakChecking() +{ + int flCrtDbgFlags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + + flCrtDbgFlags &= + ~(_CRTDBG_LEAK_CHECK_DF | + _CRTDBG_CHECK_ALWAYS_DF | + _CRTDBG_DELAY_FREE_MEM_DF); + + // always check for memory leaks + flCrtDbgFlags |= _CRTDBG_LEAK_CHECK_DF; + + // others you may / may not want to set + flCrtDbgFlags |= _CRTDBG_CHECK_ALWAYS_DF; + flCrtDbgFlags |= _CRTDBG_DELAY_FREE_MEM_DF; + + _CrtSetDbgFlag(flCrtDbgFlags); + + // all types of reports go via OutputDebugString + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); + + // big errors and asserts get their own assert window + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_WNDW); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_WNDW); + + // _CrtSetBreakAlloc(0); +} +#endif + +//========================================================================= +// Main Stripify routine +//========================================================================= +int Stripify(int numtris, WORD *ptriangles, int *pnumindices, WORD **ppstripindices) +{ + if(!numtris || !ptriangles) + return 0; + +#ifdef _DEBUG +// EnableLeakChecking(); +#endif + + CStripper stripper(numtris, (TRIANGLELIST)ptriangles); + + // map of various args to try stripifying mesh with + struct ARGMAP + { + int maxlen; // maximum length of strips + bool flookahead; // use sgi greedy lookahead (or not) + } rgargmap[] = + { + { 1024, true }, + { 1024, false }, + }; + static const int cargmaps = sizeof(rgargmap) / sizeof(rgargmap[0]); + STRIPLIST striplistbest; + int bestlistcost = 0; + + for(int imap = 0; imap < cargmaps; imap++) + { + STRIPLIST striplist; + + // build the strip with the various args + stripper.BuildStrips(&striplist, rgargmap[imap].maxlen, + rgargmap[imap].flookahead); + + // guesstimate the list cost and store it if it's good + int listcost = EstimateStripCost(&striplist); + if(!bestlistcost || (listcost < bestlistcost)) + { + // free the old best list + FreeStripListVerts(&striplistbest); + + // store the new best list + striplistbest = striplist; + bestlistcost = listcost; + assert(bestlistcost > 0); + } + else + { + FreeStripListVerts(&striplist); + } + } + +#ifdef NEVER + // Return the strips in [size1 i1 i2 i3][size2 i4 i5 i6]...[0] format + // Very useful for debugging... + return stripper.CreateManyStrips(&striplistbest, ppstripindices); +#endif // NEVER + + // return one big long strip + int numindices = stripper.CreateLongStrip(&striplistbest, ppstripindices); + + if(pnumindices) + *pnumindices = numindices; + return numindices; +} + +//========================================================================= +// Class used to vertices for locality of access. +//========================================================================= +struct SortEntry +{ +public: + int iFirstUsed; + int iOrigIndex; + + bool operator<(const SortEntry& rhs) const + { + return iFirstUsed < rhs.iFirstUsed; + } +}; + +//========================================================================= +// Reorder the vertices +//========================================================================= +void ComputeVertexPermutation(int numstripindices, WORD* pstripindices, + int* pnumverts, WORD** ppvertexpermutation) +{ + // Sort verts to maximize locality. + SortEntry* pSortTable = new SortEntry[*pnumverts]; + + // Fill in original index. + int i; + for( i = 0; i < *pnumverts; i++) + { + pSortTable[i].iOrigIndex = i; + pSortTable[i].iFirstUsed = -1; + } + + // Fill in first used flag. + for(i = 0; i < numstripindices; i++) + { + int index = pstripindices[i]; + + if(pSortTable[index].iFirstUsed == -1) + pSortTable[index].iFirstUsed = i; + } + + // Sort the table. + sort(pSortTable, pSortTable + *pnumverts); + + // Copy re-mapped to orignal vertex permutaion into output array. + *ppvertexpermutation = new WORD[*pnumverts]; + + for(i = 0; i < *pnumverts; i++) + { + (*ppvertexpermutation)[i] = pSortTable[i].iOrigIndex; + } + + // Build original to re-mapped permutation. + WORD* pInversePermutation = new WORD[numstripindices]; + + for(i = 0; i < *pnumverts; i++) + { + pInversePermutation[pSortTable[i].iOrigIndex] = i; + } + + // We need to remap indices as well. + for(i = 0; i < numstripindices; i++) + { + pstripindices[i] = pInversePermutation[pstripindices[i]]; + } + + delete[] pSortTable; + delete[] pInversePermutation; +} + diff --git a/mp/src/utils/common/mstristrip.h b/mp/src/utils/common/mstristrip.h index 626a0062..55c93198 100644 --- a/mp/src/utils/common/mstristrip.h +++ b/mp/src/utils/common/mstristrip.h @@ -1,43 +1,43 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -//----------------------------------------------------------------------------- -// FILE: TRISTRIP.H -// -// Desc: tristrip header file -// -// Copyright (c) 1999-2000 Microsoft Corporation. All rights reserved. -//----------------------------------------------------------------------------- - -typedef unsigned short WORD; - -// -// Main Stripify routine. Returns number of strip indices contained -// in ppstripindices. Caller must delete [] ppstripindices. -// -int Stripify( - int numtris, // Number of triangles - WORD *ptriangles, // triangle indices pointer - int *pnumindices, // number of indices in ppstripindices (out) - WORD **ppstripindices // triangle strip indices -); - -// -// Re-arrange vertices so that they occur in the order that they are first -// used. This function doesn't actually move vertex data around, it returns -// an array that specifies where in the new vertex array each old vertex -// should go. It also re-maps the strip indices to use the new vertex -// locations. Caller must delete [] pVertexPermutation. -// -void ComputeVertexPermutation -( - int numstripindices, // Number of strip indices - WORD *pstripindices, // Strip indices - int *pnumverts, // Number of verts (in and out) - WORD **ppvertexpermutation // Map from orignal index to remapped index -); - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +//----------------------------------------------------------------------------- +// FILE: TRISTRIP.H +// +// Desc: tristrip header file +// +// Copyright (c) 1999-2000 Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- + +typedef unsigned short WORD; + +// +// Main Stripify routine. Returns number of strip indices contained +// in ppstripindices. Caller must delete [] ppstripindices. +// +int Stripify( + int numtris, // Number of triangles + WORD *ptriangles, // triangle indices pointer + int *pnumindices, // number of indices in ppstripindices (out) + WORD **ppstripindices // triangle strip indices +); + +// +// Re-arrange vertices so that they occur in the order that they are first +// used. This function doesn't actually move vertex data around, it returns +// an array that specifies where in the new vertex array each old vertex +// should go. It also re-maps the strip indices to use the new vertex +// locations. Caller must delete [] pVertexPermutation. +// +void ComputeVertexPermutation +( + int numstripindices, // Number of strip indices + WORD *pstripindices, // Strip indices + int *pnumverts, // Number of verts (in and out) + WORD **ppvertexpermutation // Map from orignal index to remapped index +); + diff --git a/mp/src/utils/common/pacifier.cpp b/mp/src/utils/common/pacifier.cpp index 6d9c73f2..41a10184 100644 --- a/mp/src/utils/common/pacifier.cpp +++ b/mp/src/utils/common/pacifier.cpp @@ -1,63 +1,63 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include -#include "basetypes.h" -#include "pacifier.h" -#include "tier0/dbg.h" - - -static int g_LastPacifierDrawn = -1; -static bool g_bPacifierSuppressed = false; - -#define clamp(a,b,c) ( (a) > (c) ? (c) : ( (a) < (b) ? (b) : (a) ) ) - -void StartPacifier( char const *pPrefix ) -{ - Msg( "%s", pPrefix ); - g_LastPacifierDrawn = -1; - UpdatePacifier( 0.001f ); -} - -void UpdatePacifier( float flPercent ) -{ - int iCur = (int)(flPercent * 40.0f); - iCur = clamp( iCur, g_LastPacifierDrawn, 40 ); - - if( iCur != g_LastPacifierDrawn && !g_bPacifierSuppressed ) - { - for( int i=g_LastPacifierDrawn+1; i <= iCur; i++ ) - { - if ( !( i % 4 ) ) - { - Msg("%d", i/4); - } - else - { - if( i != 40 ) - { - Msg("."); - } - } - } - - g_LastPacifierDrawn = iCur; - } -} - -void EndPacifier( bool bCarriageReturn ) -{ - UpdatePacifier(1); - - if( bCarriageReturn && !g_bPacifierSuppressed ) - Msg("\n"); -} - -void SuppressPacifier( bool bSuppress ) -{ - g_bPacifierSuppressed = bSuppress; -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include +#include "basetypes.h" +#include "pacifier.h" +#include "tier0/dbg.h" + + +static int g_LastPacifierDrawn = -1; +static bool g_bPacifierSuppressed = false; + +#define clamp(a,b,c) ( (a) > (c) ? (c) : ( (a) < (b) ? (b) : (a) ) ) + +void StartPacifier( char const *pPrefix ) +{ + Msg( "%s", pPrefix ); + g_LastPacifierDrawn = -1; + UpdatePacifier( 0.001f ); +} + +void UpdatePacifier( float flPercent ) +{ + int iCur = (int)(flPercent * 40.0f); + iCur = clamp( iCur, g_LastPacifierDrawn, 40 ); + + if( iCur != g_LastPacifierDrawn && !g_bPacifierSuppressed ) + { + for( int i=g_LastPacifierDrawn+1; i <= iCur; i++ ) + { + if ( !( i % 4 ) ) + { + Msg("%d", i/4); + } + else + { + if( i != 40 ) + { + Msg("."); + } + } + } + + g_LastPacifierDrawn = iCur; + } +} + +void EndPacifier( bool bCarriageReturn ) +{ + UpdatePacifier(1); + + if( bCarriageReturn && !g_bPacifierSuppressed ) + Msg("\n"); +} + +void SuppressPacifier( bool bSuppress ) +{ + g_bPacifierSuppressed = bSuppress; +} diff --git a/mp/src/utils/common/pacifier.h b/mp/src/utils/common/pacifier.h index 42ecd6ec..d64d6cda 100644 --- a/mp/src/utils/common/pacifier.h +++ b/mp/src/utils/common/pacifier.h @@ -1,23 +1,23 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef PACIFIER_H -#define PACIFIER_H -#ifdef _WIN32 -#pragma once -#endif - - -// Use these to display a pacifier like: -// ProcessBlock_Thread: 0...1...2...3...4...5...6...7...8...9... (0) -void StartPacifier( char const *pPrefix ); // Prints the prefix and resets the pacifier -void UpdatePacifier( float flPercent ); // percent value between 0 and 1. -void EndPacifier( bool bCarriageReturn = true ); // Completes pacifier as if 100% was done -void SuppressPacifier( bool bSuppress = true ); // Suppresses pacifier updates if another thread might still be firing them - - -#endif // PACIFIER_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef PACIFIER_H +#define PACIFIER_H +#ifdef _WIN32 +#pragma once +#endif + + +// Use these to display a pacifier like: +// ProcessBlock_Thread: 0...1...2...3...4...5...6...7...8...9... (0) +void StartPacifier( char const *pPrefix ); // Prints the prefix and resets the pacifier +void UpdatePacifier( float flPercent ); // percent value between 0 and 1. +void EndPacifier( bool bCarriageReturn = true ); // Completes pacifier as if 100% was done +void SuppressPacifier( bool bSuppress = true ); // Suppresses pacifier updates if another thread might still be firing them + + +#endif // PACIFIER_H diff --git a/mp/src/utils/common/physdll.cpp b/mp/src/utils/common/physdll.cpp index fae9810a..3df4b964 100644 --- a/mp/src/utils/common/physdll.cpp +++ b/mp/src/utils/common/physdll.cpp @@ -1,31 +1,31 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#include -#include "physdll.h" -#include "filesystem_tools.h" - -static CSysModule *pPhysicsModule = NULL; -CreateInterfaceFn GetPhysicsFactory( void ) -{ - if ( !pPhysicsModule ) - { - pPhysicsModule = g_pFullFileSystem->LoadModule( "VPHYSICS.DLL" ); - if ( !pPhysicsModule ) - return NULL; - } - - return Sys_GetFactory( pPhysicsModule ); -} - -void PhysicsDLLPath( const char *pPathname ) -{ - if ( !pPhysicsModule ) - { - pPhysicsModule = g_pFullFileSystem->LoadModule( pPathname ); - } -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include +#include "physdll.h" +#include "filesystem_tools.h" + +static CSysModule *pPhysicsModule = NULL; +CreateInterfaceFn GetPhysicsFactory( void ) +{ + if ( !pPhysicsModule ) + { + pPhysicsModule = g_pFullFileSystem->LoadModule( "VPHYSICS.DLL" ); + if ( !pPhysicsModule ) + return NULL; + } + + return Sys_GetFactory( pPhysicsModule ); +} + +void PhysicsDLLPath( const char *pPathname ) +{ + if ( !pPhysicsModule ) + { + pPhysicsModule = g_pFullFileSystem->LoadModule( pPathname ); + } +} diff --git a/mp/src/utils/common/physdll.h b/mp/src/utils/common/physdll.h index 151a9e50..f6faf2d8 100644 --- a/mp/src/utils/common/physdll.h +++ b/mp/src/utils/common/physdll.h @@ -1,30 +1,30 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef PHYSDLL_H -#define PHYSDLL_H -#pragma once - - -#ifdef __cplusplus -#include "vphysics_interface.h" -class IPhysics; -class IPhysicsCollision; - -extern CreateInterfaceFn GetPhysicsFactory( void ); - -extern "C" { -#endif - -// tools need to force the path -void PhysicsDLLPath( const char *pPathname ); - -#ifdef __cplusplus -} -#endif - -#endif // PHYSDLL_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef PHYSDLL_H +#define PHYSDLL_H +#pragma once + + +#ifdef __cplusplus +#include "vphysics_interface.h" +class IPhysics; +class IPhysicsCollision; + +extern CreateInterfaceFn GetPhysicsFactory( void ); + +extern "C" { +#endif + +// tools need to force the path +void PhysicsDLLPath( const char *pPathname ); + +#ifdef __cplusplus +} +#endif + +#endif // PHYSDLL_H diff --git a/mp/src/utils/common/polylib.cpp b/mp/src/utils/common/polylib.cpp index 36690a27..63c91449 100644 --- a/mp/src/utils/common/polylib.cpp +++ b/mp/src/utils/common/polylib.cpp @@ -1,915 +1,915 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// - -#include "cmdlib.h" -#include "mathlib/mathlib.h" -#include "polylib.h" -#include "worldsize.h" -#include "threads.h" -#include "tier0/dbg.h" - -// doesn't seem to need to be here? -- in threads.h -//extern int numthreads; - -// counters are only bumped when running single threaded, -// because they are an awefull coherence problem -int c_active_windings; -int c_peak_windings; -int c_winding_allocs; -int c_winding_points; - -void pw(winding_t *w) -{ - int i; - for (i=0 ; inumpoints ; i++) - printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); -} - -winding_t *winding_pool[MAX_POINTS_ON_WINDING+4]; - -/* -============= -AllocWinding -============= -*/ -winding_t *AllocWinding (int points) -{ - winding_t *w; - - if (numthreads == 1) - { - c_winding_allocs++; - c_winding_points += points; - c_active_windings++; - if (c_active_windings > c_peak_windings) - c_peak_windings = c_active_windings; - } - ThreadLock(); - if (winding_pool[points]) - { - w = winding_pool[points]; - winding_pool[points] = w->next; - } - else - { - w = (winding_t *)malloc(sizeof(*w)); - w->p = (Vector *)calloc( points, sizeof(Vector) ); - } - ThreadUnlock(); - w->numpoints = 0; // None are occupied yet even though allocated. - w->maxpoints = points; - w->next = NULL; - return w; -} - -void FreeWinding (winding_t *w) -{ - if (w->numpoints == 0xdeaddead) - Error ("FreeWinding: freed a freed winding"); - - ThreadLock(); - w->numpoints = 0xdeaddead; // flag as freed - w->next = winding_pool[w->maxpoints]; - winding_pool[w->maxpoints] = w; - ThreadUnlock(); -} - -/* -============ -RemoveColinearPoints -============ -*/ -int c_removed; - -void RemoveColinearPoints (winding_t *w) -{ - int i, j, k; - Vector v1, v2; - int nump; - Vector p[MAX_POINTS_ON_WINDING]; - - nump = 0; - for (i=0 ; inumpoints ; i++) - { - j = (i+1)%w->numpoints; - k = (i+w->numpoints-1)%w->numpoints; - VectorSubtract (w->p[j], w->p[i], v1); - VectorSubtract (w->p[i], w->p[k], v2); - VectorNormalize(v1); - VectorNormalize(v2); - if (DotProduct(v1, v2) < 0.999) - { - VectorCopy (w->p[i], p[nump]); - nump++; - } - } - - if (nump == w->numpoints) - return; - - if (numthreads == 1) - c_removed += w->numpoints - nump; - w->numpoints = nump; - memcpy (w->p, p, nump*sizeof(p[0])); -} - -/* -============ -WindingPlane -============ -*/ -void WindingPlane (winding_t *w, Vector &normal, vec_t *dist) -{ - Vector v1, v2; - - VectorSubtract (w->p[1], w->p[0], v1); - - // HACKHACK: Avoid potentially collinear verts - if ( w->numpoints > 3 ) - { - VectorSubtract (w->p[3], w->p[0], v2); - } - else - { - VectorSubtract (w->p[2], w->p[0], v2); - } - CrossProduct (v2, v1, normal); - VectorNormalize (normal); - *dist = DotProduct (w->p[0], normal); - -} - - -/* -============= -WindingArea -============= -*/ -vec_t WindingArea(winding_t *w) -{ - int i; - Vector d1, d2, cross; - vec_t total; - - total = 0; - for (i=2 ; inumpoints ; i++) - { - VectorSubtract (w->p[i-1], w->p[0], d1); - VectorSubtract (w->p[i], w->p[0], d2); - CrossProduct (d1, d2, cross); - total += VectorLength ( cross ); - } - return total * 0.5; -} - -void WindingBounds (winding_t *w, Vector &mins, Vector &maxs) -{ - vec_t v; - int i,j; - - mins[0] = mins[1] = mins[2] = 99999; - maxs[0] = maxs[1] = maxs[2] = -99999; - - for (i=0 ; inumpoints ; i++) - { - for (j=0 ; j<3 ; j++) - { - v = w->p[i][j]; - if (v < mins[j]) - mins[j] = v; - if (v > maxs[j]) - maxs[j] = v; - } - } -} - -/* -============= -WindingCenter -============= -*/ -void WindingCenter (winding_t *w, Vector ¢er) -{ - int i; - float scale; - - VectorCopy (vec3_origin, center); - for (i=0 ; inumpoints ; i++) - VectorAdd (w->p[i], center, center); - - scale = 1.0/w->numpoints; - VectorScale (center, scale, center); -} - - - -/* -============= -WindingCenter -============= -*/ -vec_t WindingAreaAndBalancePoint( winding_t *w, Vector ¢er ) -{ - int i; - Vector d1, d2, cross; - vec_t total; - - VectorCopy (vec3_origin, center); - if ( !w ) - return 0.0f; - - total = 0; - for (i=2 ; inumpoints ; i++) - { - VectorSubtract (w->p[i-1], w->p[0], d1); - VectorSubtract (w->p[i], w->p[0], d2); - CrossProduct (d1, d2, cross); - float area = VectorLength ( cross ); - total += area; - - // center of triangle, weighed by area - VectorMA( center, area / 3.0, w->p[i-1], center ); - VectorMA( center, area / 3.0, w->p[i], center ); - VectorMA( center, area / 3.0, w->p[0], center ); - } - if (total) - { - VectorScale( center, 1.0 / total, center ); - } - return total * 0.5; -} - -/* -================= -BaseWindingForPlane -================= -*/ -winding_t *BaseWindingForPlane (const Vector &normal, vec_t dist) -{ - int i, x; - vec_t max, v; - Vector org, vright, vup; - winding_t *w; - -// find the major axis - - max = -1; - x = -1; - for (i=0 ; i<3; i++) - { - v = fabs(normal[i]); - if (v > max) - { - x = i; - max = v; - } - } - if (x==-1) - Error ("BaseWindingForPlane: no axis found"); - - VectorCopy (vec3_origin, vup); - switch (x) - { - case 0: - case 1: - vup[2] = 1; - break; - case 2: - vup[0] = 1; - break; - } - - v = DotProduct (vup, normal); - VectorMA (vup, -v, normal, vup); - VectorNormalize (vup); - - VectorScale (normal, dist, org); - - CrossProduct (vup, normal, vright); - - VectorScale (vup, (MAX_COORD_INTEGER*4), vup); - VectorScale (vright, (MAX_COORD_INTEGER*4), vright); - -// project a really big axis aligned box onto the plane - w = AllocWinding (4); - - VectorSubtract (org, vright, w->p[0]); - VectorAdd (w->p[0], vup, w->p[0]); - - VectorAdd (org, vright, w->p[1]); - VectorAdd (w->p[1], vup, w->p[1]); - - VectorAdd (org, vright, w->p[2]); - VectorSubtract (w->p[2], vup, w->p[2]); - - VectorSubtract (org, vright, w->p[3]); - VectorSubtract (w->p[3], vup, w->p[3]); - - w->numpoints = 4; - - return w; -} - -/* -================== -CopyWinding -================== -*/ -winding_t *CopyWinding (winding_t *w) -{ - int size; - winding_t *c; - - c = AllocWinding (w->numpoints); - c->numpoints = w->numpoints; - size = w->numpoints*sizeof(w->p[0]); - memcpy (c->p, w->p, size); - return c; -} - -/* -================== -ReverseWinding -================== -*/ -winding_t *ReverseWinding (winding_t *w) -{ - int i; - winding_t *c; - - c = AllocWinding (w->numpoints); - for (i=0 ; inumpoints ; i++) - { - VectorCopy (w->p[w->numpoints-1-i], c->p[i]); - } - c->numpoints = w->numpoints; - return c; -} - - -// BUGBUG: Hunt this down - it's causing CSG errors -#pragma optimize("g", off) -/* -============= -ClipWindingEpsilon -============= -*/ - -void ClipWindingEpsilon (winding_t *in, const Vector &normal, vec_t dist, - vec_t epsilon, winding_t **front, winding_t **back) -{ - vec_t dists[MAX_POINTS_ON_WINDING+4]; - int sides[MAX_POINTS_ON_WINDING+4]; - int counts[3]; - vec_t dot; - int i, j; - Vector mid = vec3_origin; - winding_t *f, *b; - int maxpts; - - counts[0] = counts[1] = counts[2] = 0; - -// determine sides for each point - for (i=0 ; inumpoints ; i++) - { - dot = DotProduct (in->p[i], normal); - dot -= dist; - dists[i] = dot; - if (dot > epsilon) - sides[i] = SIDE_FRONT; - else if (dot < -epsilon) - sides[i] = SIDE_BACK; - else - { - sides[i] = SIDE_ON; - } - counts[sides[i]]++; - } - sides[i] = sides[0]; - dists[i] = dists[0]; - - *front = *back = NULL; - - if (!counts[0]) - { - *back = CopyWinding (in); - return; - } - if (!counts[1]) - { - *front = CopyWinding (in); - return; - } - - maxpts = in->numpoints+4; // cant use counts[0]+2 because - // of fp grouping errors - - *front = f = AllocWinding (maxpts); - *back = b = AllocWinding (maxpts); - - for (i=0 ; inumpoints ; i++) - { - Vector& p1 = in->p[i]; - - if (sides[i] == SIDE_ON) - { - VectorCopy (p1, f->p[f->numpoints]); - f->numpoints++; - VectorCopy (p1, b->p[b->numpoints]); - b->numpoints++; - continue; - } - - if (sides[i] == SIDE_FRONT) - { - VectorCopy (p1, f->p[f->numpoints]); - f->numpoints++; - } - if (sides[i] == SIDE_BACK) - { - VectorCopy (p1, b->p[b->numpoints]); - b->numpoints++; - } - - if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) - continue; - - // generate a split point - Vector& p2 = in->p[(i+1)%in->numpoints]; - - dot = dists[i] / (dists[i]-dists[i+1]); - for (j=0 ; j<3 ; j++) - { // avoid round off error when possible - if (normal[j] == 1) - mid[j] = dist; - else if (normal[j] == -1) - mid[j] = -dist; - else - mid[j] = p1[j] + dot*(p2[j]-p1[j]); - } - - VectorCopy (mid, f->p[f->numpoints]); - f->numpoints++; - VectorCopy (mid, b->p[b->numpoints]); - b->numpoints++; - } - - if (f->numpoints > maxpts || b->numpoints > maxpts) - Error ("ClipWinding: points exceeded estimate"); - if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) - Error ("ClipWinding: MAX_POINTS_ON_WINDING"); -} -#pragma optimize("", on) - - -// NOTE: This is identical to ClipWindingEpsilon, but it does a pre/post translation to improve precision -void ClipWindingEpsilon_Offset( winding_t *in, const Vector &normal, vec_t dist, vec_t epsilon, winding_t **front, winding_t **back, const Vector &offset ) -{ - TranslateWinding( in, offset ); - ClipWindingEpsilon( in, normal, dist+DotProduct(offset,normal), epsilon, front, back ); - TranslateWinding( in, -offset ); - if ( front && *front ) - { - TranslateWinding( *front, -offset ); - } - if ( back && *back ) - { - TranslateWinding( *back, -offset ); - } -} - -void ClassifyWindingEpsilon_Offset( winding_t *in, const Vector &normal, vec_t dist, vec_t epsilon, winding_t **front, winding_t **back, winding_t **on, const Vector &offset) -{ - TranslateWinding( in, offset ); - ClassifyWindingEpsilon( in, normal, dist+DotProduct(offset,normal), epsilon, front, back, on ); - TranslateWinding( in, -offset ); - if ( front && *front ) - { - TranslateWinding( *front, -offset ); - } - if ( back && *back ) - { - TranslateWinding( *back, -offset ); - } - if ( on && *on ) - { - TranslateWinding( *on, -offset ); - } -} - -/* -============= -ClassifyWindingEpsilon -============= -*/ -// This version returns the winding as "on" if all verts lie in the plane -void ClassifyWindingEpsilon( winding_t *in, const Vector &normal, vec_t dist, - vec_t epsilon, winding_t **front, winding_t **back, winding_t **on) -{ - vec_t dists[MAX_POINTS_ON_WINDING+4]; - int sides[MAX_POINTS_ON_WINDING+4]; - int counts[3]; - vec_t dot; - int i, j; - Vector mid = vec3_origin; - winding_t *f, *b; - int maxpts; - - counts[0] = counts[1] = counts[2] = 0; - -// determine sides for each point - for (i=0 ; inumpoints ; i++) - { - dot = DotProduct (in->p[i], normal); - dot -= dist; - dists[i] = dot; - if (dot > epsilon) - sides[i] = SIDE_FRONT; - else if (dot < -epsilon) - sides[i] = SIDE_BACK; - else - { - sides[i] = SIDE_ON; - } - counts[sides[i]]++; - } - sides[i] = sides[0]; - dists[i] = dists[0]; - - *front = *back = *on = NULL; - - if ( !counts[0] && !counts[1] ) - { - *on = CopyWinding(in); - return; - } - - if (!counts[0]) - { - *back = CopyWinding(in); - return; - } - if (!counts[1]) - { - *front = CopyWinding(in); - return; - } - - maxpts = in->numpoints+4; // cant use counts[0]+2 because - // of fp grouping errors - - *front = f = AllocWinding (maxpts); - *back = b = AllocWinding (maxpts); - - for (i=0 ; inumpoints ; i++) - { - Vector& p1 = in->p[i]; - - if (sides[i] == SIDE_ON) - { - VectorCopy (p1, f->p[f->numpoints]); - f->numpoints++; - VectorCopy (p1, b->p[b->numpoints]); - b->numpoints++; - continue; - } - - if (sides[i] == SIDE_FRONT) - { - VectorCopy (p1, f->p[f->numpoints]); - f->numpoints++; - } - if (sides[i] == SIDE_BACK) - { - VectorCopy (p1, b->p[b->numpoints]); - b->numpoints++; - } - - if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) - continue; - - // generate a split point - Vector& p2 = in->p[(i+1)%in->numpoints]; - - dot = dists[i] / (dists[i]-dists[i+1]); - for (j=0 ; j<3 ; j++) - { // avoid round off error when possible - if (normal[j] == 1) - mid[j] = dist; - else if (normal[j] == -1) - mid[j] = -dist; - else - mid[j] = p1[j] + dot*(p2[j]-p1[j]); - } - - VectorCopy (mid, f->p[f->numpoints]); - f->numpoints++; - VectorCopy (mid, b->p[b->numpoints]); - b->numpoints++; - } - - if (f->numpoints > maxpts || b->numpoints > maxpts) - Error ("ClipWinding: points exceeded estimate"); - if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) - Error ("ClipWinding: MAX_POINTS_ON_WINDING"); -} - -/* -============= -ChopWindingInPlace -============= -*/ -void ChopWindingInPlace (winding_t **inout, const Vector &normal, vec_t dist, vec_t epsilon) -{ - winding_t *in; - vec_t dists[MAX_POINTS_ON_WINDING+4]; - int sides[MAX_POINTS_ON_WINDING+4]; - int counts[3]; - vec_t dot; - int i, j; - Vector mid = vec3_origin; - winding_t *f; - int maxpts; - - in = *inout; - counts[0] = counts[1] = counts[2] = 0; -// determine sides for each point - for (i=0 ; inumpoints ; i++) - { - dot = DotProduct (in->p[i], normal); - dot -= dist; - dists[i] = dot; - if (dot > epsilon) - { - sides[i] = SIDE_FRONT; - } - else if (dot < -epsilon) - { - sides[i] = SIDE_BACK; - } - else - { - sides[i] = SIDE_ON; - } - counts[sides[i]]++; - } - sides[i] = sides[0]; - dists[i] = dists[0]; - - if (!counts[0]) - { - FreeWinding (in); - *inout = NULL; - return; - } - if (!counts[1]) - return; // inout stays the same - - maxpts = in->numpoints+4; // cant use counts[0]+2 because - // of fp grouping errors - - f = AllocWinding (maxpts); - - for (i=0 ; inumpoints ; i++) - { - Vector& p1 = in->p[i]; - - if (sides[i] == SIDE_ON) - { - VectorCopy (p1, f->p[f->numpoints]); - f->numpoints++; - continue; - } - - if (sides[i] == SIDE_FRONT) - { - VectorCopy (p1, f->p[f->numpoints]); - f->numpoints++; - } - - if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) - continue; - - // generate a split point - Vector& p2 = in->p[(i+1)%in->numpoints]; - - dot = dists[i] / (dists[i]-dists[i+1]); - for (j=0 ; j<3 ; j++) - { // avoid round off error when possible - if (normal[j] == 1) - mid[j] = dist; - else if (normal[j] == -1) - mid[j] = -dist; - else - mid[j] = p1[j] + dot*(p2[j]-p1[j]); - } - - VectorCopy (mid, f->p[f->numpoints]); - f->numpoints++; - } - - if (f->numpoints > maxpts) - Error ("ClipWinding: points exceeded estimate"); - if (f->numpoints > MAX_POINTS_ON_WINDING) - Error ("ClipWinding: MAX_POINTS_ON_WINDING"); - - FreeWinding (in); - *inout = f; -} - - -/* -================= -ChopWinding - -Returns the fragment of in that is on the front side -of the cliping plane. The original is freed. -================= -*/ -winding_t *ChopWinding (winding_t *in, const Vector &normal, vec_t dist) -{ - winding_t *f, *b; - - ClipWindingEpsilon (in, normal, dist, ON_EPSILON, &f, &b); - FreeWinding (in); - if (b) - FreeWinding (b); - return f; -} - - -/* -================= -CheckWinding - -================= -*/ -void CheckWinding (winding_t *w) -{ - int i, j; - vec_t d, edgedist; - Vector dir, edgenormal, facenormal; - vec_t area; - vec_t facedist; - - if (w->numpoints < 3) - Error ("CheckWinding: %i points",w->numpoints); - - area = WindingArea(w); - if (area < 1) - Error ("CheckWinding: %f area", area); - - WindingPlane (w, facenormal, &facedist); - - for (i=0 ; inumpoints ; i++) - { - Vector& p1 = w->p[i]; - - for (j=0 ; j<3 ; j++) - { - if (p1[j] > MAX_COORD_INTEGER || p1[j] < MIN_COORD_INTEGER) - Error ("CheckFace: out of range: %f",p1[j]); - } - - j = i+1 == w->numpoints ? 0 : i+1; - - // check the point is on the face plane - d = DotProduct (p1, facenormal) - facedist; - if (d < -ON_EPSILON || d > ON_EPSILON) - Error ("CheckWinding: point off plane"); - - // check the edge isnt degenerate - Vector& p2 = w->p[j]; - VectorSubtract (p2, p1, dir); - - if (VectorLength (dir) < ON_EPSILON) - Error ("CheckWinding: degenerate edge"); - - CrossProduct (facenormal, dir, edgenormal); - VectorNormalize (edgenormal); - edgedist = DotProduct (p1, edgenormal); - edgedist += ON_EPSILON; - - // all other points must be on front side - for (j=0 ; jnumpoints ; j++) - { - if (j == i) - continue; - d = DotProduct (w->p[j], edgenormal); - if (d > edgedist) - Error ("CheckWinding: non-convex"); - } - } -} - - -/* -============ -WindingOnPlaneSide -============ -*/ -int WindingOnPlaneSide (winding_t *w, const Vector &normal, vec_t dist) -{ - qboolean front, back; - int i; - vec_t d; - - front = false; - back = false; - for (i=0 ; inumpoints ; i++) - { - d = DotProduct (w->p[i], normal) - dist; - if (d < -ON_EPSILON) - { - if (front) - return SIDE_CROSS; - back = true; - continue; - } - if (d > ON_EPSILON) - { - if (back) - return SIDE_CROSS; - front = true; - continue; - } - } - - if (back) - return SIDE_BACK; - if (front) - return SIDE_FRONT; - return SIDE_ON; -} - - -//----------------------------------------------------------------------------- -// Purpose: 2d point inside of winding test (assumes the point resides in the -// winding plane) -//----------------------------------------------------------------------------- -bool PointInWinding( const Vector &pt, winding_t *pWinding ) -{ - if( !pWinding ) - return false; - -#if 0 - // - // NOTE: this will be a quicker way to calculate this, however I don't - // know the trick off hand (post dot product tests??) - // TODO: look in graphics gems!!!! (cab) - // - - Vector edge1, edge2; - for( int ndxPt = 0; ndxPt < pWinding->numpoints; ndxPt++ ) - { - edge1 = pWinding->p[ndxPt] - pt; - edge2 = pWinding->p[(ndxPt+1)%pWinding->numpoints] - pt; - - VectorNormalize( edge1 ); - VectorNormalize( edge2 ); - - if( edge2.Dot( edge1 ) < 0.0f ) - return false; - } - - return true; - -#else - Vector edge, toPt, cross, testCross; - - // - // get the first normal to test - // - toPt = pt - pWinding->p[0]; - edge = pWinding->p[1] - pWinding->p[0]; - testCross = edge.Cross( toPt ); - VectorNormalize( testCross ); - - for( int ndxPt = 1; ndxPt < pWinding->numpoints; ndxPt++ ) - { - toPt = pt - pWinding->p[ndxPt]; - edge = pWinding->p[(ndxPt+1)%pWinding->numpoints] - pWinding->p[ndxPt]; - cross = edge.Cross( toPt ); - VectorNormalize( cross ); - - if( cross.Dot( testCross ) < 0.0f ) - return false; - } - - return true; -#endif -} - -void TranslateWinding( winding_t *pWinding, const Vector &offset ) -{ - for ( int i = 0; i < pWinding->numpoints; i++ ) - { - pWinding->p[i] += offset; - } -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#include "cmdlib.h" +#include "mathlib/mathlib.h" +#include "polylib.h" +#include "worldsize.h" +#include "threads.h" +#include "tier0/dbg.h" + +// doesn't seem to need to be here? -- in threads.h +//extern int numthreads; + +// counters are only bumped when running single threaded, +// because they are an awefull coherence problem +int c_active_windings; +int c_peak_windings; +int c_winding_allocs; +int c_winding_points; + +void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); +} + +winding_t *winding_pool[MAX_POINTS_ON_WINDING+4]; + +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding (int points) +{ + winding_t *w; + + if (numthreads == 1) + { + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if (c_active_windings > c_peak_windings) + c_peak_windings = c_active_windings; + } + ThreadLock(); + if (winding_pool[points]) + { + w = winding_pool[points]; + winding_pool[points] = w->next; + } + else + { + w = (winding_t *)malloc(sizeof(*w)); + w->p = (Vector *)calloc( points, sizeof(Vector) ); + } + ThreadUnlock(); + w->numpoints = 0; // None are occupied yet even though allocated. + w->maxpoints = points; + w->next = NULL; + return w; +} + +void FreeWinding (winding_t *w) +{ + if (w->numpoints == 0xdeaddead) + Error ("FreeWinding: freed a freed winding"); + + ThreadLock(); + w->numpoints = 0xdeaddead; // flag as freed + w->next = winding_pool[w->maxpoints]; + winding_pool[w->maxpoints] = w; + ThreadUnlock(); +} + +/* +============ +RemoveColinearPoints +============ +*/ +int c_removed; + +void RemoveColinearPoints (winding_t *w) +{ + int i, j, k; + Vector v1, v2; + int nump; + Vector p[MAX_POINTS_ON_WINDING]; + + nump = 0; + for (i=0 ; inumpoints ; i++) + { + j = (i+1)%w->numpoints; + k = (i+w->numpoints-1)%w->numpoints; + VectorSubtract (w->p[j], w->p[i], v1); + VectorSubtract (w->p[i], w->p[k], v2); + VectorNormalize(v1); + VectorNormalize(v2); + if (DotProduct(v1, v2) < 0.999) + { + VectorCopy (w->p[i], p[nump]); + nump++; + } + } + + if (nump == w->numpoints) + return; + + if (numthreads == 1) + c_removed += w->numpoints - nump; + w->numpoints = nump; + memcpy (w->p, p, nump*sizeof(p[0])); +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane (winding_t *w, Vector &normal, vec_t *dist) +{ + Vector v1, v2; + + VectorSubtract (w->p[1], w->p[0], v1); + + // HACKHACK: Avoid potentially collinear verts + if ( w->numpoints > 3 ) + { + VectorSubtract (w->p[3], w->p[0], v2); + } + else + { + VectorSubtract (w->p[2], w->p[0], v2); + } + CrossProduct (v2, v1, normal); + VectorNormalize (normal); + *dist = DotProduct (w->p[0], normal); + +} + + +/* +============= +WindingArea +============= +*/ +vec_t WindingArea(winding_t *w) +{ + int i; + Vector d1, d2, cross; + vec_t total; + + total = 0; + for (i=2 ; inumpoints ; i++) + { + VectorSubtract (w->p[i-1], w->p[0], d1); + VectorSubtract (w->p[i], w->p[0], d2); + CrossProduct (d1, d2, cross); + total += VectorLength ( cross ); + } + return total * 0.5; +} + +void WindingBounds (winding_t *w, Vector &mins, Vector &maxs) +{ + vec_t v; + int i,j; + + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + { + v = w->p[i][j]; + if (v < mins[j]) + mins[j] = v; + if (v > maxs[j]) + maxs[j] = v; + } + } +} + +/* +============= +WindingCenter +============= +*/ +void WindingCenter (winding_t *w, Vector ¢er) +{ + int i; + float scale; + + VectorCopy (vec3_origin, center); + for (i=0 ; inumpoints ; i++) + VectorAdd (w->p[i], center, center); + + scale = 1.0/w->numpoints; + VectorScale (center, scale, center); +} + + + +/* +============= +WindingCenter +============= +*/ +vec_t WindingAreaAndBalancePoint( winding_t *w, Vector ¢er ) +{ + int i; + Vector d1, d2, cross; + vec_t total; + + VectorCopy (vec3_origin, center); + if ( !w ) + return 0.0f; + + total = 0; + for (i=2 ; inumpoints ; i++) + { + VectorSubtract (w->p[i-1], w->p[0], d1); + VectorSubtract (w->p[i], w->p[0], d2); + CrossProduct (d1, d2, cross); + float area = VectorLength ( cross ); + total += area; + + // center of triangle, weighed by area + VectorMA( center, area / 3.0, w->p[i-1], center ); + VectorMA( center, area / 3.0, w->p[i], center ); + VectorMA( center, area / 3.0, w->p[0], center ); + } + if (total) + { + VectorScale( center, 1.0 / total, center ); + } + return total * 0.5; +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane (const Vector &normal, vec_t dist) +{ + int i, x; + vec_t max, v; + Vector org, vright, vup; + winding_t *w; + +// find the major axis + + max = -1; + x = -1; + for (i=0 ; i<3; i++) + { + v = fabs(normal[i]); + if (v > max) + { + x = i; + max = v; + } + } + if (x==-1) + Error ("BaseWindingForPlane: no axis found"); + + VectorCopy (vec3_origin, vup); + switch (x) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct (vup, normal); + VectorMA (vup, -v, normal, vup); + VectorNormalize (vup); + + VectorScale (normal, dist, org); + + CrossProduct (vup, normal, vright); + + VectorScale (vup, (MAX_COORD_INTEGER*4), vup); + VectorScale (vright, (MAX_COORD_INTEGER*4), vright); + +// project a really big axis aligned box onto the plane + w = AllocWinding (4); + + VectorSubtract (org, vright, w->p[0]); + VectorAdd (w->p[0], vup, w->p[0]); + + VectorAdd (org, vright, w->p[1]); + VectorAdd (w->p[1], vup, w->p[1]); + + VectorAdd (org, vright, w->p[2]); + VectorSubtract (w->p[2], vup, w->p[2]); + + VectorSubtract (org, vright, w->p[3]); + VectorSubtract (w->p[3], vup, w->p[3]); + + w->numpoints = 4; + + return w; +} + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding (winding_t *w) +{ + int size; + winding_t *c; + + c = AllocWinding (w->numpoints); + c->numpoints = w->numpoints; + size = w->numpoints*sizeof(w->p[0]); + memcpy (c->p, w->p, size); + return c; +} + +/* +================== +ReverseWinding +================== +*/ +winding_t *ReverseWinding (winding_t *w) +{ + int i; + winding_t *c; + + c = AllocWinding (w->numpoints); + for (i=0 ; inumpoints ; i++) + { + VectorCopy (w->p[w->numpoints-1-i], c->p[i]); + } + c->numpoints = w->numpoints; + return c; +} + + +// BUGBUG: Hunt this down - it's causing CSG errors +#pragma optimize("g", off) +/* +============= +ClipWindingEpsilon +============= +*/ + +void ClipWindingEpsilon (winding_t *in, const Vector &normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + Vector mid = vec3_origin; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if (!counts[0]) + { + *back = CopyWinding (in); + return; + } + if (!counts[1]) + { + *front = CopyWinding (in); + return; + } + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding (maxpts); + *back = b = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + Vector& p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + if (sides[i] == SIDE_BACK) + { + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + Vector& p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); +} +#pragma optimize("", on) + + +// NOTE: This is identical to ClipWindingEpsilon, but it does a pre/post translation to improve precision +void ClipWindingEpsilon_Offset( winding_t *in, const Vector &normal, vec_t dist, vec_t epsilon, winding_t **front, winding_t **back, const Vector &offset ) +{ + TranslateWinding( in, offset ); + ClipWindingEpsilon( in, normal, dist+DotProduct(offset,normal), epsilon, front, back ); + TranslateWinding( in, -offset ); + if ( front && *front ) + { + TranslateWinding( *front, -offset ); + } + if ( back && *back ) + { + TranslateWinding( *back, -offset ); + } +} + +void ClassifyWindingEpsilon_Offset( winding_t *in, const Vector &normal, vec_t dist, vec_t epsilon, winding_t **front, winding_t **back, winding_t **on, const Vector &offset) +{ + TranslateWinding( in, offset ); + ClassifyWindingEpsilon( in, normal, dist+DotProduct(offset,normal), epsilon, front, back, on ); + TranslateWinding( in, -offset ); + if ( front && *front ) + { + TranslateWinding( *front, -offset ); + } + if ( back && *back ) + { + TranslateWinding( *back, -offset ); + } + if ( on && *on ) + { + TranslateWinding( *on, -offset ); + } +} + +/* +============= +ClassifyWindingEpsilon +============= +*/ +// This version returns the winding as "on" if all verts lie in the plane +void ClassifyWindingEpsilon( winding_t *in, const Vector &normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back, winding_t **on) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + Vector mid = vec3_origin; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = *on = NULL; + + if ( !counts[0] && !counts[1] ) + { + *on = CopyWinding(in); + return; + } + + if (!counts[0]) + { + *back = CopyWinding(in); + return; + } + if (!counts[1]) + { + *front = CopyWinding(in); + return; + } + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding (maxpts); + *back = b = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + Vector& p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + if (sides[i] == SIDE_BACK) + { + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + Vector& p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); +} + +/* +============= +ChopWindingInPlace +============= +*/ +void ChopWindingInPlace (winding_t **inout, const Vector &normal, vec_t dist, vec_t epsilon) +{ + winding_t *in; + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + Vector mid = vec3_origin; + winding_t *f; + int maxpts; + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + { + sides[i] = SIDE_FRONT; + } + else if (dot < -epsilon) + { + sides[i] = SIDE_BACK; + } + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + { + FreeWinding (in); + *inout = NULL; + return; + } + if (!counts[1]) + return; // inout stays the same + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + f = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + Vector& p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + Vector& p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + } + + if (f->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); + + FreeWinding (in); + *inout = f; +} + + +/* +================= +ChopWinding + +Returns the fragment of in that is on the front side +of the cliping plane. The original is freed. +================= +*/ +winding_t *ChopWinding (winding_t *in, const Vector &normal, vec_t dist) +{ + winding_t *f, *b; + + ClipWindingEpsilon (in, normal, dist, ON_EPSILON, &f, &b); + FreeWinding (in); + if (b) + FreeWinding (b); + return f; +} + + +/* +================= +CheckWinding + +================= +*/ +void CheckWinding (winding_t *w) +{ + int i, j; + vec_t d, edgedist; + Vector dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if (w->numpoints < 3) + Error ("CheckWinding: %i points",w->numpoints); + + area = WindingArea(w); + if (area < 1) + Error ("CheckWinding: %f area", area); + + WindingPlane (w, facenormal, &facedist); + + for (i=0 ; inumpoints ; i++) + { + Vector& p1 = w->p[i]; + + for (j=0 ; j<3 ; j++) + { + if (p1[j] > MAX_COORD_INTEGER || p1[j] < MIN_COORD_INTEGER) + Error ("CheckFace: out of range: %f",p1[j]); + } + + j = i+1 == w->numpoints ? 0 : i+1; + + // check the point is on the face plane + d = DotProduct (p1, facenormal) - facedist; + if (d < -ON_EPSILON || d > ON_EPSILON) + Error ("CheckWinding: point off plane"); + + // check the edge isnt degenerate + Vector& p2 = w->p[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + Error ("CheckWinding: degenerate edge"); + + CrossProduct (facenormal, dir, edgenormal); + VectorNormalize (edgenormal); + edgedist = DotProduct (p1, edgenormal); + edgedist += ON_EPSILON; + + // all other points must be on front side + for (j=0 ; jnumpoints ; j++) + { + if (j == i) + continue; + d = DotProduct (w->p[j], edgenormal); + if (d > edgedist) + Error ("CheckWinding: non-convex"); + } + } +} + + +/* +============ +WindingOnPlaneSide +============ +*/ +int WindingOnPlaneSide (winding_t *w, const Vector &normal, vec_t dist) +{ + qboolean front, back; + int i; + vec_t d; + + front = false; + back = false; + for (i=0 ; inumpoints ; i++) + { + d = DotProduct (w->p[i], normal) - dist; + if (d < -ON_EPSILON) + { + if (front) + return SIDE_CROSS; + back = true; + continue; + } + if (d > ON_EPSILON) + { + if (back) + return SIDE_CROSS; + front = true; + continue; + } + } + + if (back) + return SIDE_BACK; + if (front) + return SIDE_FRONT; + return SIDE_ON; +} + + +//----------------------------------------------------------------------------- +// Purpose: 2d point inside of winding test (assumes the point resides in the +// winding plane) +//----------------------------------------------------------------------------- +bool PointInWinding( const Vector &pt, winding_t *pWinding ) +{ + if( !pWinding ) + return false; + +#if 0 + // + // NOTE: this will be a quicker way to calculate this, however I don't + // know the trick off hand (post dot product tests??) + // TODO: look in graphics gems!!!! (cab) + // + + Vector edge1, edge2; + for( int ndxPt = 0; ndxPt < pWinding->numpoints; ndxPt++ ) + { + edge1 = pWinding->p[ndxPt] - pt; + edge2 = pWinding->p[(ndxPt+1)%pWinding->numpoints] - pt; + + VectorNormalize( edge1 ); + VectorNormalize( edge2 ); + + if( edge2.Dot( edge1 ) < 0.0f ) + return false; + } + + return true; + +#else + Vector edge, toPt, cross, testCross; + + // + // get the first normal to test + // + toPt = pt - pWinding->p[0]; + edge = pWinding->p[1] - pWinding->p[0]; + testCross = edge.Cross( toPt ); + VectorNormalize( testCross ); + + for( int ndxPt = 1; ndxPt < pWinding->numpoints; ndxPt++ ) + { + toPt = pt - pWinding->p[ndxPt]; + edge = pWinding->p[(ndxPt+1)%pWinding->numpoints] - pWinding->p[ndxPt]; + cross = edge.Cross( toPt ); + VectorNormalize( cross ); + + if( cross.Dot( testCross ) < 0.0f ) + return false; + } + + return true; +#endif +} + +void TranslateWinding( winding_t *pWinding, const Vector &offset ) +{ + for ( int i = 0; i < pWinding->numpoints; i++ ) + { + pWinding->p[i] += offset; + } +} diff --git a/mp/src/utils/common/polylib.h b/mp/src/utils/common/polylib.h index 1750a82a..8753dccc 100644 --- a/mp/src/utils/common/polylib.h +++ b/mp/src/utils/common/polylib.h @@ -1,78 +1,78 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef POLYLIB_H -#define POLYLIB_H -#pragma once - -#ifndef MATHLIB_H -#include "mathlib/mathlib.h" -#endif - -struct winding_t -{ - int numpoints; - Vector *p; // variable sized - int maxpoints; - winding_t *next; -}; - -#define MAX_POINTS_ON_WINDING 64 - -// you can define on_epsilon in the makefile as tighter -// point on plane side epsilon -// todo: need a world-space epsilon, a lightmap-space epsilon, and a texture space epsilon -// or at least convert from a world-space epsilon to lightmap and texture space epsilons -#ifndef ON_EPSILON -#define ON_EPSILON 0.1 -#endif - - -winding_t *AllocWinding (int points); -vec_t WindingArea (winding_t *w); -void WindingCenter (winding_t *w, Vector ¢er); -vec_t WindingAreaAndBalancePoint( winding_t *w, Vector ¢er ); -void ClipWindingEpsilon (winding_t *in, const Vector &normal, vec_t dist, - vec_t epsilon, winding_t **front, winding_t **back); - -// translates everything by offset, then does the clip, then translates back (to keep precision) -void ClipWindingEpsilon_Offset( winding_t *in, const Vector &normal, vec_t dist, vec_t epsilon, winding_t **front, winding_t **back, const Vector &offset ); - -void ClassifyWindingEpsilon( winding_t *in, const Vector &normal, vec_t dist, - vec_t epsilon, winding_t **front, winding_t **back, winding_t **on); -void ClassifyWindingEpsilon_Offset( winding_t *in, const Vector &normal, vec_t dist, - vec_t epsilon, winding_t **front, winding_t **back, winding_t **on, const Vector &offset); - -winding_t *ChopWinding (winding_t *in, const Vector &normal, vec_t dist); -winding_t *CopyWinding (winding_t *w); -winding_t *ReverseWinding (winding_t *w); -winding_t *BaseWindingForPlane (const Vector &normal, vec_t dist); -void CheckWinding (winding_t *w); -void WindingPlane (winding_t *w, Vector &normal, vec_t *dist); -void RemoveColinearPoints (winding_t *w); -int WindingOnPlaneSide (winding_t *w, const Vector &normal, vec_t dist); -void FreeWinding (winding_t *w); -void WindingBounds (winding_t *w, Vector &mins, Vector &maxs); - -void ChopWindingInPlace (winding_t **w, const Vector &normal, vec_t dist, vec_t epsilon); -// frees the original if clipped - -bool PointInWinding( Vector const &pt, winding_t *pWinding ); - -// translates a winding by offset -void TranslateWinding( winding_t *pWinding, const Vector &offset ); - -void pw(winding_t *w); - - -#endif // POLYLIB_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef POLYLIB_H +#define POLYLIB_H +#pragma once + +#ifndef MATHLIB_H +#include "mathlib/mathlib.h" +#endif + +struct winding_t +{ + int numpoints; + Vector *p; // variable sized + int maxpoints; + winding_t *next; +}; + +#define MAX_POINTS_ON_WINDING 64 + +// you can define on_epsilon in the makefile as tighter +// point on plane side epsilon +// todo: need a world-space epsilon, a lightmap-space epsilon, and a texture space epsilon +// or at least convert from a world-space epsilon to lightmap and texture space epsilons +#ifndef ON_EPSILON +#define ON_EPSILON 0.1 +#endif + + +winding_t *AllocWinding (int points); +vec_t WindingArea (winding_t *w); +void WindingCenter (winding_t *w, Vector ¢er); +vec_t WindingAreaAndBalancePoint( winding_t *w, Vector ¢er ); +void ClipWindingEpsilon (winding_t *in, const Vector &normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back); + +// translates everything by offset, then does the clip, then translates back (to keep precision) +void ClipWindingEpsilon_Offset( winding_t *in, const Vector &normal, vec_t dist, vec_t epsilon, winding_t **front, winding_t **back, const Vector &offset ); + +void ClassifyWindingEpsilon( winding_t *in, const Vector &normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back, winding_t **on); +void ClassifyWindingEpsilon_Offset( winding_t *in, const Vector &normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back, winding_t **on, const Vector &offset); + +winding_t *ChopWinding (winding_t *in, const Vector &normal, vec_t dist); +winding_t *CopyWinding (winding_t *w); +winding_t *ReverseWinding (winding_t *w); +winding_t *BaseWindingForPlane (const Vector &normal, vec_t dist); +void CheckWinding (winding_t *w); +void WindingPlane (winding_t *w, Vector &normal, vec_t *dist); +void RemoveColinearPoints (winding_t *w); +int WindingOnPlaneSide (winding_t *w, const Vector &normal, vec_t dist); +void FreeWinding (winding_t *w); +void WindingBounds (winding_t *w, Vector &mins, Vector &maxs); + +void ChopWindingInPlace (winding_t **w, const Vector &normal, vec_t dist, vec_t epsilon); +// frees the original if clipped + +bool PointInWinding( Vector const &pt, winding_t *pWinding ); + +// translates a winding by offset +void TranslateWinding( winding_t *pWinding, const Vector &offset ); + +void pw(winding_t *w); + + +#endif // POLYLIB_H diff --git a/mp/src/utils/common/qfiles.h b/mp/src/utils/common/qfiles.h index b1d2232f..fc122c33 100644 --- a/mp/src/utils/common/qfiles.h +++ b/mp/src/utils/common/qfiles.h @@ -1,42 +1,42 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef QFILES_H -#define QFILES_H -#pragma once - - -// -// qfiles.h: quake file formats -// This file must be identical in the quake and utils directories -// - -#include "basetypes.h" -#include "commonmacros.h" -#include "worldsize.h" -#include "bspfile.h" - -#define MAX_OSPATH 260 -#define MAX_QPATH 64 - -/* -======================================================================== - -The .pak files are just a linear collapse of a directory tree - -======================================================================== -*/ - -#define IDPAKHEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') - -#endif // QFILES_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef QFILES_H +#define QFILES_H +#pragma once + + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +#include "basetypes.h" +#include "commonmacros.h" +#include "worldsize.h" +#include "bspfile.h" + +#define MAX_OSPATH 260 +#define MAX_QPATH 64 + +/* +======================================================================== + +The .pak files are just a linear collapse of a directory tree + +======================================================================== +*/ + +#define IDPAKHEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') + +#endif // QFILES_H diff --git a/mp/src/utils/common/scratchpad_helpers.cpp b/mp/src/utils/common/scratchpad_helpers.cpp index 9a3c2d74..ecab731b 100644 --- a/mp/src/utils/common/scratchpad_helpers.cpp +++ b/mp/src/utils/common/scratchpad_helpers.cpp @@ -1,103 +1,103 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#include "scratchpad_helpers.h" -#include "bspfile.h" -#include "bsplib.h" - - -void ScratchPad_DrawWinding( - IScratchPad3D *pPad, - int nPoints, - Vector *pPoints, - Vector vColor, - Vector vOffset ) -{ - for ( int i=0; i < nPoints; i++ ) - { - pPad->DrawLine( CSPVert( pPoints[i]+vOffset, vColor ), CSPVert( pPoints[(i+1)%nPoints]+vOffset, vColor ) ); - } -} - - -void ScratchPad_DrawFace( IScratchPad3D *pPad, dface_t *f, int iFaceNumber, const CSPColor &faceColor, const Vector &vOffset ) -{ - // Draw the face's outline, then put text for its face index on it too. - CUtlVector points; - for ( int iEdge = 0; iEdge < f->numedges; iEdge++ ) - { - int v; - int se = dsurfedges[f->firstedge + iEdge]; - if ( se < 0 ) - v = dedges[-se].v[1]; - else - v = dedges[se].v[0]; - - dvertex_t *dv = &dvertexes[v]; - points.AddToTail( dv->point ); - } - - // Draw the outline. - Vector vCenter( 0, 0, 0 ); - for ( int iEdge=0; iEdge < points.Count(); iEdge++ ) - { - pPad->DrawLine( CSPVert( points[iEdge]+vOffset, faceColor ), CSPVert( points[(iEdge+1)%points.Count()]+vOffset, faceColor ) ); - vCenter += points[iEdge]; - } - vCenter /= points.Count(); - vCenter += vOffset; - - // Draw the text. - if ( iFaceNumber != -1 ) - { - char str[64]; - Q_snprintf( str, sizeof( str ), "%d", iFaceNumber ); - - CTextParams params; - - params.m_bCentered = true; - params.m_bOutline = true; - params.m_flLetterWidth = 2; - params.m_vColor.Init( 1, 0, 0 ); - - VectorAngles( dplanes[f->planenum].normal, params.m_vAngles ); - params.m_bTwoSided = true; - - params.m_vPos = vCenter; - - pPad->DrawText( str, params ); - } -} - - -void ScratchPad_DrawWorld( IScratchPad3D *pPad, bool bDrawFaceNumbers, const CSPColor &faceColor ) -{ - bool bAutoFlush = pPad->GetAutoFlush(); - pPad->SetAutoFlush( false ); - - for ( int i=0; i < numleafs; i++ ) - { - dleaf_t *l = &dleafs[i]; - if ( l->contents & CONTENTS_DETAIL ) - continue; - - for ( int z=0; z < l->numleaffaces; z++ ) - { - int iFace = dleaffaces[l->firstleafface+z]; - dface_t *f = &dfaces[iFace]; - ScratchPad_DrawFace( pPad, f, bDrawFaceNumbers ? i : -1 ); - } - } - - pPad->SetAutoFlush( bAutoFlush ); -} - - -void ScratchPad_DrawWorld( bool bDrawFaceNumbers, const CSPColor &faceColor ) -{ - IScratchPad3D *pPad = ScratchPad3D_Create(); - ScratchPad_DrawWorld( pPad, bDrawFaceNumbers ); -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "scratchpad_helpers.h" +#include "bspfile.h" +#include "bsplib.h" + + +void ScratchPad_DrawWinding( + IScratchPad3D *pPad, + int nPoints, + Vector *pPoints, + Vector vColor, + Vector vOffset ) +{ + for ( int i=0; i < nPoints; i++ ) + { + pPad->DrawLine( CSPVert( pPoints[i]+vOffset, vColor ), CSPVert( pPoints[(i+1)%nPoints]+vOffset, vColor ) ); + } +} + + +void ScratchPad_DrawFace( IScratchPad3D *pPad, dface_t *f, int iFaceNumber, const CSPColor &faceColor, const Vector &vOffset ) +{ + // Draw the face's outline, then put text for its face index on it too. + CUtlVector points; + for ( int iEdge = 0; iEdge < f->numedges; iEdge++ ) + { + int v; + int se = dsurfedges[f->firstedge + iEdge]; + if ( se < 0 ) + v = dedges[-se].v[1]; + else + v = dedges[se].v[0]; + + dvertex_t *dv = &dvertexes[v]; + points.AddToTail( dv->point ); + } + + // Draw the outline. + Vector vCenter( 0, 0, 0 ); + for ( int iEdge=0; iEdge < points.Count(); iEdge++ ) + { + pPad->DrawLine( CSPVert( points[iEdge]+vOffset, faceColor ), CSPVert( points[(iEdge+1)%points.Count()]+vOffset, faceColor ) ); + vCenter += points[iEdge]; + } + vCenter /= points.Count(); + vCenter += vOffset; + + // Draw the text. + if ( iFaceNumber != -1 ) + { + char str[64]; + Q_snprintf( str, sizeof( str ), "%d", iFaceNumber ); + + CTextParams params; + + params.m_bCentered = true; + params.m_bOutline = true; + params.m_flLetterWidth = 2; + params.m_vColor.Init( 1, 0, 0 ); + + VectorAngles( dplanes[f->planenum].normal, params.m_vAngles ); + params.m_bTwoSided = true; + + params.m_vPos = vCenter; + + pPad->DrawText( str, params ); + } +} + + +void ScratchPad_DrawWorld( IScratchPad3D *pPad, bool bDrawFaceNumbers, const CSPColor &faceColor ) +{ + bool bAutoFlush = pPad->GetAutoFlush(); + pPad->SetAutoFlush( false ); + + for ( int i=0; i < numleafs; i++ ) + { + dleaf_t *l = &dleafs[i]; + if ( l->contents & CONTENTS_DETAIL ) + continue; + + for ( int z=0; z < l->numleaffaces; z++ ) + { + int iFace = dleaffaces[l->firstleafface+z]; + dface_t *f = &dfaces[iFace]; + ScratchPad_DrawFace( pPad, f, bDrawFaceNumbers ? i : -1 ); + } + } + + pPad->SetAutoFlush( bAutoFlush ); +} + + +void ScratchPad_DrawWorld( bool bDrawFaceNumbers, const CSPColor &faceColor ) +{ + IScratchPad3D *pPad = ScratchPad3D_Create(); + ScratchPad_DrawWorld( pPad, bDrawFaceNumbers ); +} diff --git a/mp/src/utils/common/scratchpad_helpers.h b/mp/src/utils/common/scratchpad_helpers.h index 8f409fca..29e5a122 100644 --- a/mp/src/utils/common/scratchpad_helpers.h +++ b/mp/src/utils/common/scratchpad_helpers.h @@ -1,25 +1,25 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef SCRATCHPAD_HELPERS_H -#define SCRATCHPAD_HELPERS_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "iscratchpad3d.h" -#include "bspfile.h" - - -void ScratchPad_DrawWinding( IScratchPad3D *pPad, int nPoints, Vector *pPoints, Vector vColor, Vector vOffset = Vector(0,0,0) ); - -void ScratchPad_DrawFace( IScratchPad3D *pPad, dface_t *f, int iFaceNumber = -1, const CSPColor &faceColor=CSPColor(1,1,1,1), const Vector &vOffset=Vector(0,0,0) ); -void ScratchPad_DrawWorld( IScratchPad3D *pPad, bool bDrawFaceNumbers, const CSPColor &faceColor=CSPColor(1,1,1,1) ); -void ScratchPad_DrawWorld( bool bDrawFaceNumbers, const CSPColor &faceColor=CSPColor(1,1,1,1) ); - - -#endif // SCRATCHPAD_HELPERS_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef SCRATCHPAD_HELPERS_H +#define SCRATCHPAD_HELPERS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "iscratchpad3d.h" +#include "bspfile.h" + + +void ScratchPad_DrawWinding( IScratchPad3D *pPad, int nPoints, Vector *pPoints, Vector vColor, Vector vOffset = Vector(0,0,0) ); + +void ScratchPad_DrawFace( IScratchPad3D *pPad, dface_t *f, int iFaceNumber = -1, const CSPColor &faceColor=CSPColor(1,1,1,1), const Vector &vOffset=Vector(0,0,0) ); +void ScratchPad_DrawWorld( IScratchPad3D *pPad, bool bDrawFaceNumbers, const CSPColor &faceColor=CSPColor(1,1,1,1) ); +void ScratchPad_DrawWorld( bool bDrawFaceNumbers, const CSPColor &faceColor=CSPColor(1,1,1,1) ); + + +#endif // SCRATCHPAD_HELPERS_H diff --git a/mp/src/utils/common/scriplib.cpp b/mp/src/utils/common/scriplib.cpp index 469c7885..1c8b47f8 100644 --- a/mp/src/utils/common/scriplib.cpp +++ b/mp/src/utils/common/scriplib.cpp @@ -1,1349 +1,1349 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//===========================================================================// - -// scriplib.c - -#include "tier1/strtools.h" -#include "tier2/tier2.h" -#include "cmdlib.h" -#include "scriplib.h" -#if defined( _X360 ) -#include "xbox\xbox_win32stubs.h" -#endif -#if defined(POSIX) -#include "../../filesystem/linux_support.h" -#include -#endif -/* -============================================================================= - - PARSING STUFF - -============================================================================= -*/ - -typedef struct -{ - char filename[1024]; - char *buffer,*script_p,*end_p; - int line; - - char macrobuffer[4096]; - char *macroparam[64]; - char *macrovalue[64]; - int nummacroparams; - -} script_t; - -#define MAX_INCLUDES 64 -script_t scriptstack[MAX_INCLUDES]; -script_t *script = NULL; -int scriptline; - -char token[MAXTOKEN]; -qboolean endofscript; -qboolean tokenready; // only true if UnGetToken was just called - -typedef struct -{ - char *param; - char *value; -} variable_t; - -CUtlVector g_definevariable; - -/* -Callback stuff -*/ - -void DefaultScriptLoadedCallback( char const *pFilenameLoaded, char const *pIncludedFromFileName, int nIncludeLineNumber ) -{ - NULL; -} - -SCRIPT_LOADED_CALLBACK g_pfnCallback = DefaultScriptLoadedCallback; - -SCRIPT_LOADED_CALLBACK SetScriptLoadedCallback( SCRIPT_LOADED_CALLBACK pfnNewScriptLoadedCallback ) -{ - SCRIPT_LOADED_CALLBACK pfnCallback = g_pfnCallback; - g_pfnCallback = pfnNewScriptLoadedCallback; - return pfnCallback; -} - -/* -============== -AddScriptToStack -============== -*/ -void AddScriptToStack (char *filename, ScriptPathMode_t pathMode = SCRIPT_USE_ABSOLUTE_PATH) -{ - int size; - - script++; - if (script == &scriptstack[MAX_INCLUDES]) - Error ("script file exceeded MAX_INCLUDES"); - - if ( pathMode == SCRIPT_USE_RELATIVE_PATH ) - Q_strncpy( script->filename, filename, sizeof( script->filename ) ); - else - Q_strncpy (script->filename, ExpandPath (filename), sizeof( script->filename ) ); - - size = LoadFile (script->filename, (void **)&script->buffer); - - // printf ("entering %s\n", script->filename); - if ( g_pfnCallback ) - { - if ( script == scriptstack + 1 ) - g_pfnCallback( script->filename, NULL, 0 ); - else - g_pfnCallback( script->filename, script[-1].filename, script[-1].line ); - } - - script->line = 1; - - script->script_p = script->buffer; - script->end_p = script->buffer + size; -} - - -/* -============== -LoadScriptFile -============== -*/ -void LoadScriptFile (char *filename, ScriptPathMode_t pathMode) -{ - script = scriptstack; - AddScriptToStack (filename, pathMode); - - endofscript = false; - tokenready = false; -} - - -/* -============== -============== -*/ - -script_t *macrolist[256]; -int nummacros; - -void DefineMacro( char *macroname ) -{ - script_t *pmacro = (script_t *)malloc( sizeof( script_t ) ); - - strcpy( pmacro->filename, macroname ); - pmacro->line = script->line; - pmacro->nummacroparams = 0; - - char *mp = pmacro->macrobuffer; - char *cp = script->script_p; - - while (TokenAvailable( )) - { - GetToken( false ); - - if (token[0] == '\\' && token[1] == '\\') - { - break; - } - cp = script->script_p; - - pmacro->macroparam[pmacro->nummacroparams++] = mp; - - strcpy( mp, token ); - mp += strlen( token ) + 1; - - if (mp >= pmacro->macrobuffer + sizeof( pmacro->macrobuffer )) - Error("Macro buffer overflow\n"); - } - // roll back script_p to previous valid location - script->script_p = cp; - - // find end of macro def - while (*cp && *cp != '\n') - { - //Msg("%d ", *cp ); - if (*cp == '\\' && *(cp+1) == '\\') - { - // skip till end of line - while (*cp && *cp != '\n') - { - *cp = ' '; // replace with spaces - cp++; - } - - if (*cp) - { - cp++; - } - } - else - { - cp++; - } - } - - int size = (cp - script->script_p); - - pmacro->buffer = (char *)malloc( size + 1); - memcpy( pmacro->buffer, script->script_p, size ); - pmacro->buffer[size] = '\0'; - pmacro->end_p = &pmacro->buffer[size]; - - macrolist[nummacros++] = pmacro; - - script->script_p = cp; -} - - -void DefineVariable( char *variablename ) -{ - variable_t v; - - v.param = strdup( variablename ); - - GetToken( false ); - - v.value = strdup( token ); - - g_definevariable.AddToTail( v ); -} - - - -/* -============== -============== -*/ -bool AddMacroToStack( char *macroname ) -{ - // lookup macro - if (macroname[0] != '$') - return false; - - int i; - for (i = 0; i < nummacros; i++) - { - if (strcmpi( macrolist[i]->filename, ¯oname[1] ) == 0) - { - break; - } - } - if (i == nummacros) - return false; - - script_t *pmacro = macrolist[i]; - - // get tokens - script_t *pnext = script + 1; - - pnext++; - if (pnext == &scriptstack[MAX_INCLUDES]) - Error ("script file exceeded MAX_INCLUDES"); - - // get tokens - char *cp = pnext->macrobuffer; - - pnext->nummacroparams = pmacro->nummacroparams; - - for (i = 0; i < pnext->nummacroparams; i++) - { - GetToken(false); - - strcpy( cp, token ); - pnext->macroparam[i] = pmacro->macroparam[i]; - pnext->macrovalue[i] = cp; - - cp += strlen( token ) + 1; - - if (cp >= pnext->macrobuffer + sizeof( pnext->macrobuffer )) - Error("Macro buffer overflow\n"); - } - - script = pnext; - strcpy( script->filename, pmacro->filename ); - - int size = pmacro->end_p - pmacro->buffer; - script->buffer = (char *)malloc( size + 1 ); - memcpy( script->buffer, pmacro->buffer, size ); - pmacro->buffer[size] = '\0'; - script->script_p = script->buffer; - script->end_p = script->buffer + size; - script->line = pmacro->line; - - return true; -} - - - -bool ExpandMacroToken( char *&token_p ) -{ - if ( script->nummacroparams && *script->script_p == '$' ) - { - char *cp = script->script_p + 1; - - while ( *cp > 32 && *cp != '$' ) - { - cp++; - } - - // found a word with $'s on either end? - if (*cp != '$') - return false; - - // get token pointer - char *tp = script->script_p + 1; - int len = (cp - tp); - *(tp + len) = '\0'; - - // lookup macro parameter - int index = 0; - for (index = 0; index < script->nummacroparams; index++) - { - if (stricmp( script->macroparam[index], tp ) == 0) - break; - } - if (index >= script->nummacroparams) - { - Error("unknown macro token \"%s\" in %s\n", tp, script->filename ); - } - - // paste token into - len = strlen( script->macrovalue[index] ); - strcpy( token_p, script->macrovalue[index] ); - token_p += len; - - script->script_p = cp + 1; - - if (script->script_p >= script->end_p) - Error ("Macro expand overflow\n"); - - if (token_p >= &token[MAXTOKEN]) - Error ("Token too large on line %i\n",scriptline); - - return true; - } - return false; -} - - - -/* -============== -============== -*/ -// FIXME: this should create a new script context so the individual tokens in the variable can be parsed -bool ExpandVariableToken( char *&token_p ) -{ - if ( *script->script_p == '$' ) - { - char *cp = script->script_p + 1; - - while ( *cp > 32 && *cp != '$' ) - { - cp++; - } - - // found a word with $'s on either end? - if (*cp != '$') - return false; - - // get token pointer - char *tp = script->script_p + 1; - int len = (cp - tp); - *(tp + len) = '\0'; - - // lookup macro parameter - - int index; - for (index = 0; index < g_definevariable.Count(); index++) - { - if (Q_strnicmp( g_definevariable[index].param, tp, len ) == 0) - break; - } - - if (index >= g_definevariable.Count() ) - { - Error("unknown variable token \"%s\" in %s\n", tp, script->filename ); - } - - // paste token into - len = strlen( g_definevariable[index].value ); - strcpy( token_p, g_definevariable[index].value ); - token_p += len; - - script->script_p = cp + 1; - - if (script->script_p >= script->end_p) - Error ("Macro expand overflow\n"); - - if (token_p >= &token[MAXTOKEN]) - Error ("Token too large on line %i\n",scriptline); - - return true; - } - return false; -} - - - -/* -============== -ParseFromMemory -============== -*/ -void ParseFromMemory (char *buffer, int size) -{ - script = scriptstack; - script++; - if (script == &scriptstack[MAX_INCLUDES]) - Error ("script file exceeded MAX_INCLUDES"); - strcpy (script->filename, "memory buffer" ); - - script->buffer = buffer; - script->line = 1; - script->script_p = script->buffer; - script->end_p = script->buffer + size; - - endofscript = false; - tokenready = false; -} - - -//----------------------------------------------------------------------------- -// Used instead of ParseFromMemory to temporarily add a memory buffer -// to the script stack. ParseFromMemory just blows away the stack. -//----------------------------------------------------------------------------- -void PushMemoryScript( char *pszBuffer, const int nSize ) -{ - if ( script == NULL ) - { - script = scriptstack; - } - script++; - if ( script == &scriptstack[MAX_INCLUDES] ) - { - Error ( "script file exceeded MAX_INCLUDES" ); - } - strcpy (script->filename, "memory buffer" ); - - script->buffer = pszBuffer; - script->line = 1; - script->script_p = script->buffer; - script->end_p = script->buffer + nSize; - - endofscript = false; - tokenready = false; -} - - -//----------------------------------------------------------------------------- -// Used after calling PushMemoryScript to clean up the memory buffer -// added to the script stack. The normal end of script terminates -// all parsing at the end of a memory buffer even if there are more scripts -// remaining on the script stack -//----------------------------------------------------------------------------- -bool PopMemoryScript() -{ - if ( V_stricmp( script->filename, "memory buffer" ) ) - return false; - - if ( script == scriptstack ) - { - endofscript = true; - return false; - } - script--; - scriptline = script->line; - - endofscript = false; - - return true; -} - - -/* -============== -UnGetToken - -Signals that the current token was not used, and should be reported -for the next GetToken. Note that - -GetToken (true); -UnGetToken (); -GetToken (false); - -could cross a line boundary. -============== -*/ -void UnGetToken (void) -{ - tokenready = true; -} - - -qboolean EndOfScript (qboolean crossline) -{ - if (!crossline) - Error ("Line %i is incomplete\n",scriptline); - - if (!strcmp (script->filename, "memory buffer")) - { - endofscript = true; - return false; - } - - free (script->buffer); - script->buffer = NULL; - if (script == scriptstack+1) - { - endofscript = true; - return false; - } - script--; - scriptline = script->line; - // printf ("returning to %s\n", script->filename); - return GetToken (crossline); -} - - -//----------------------------------------------------------------------------- -// Purpose: Given an absolute path, do a find first find next on it and build -// a list of files. Physical file system only -//----------------------------------------------------------------------------- -static void FindFileAbsoluteList( CUtlVector< CUtlString > &outAbsolutePathNames, const char *pszFindName ) -{ - char szPath[MAX_PATH]; - V_strncpy( szPath, pszFindName, sizeof( szPath ) ); - V_StripFilename( szPath ); - - char szResult[MAX_PATH]; - FileFindHandle_t hFile = FILESYSTEM_INVALID_FIND_HANDLE; - - for ( const char *pszFoundFile = g_pFullFileSystem->FindFirst( pszFindName, &hFile ); pszFoundFile && hFile != FILESYSTEM_INVALID_FIND_HANDLE; pszFoundFile = g_pFullFileSystem->FindNext( hFile ) ) - { - V_ComposeFileName( szPath, pszFoundFile, szResult, sizeof( szResult ) ); - outAbsolutePathNames.AddToTail( szResult ); - } - - g_pFullFileSystem->FindClose( hFile ); -} - - -//----------------------------------------------------------------------------- -// Data for checking for single character tokens while parsing -//----------------------------------------------------------------------------- -bool g_bCheckSingleCharTokens = false; -CUtlString g_sSingleCharTokens; - - -//----------------------------------------------------------------------------- -// Sets whether the scriplib parser will do a special check for single -// character tokens. Returns previous state of whether single character -// tokens will be checked. -//----------------------------------------------------------------------------- -bool SetCheckSingleCharTokens( bool bCheck ) -{ - const bool bRetVal = g_bCheckSingleCharTokens; - - g_bCheckSingleCharTokens = bCheck; - - return bRetVal; -} - - -//----------------------------------------------------------------------------- -// Sets the list of single character tokens to check if SetCheckSingleCharTokens -// is turned on. -//----------------------------------------------------------------------------- -CUtlString SetSingleCharTokenList( const char *pszSingleCharTokenList ) -{ - const CUtlString sRetVal = g_sSingleCharTokens; - - if ( pszSingleCharTokenList ) - { - g_sSingleCharTokens = pszSingleCharTokenList; - } - - return sRetVal; -} - - -/* -============== -GetToken -============== -*/ -qboolean GetToken (qboolean crossline) -{ - char *token_p; - - if (tokenready) // is a token allready waiting? - { - tokenready = false; - return true; - } - - // printf("script_p %x (%x)\n", script->script_p, script->end_p ); fflush( stdout ); - - if (script->script_p >= script->end_p) - { - return EndOfScript (crossline); - } - - tokenready = false; - - // skip space, ctrl chars -skipspace: - while (*script->script_p <= 32) - { - if (script->script_p >= script->end_p) - { - return EndOfScript (crossline); - } - if (*(script->script_p++) == '\n') - { - if (!crossline) - { - Error ("Line %i is incomplete\n",scriptline); - } - scriptline = ++script->line; - } - } - - if (script->script_p >= script->end_p) - { - return EndOfScript (crossline); - } - - // strip single line comments - if (*script->script_p == ';' || *script->script_p == '#' || // semicolon and # is comment field - (*script->script_p == '/' && *((script->script_p)+1) == '/')) // also make // a comment field - { - if (!crossline) - Error ("Line %i is incomplete\n",scriptline); - while (*script->script_p++ != '\n') - { - if (script->script_p >= script->end_p) - { - return EndOfScript (crossline); - } - } - scriptline = ++script->line; - goto skipspace; - } - - // strip out matching /* */ comments - if (*script->script_p == '/' && *((script->script_p)+1) == '*') - { - script->script_p += 2; - while (*script->script_p != '*' || *((script->script_p)+1) != '/') - { - if (*script->script_p++ != '\n') - { - if (script->script_p >= script->end_p) - { - return EndOfScript (crossline); - } - - scriptline = ++script->line; - } - } - script->script_p += 2; - goto skipspace; - } - - // copy token to buffer - token_p = token; - - if (*script->script_p == '"') - { - // quoted token - script->script_p++; - while (*script->script_p != '"') - { - *token_p++ = *script->script_p++; - if (script->script_p == script->end_p) - break; - if (token_p == &token[MAXTOKEN]) - Error ("Token too large on line %i\n",scriptline); - } - script->script_p++; - } - else if ( g_bCheckSingleCharTokens && !g_sSingleCharTokens.IsEmpty() && strchr( g_sSingleCharTokens.String(), *script->script_p ) != NULL ) - { - *token_p++ = *script->script_p++; - } - else // regular token - while ( *script->script_p > 32 && *script->script_p != ';') - { - if ( !ExpandMacroToken( token_p ) ) - { - if ( !ExpandVariableToken( token_p ) ) - { - *token_p++ = *script->script_p++; - if (script->script_p == script->end_p) - break; - if (token_p == &token[MAXTOKEN]) - Error ("Token too large on line %i\n",scriptline); - - } - } - } - - // add null to end of token - *token_p = 0; - - // check for other commands - if ( !stricmp( token, "$include" ) ) - { - GetToken( false ); - - bool bFallbackToToken = true; - - CUtlVector< CUtlString > expandedPathList; - - if ( CmdLib_ExpandWithBasePaths( expandedPathList, token ) > 0 ) - { - for ( int i = 0; i < expandedPathList.Count(); ++i ) - { - CUtlVector< CUtlString > findFileList; - FindFileAbsoluteList( findFileList, expandedPathList[i].String() ); - - if ( findFileList.Count() > 0 ) - { - bFallbackToToken = false; - - // Only add the first set of glob matches from the first base path - for ( int j = 0; j < findFileList.Count(); ++j ) - { - AddScriptToStack( const_cast< char * >( findFileList[j].String() ) ); - } - - break; - } - } - } - - if ( bFallbackToToken ) - { - AddScriptToStack( token ); - } - - return GetToken( crossline ); - } - else if (!stricmp (token, "$definemacro")) - { - GetToken (false); - DefineMacro(token); - return GetToken (crossline); - } - else if (!stricmp (token, "$definevariable")) - { - GetToken (false); - DefineVariable(token); - return GetToken (crossline); - } - else if (AddMacroToStack( token )) - { - return GetToken (crossline); - } - - return true; -} - - -/* -============== -GetExprToken - use C mathematical operator parsing rules to split tokens instead of whitespace -============== -*/ -qboolean GetExprToken (qboolean crossline) -{ - char *token_p; - - if (tokenready) // is a token allready waiting? - { - tokenready = false; - return true; - } - - if (script->script_p >= script->end_p) - return EndOfScript (crossline); - - tokenready = false; - -// -// skip space -// -skipspace: - while (*script->script_p <= 32) - { - if (script->script_p >= script->end_p) - return EndOfScript (crossline); - if (*script->script_p++ == '\n') - { - if (!crossline) - Error ("Line %i is incomplete\n",scriptline); - scriptline = ++script->line; - } - } - - if (script->script_p >= script->end_p) - return EndOfScript (crossline); - - if (*script->script_p == ';' || *script->script_p == '#' || // semicolon and # is comment field - (*script->script_p == '/' && *((script->script_p)+1) == '/')) // also make // a comment field - { - if (!crossline) - Error ("Line %i is incomplete\n",scriptline); - while (*script->script_p++ != '\n') - if (script->script_p >= script->end_p) - return EndOfScript (crossline); - goto skipspace; - } - -// -// copy token -// - token_p = token; - - if (*script->script_p == '"') - { - // quoted token - script->script_p++; - while (*script->script_p != '"') - { - *token_p++ = *script->script_p++; - if (script->script_p == script->end_p) - break; - if (token_p == &token[MAXTOKEN]) - Error ("Token too large on line %i\n",scriptline); - } - script->script_p++; - } - else - { - if ( V_isalpha( *script->script_p ) || *script->script_p == '_' ) - { - // regular token - while ( V_isalnum( *script->script_p ) || *script->script_p == '_' ) - { - *token_p++ = *script->script_p++; - if (script->script_p == script->end_p) - break; - if (token_p == &token[MAXTOKEN]) - Error ("Token too large on line %i\n",scriptline); - } - } - else if ( V_isdigit( *script->script_p ) || *script->script_p == '.' ) - { - // regular token - while ( V_isdigit( *script->script_p ) || *script->script_p == '.' ) - { - *token_p++ = *script->script_p++; - if (script->script_p == script->end_p) - break; - if (token_p == &token[MAXTOKEN]) - Error ("Token too large on line %i\n",scriptline); - } - } - else - { - // single char - *token_p++ = *script->script_p++; - } - } - - *token_p = 0; - - if (!stricmp (token, "$include")) - { - GetToken (false); - AddScriptToStack (token); - return GetToken (crossline); - } - - return true; -} - - -/* -============== -TokenAvailable - -Returns true if there is another token on the line -============== -*/ -qboolean TokenAvailable (void) -{ - char *search_p; - - if (tokenready) // is a token allready waiting? - { - return true; - } - - search_p = script->script_p; - - if (search_p >= script->end_p) - return false; - - while ( *search_p <= 32) - { - if (*search_p == '\n') - return false; - search_p++; - if (search_p == script->end_p) - return false; - - } - - if (*search_p == ';' || *search_p == '#' || // semicolon and # is comment field - (*search_p == '/' && *((search_p)+1) == '/')) // also make // a comment field - return false; - - return true; -} - -qboolean GetTokenizerStatus( char **pFilename, int *pLine ) -{ - // is this the default state? - if (!script) - return false; - - if (script->script_p >= script->end_p) - return false; - - if (pFilename) - { - *pFilename = script->filename; - } - if (pLine) - { - *pLine = script->line; - } - return true; -} - - -#include -#include -#ifdef WIN32 -#include -#include -#include -#endif -#include -#include -#include -#include -#include "tier1/utlbuffer.h" - -class CScriptLib : public IScriptLib -{ -public: - virtual bool ReadFileToBuffer( const char *pSourceName, CUtlBuffer &buffer, bool bText = false, bool bNoOpenFailureWarning = false ); - virtual bool WriteBufferToFile( const char *pTargetName, CUtlBuffer &buffer, DiskWriteMode_t writeMode ); - virtual int FindFiles( char* pFileMask, bool bRecurse, CUtlVector &fileList ); - virtual char *MakeTemporaryFilename( char const *pchModPath, char *pPath, int pathSize ); - virtual void DeleteTemporaryFiles( const char *pFileMask ); - virtual int CompareFileTime( const char *pFilenameA, const char *pFilenameB ); - virtual bool DoesFileExist( const char *pFilename ); - -private: - - int GetFileList( const char* pDirPath, const char* pPattern, CUtlVector< fileList_t > &fileList ); - void RecurseFileTree_r( const char* pDirPath, int depth, CUtlVector< CUtlString > &dirList ); -}; - -static CScriptLib g_ScriptLib; -IScriptLib *scriptlib = &g_ScriptLib; -IScriptLib *g_pScriptLib = &g_ScriptLib; - -//----------------------------------------------------------------------------- -// Existence check -//----------------------------------------------------------------------------- -bool CScriptLib::DoesFileExist( const char *pFilename ) -{ - return g_pFullFileSystem->FileExists( pFilename ); -} - -//----------------------------------------------------------------------------- -// Purpose: Helper utility, read file into buffer -//----------------------------------------------------------------------------- -bool CScriptLib::ReadFileToBuffer( const char *pSourceName, CUtlBuffer &buffer, bool bText, bool bNoOpenFailureWarning ) -{ - bool bSuccess = true; - - if ( !g_pFullFileSystem->ReadFile( pSourceName, NULL, buffer ) ) - { - if ( !bNoOpenFailureWarning ) - { - Msg( "ReadFileToBuffer(): Error opening %s: %s\n", pSourceName, strerror( errno ) ); - } - return false; - } - - if ( bText ) - { - // force it into text mode - buffer.SetBufferType( true, true ); - } - else - { - buffer.SetBufferType( false, false ); - } - - return bSuccess; -} - -//----------------------------------------------------------------------------- -// Purpose: Helper utility, Write buffer to file -//----------------------------------------------------------------------------- -bool CScriptLib::WriteBufferToFile( const char *pTargetName, CUtlBuffer &buffer, DiskWriteMode_t writeMode ) -{ - char* ptr; - char dirPath[MAX_PATH]; - - bool bSuccess = true; - - // create path - // prime and skip to first seperator - strcpy( dirPath, pTargetName ); - ptr = strchr( dirPath, '\\' ); - while ( ptr ) - { - ptr = strchr( ptr+1, '\\' ); - if ( ptr ) - { - *ptr = '\0'; - _mkdir( dirPath ); - *ptr = '\\'; - } - } - - bool bDoWrite = false; - if ( writeMode == WRITE_TO_DISK_ALWAYS ) - { - bDoWrite = true; - } - else if ( writeMode == WRITE_TO_DISK_UPDATE ) - { - if ( DoesFileExist( pTargetName ) ) - { - bDoWrite = true; - } - } - - if ( bDoWrite ) - { - bSuccess = g_pFullFileSystem->WriteFile( pTargetName, NULL, buffer ); - } - - return bSuccess; -} - -//----------------------------------------------------------------------------- -// Returns -1, 0, or 1. -//----------------------------------------------------------------------------- -int CScriptLib::CompareFileTime( const char *pFilenameA, const char *pFilenameB ) -{ - int timeA = g_pFullFileSystem->GetFileTime( (char *)pFilenameA ); - int timeB = g_pFullFileSystem->GetFileTime( (char *)pFilenameB ); - - if ( timeA == -1) - { - // file a not exist - timeA = 0; - } - if ( timeB == -1 ) - { - // file b not exist - timeB = 0; - } - - if ( (unsigned int)timeA < (unsigned int)timeB ) - { - return -1; - } - else if ( (unsigned int)timeA > (unsigned int)timeB ) - { - return 1; - } - - return 0; -} - -//----------------------------------------------------------------------------- -// Make a temporary filename -//----------------------------------------------------------------------------- -char *CScriptLib::MakeTemporaryFilename( char const *pchModPath, char *pPath, int pathSize ) -{ - char *pBuffer = _tempnam( pchModPath, "mgd_" ); - if ( pBuffer[0] == '\\' ) - { - pBuffer++; - } - if ( pBuffer[strlen( pBuffer )-1] == '.' ) - { - pBuffer[strlen( pBuffer )-1] = '\0'; - } - V_snprintf( pPath, pathSize, "%s.tmp", pBuffer ); - - free( pBuffer ); - - return pPath; -} - -//----------------------------------------------------------------------------- -// Delete temporary files -//----------------------------------------------------------------------------- -void CScriptLib::DeleteTemporaryFiles( const char *pFileMask ) -{ -#if !defined( _X360 ) - const char *pEnv = getenv( "temp" ); - if ( !pEnv ) - { - pEnv = getenv( "tmp" ); - } - - if ( pEnv ) - { - char tempPath[MAX_PATH]; - strcpy( tempPath, pEnv ); - V_AppendSlash( tempPath, sizeof( tempPath ) ); - strcat( tempPath, pFileMask ); - - CUtlVector fileList; - FindFiles( tempPath, false, fileList ); - for ( int i=0; i &fileList ) -{ - char sourcePath[MAX_PATH]; - char fullPath[MAX_PATH]; - bool bFindDirs; - - fileList.Purge(); - - strcpy( sourcePath, pDirPath ); - int len = (int)strlen( sourcePath ); - if ( !len ) - { - strcpy( sourcePath, ".\\" ); - } - else if ( sourcePath[len-1] != '\\' ) - { - sourcePath[len] = '\\'; - sourcePath[len+1] = '\0'; - } - - strcpy( fullPath, sourcePath ); - if ( pPattern[0] == '\\' && pPattern[1] == '\0' ) - { - // find directories only - bFindDirs = true; - strcat( fullPath, "*" ); - } - else - { - // find files, use provided pattern - bFindDirs = false; - strcat( fullPath, pPattern ); - } - -#ifdef WIN32 - struct _finddata_t findData; - intptr_t h = _findfirst( fullPath, &findData ); - if ( h == -1 ) - { - return 0; - } - - do - { - // dos attribute complexities i.e. _A_NORMAL is 0 - if ( bFindDirs ) - { - // skip non dirs - if ( !( findData.attrib & _A_SUBDIR ) ) - continue; - } - else - { - // skip dirs - if ( findData.attrib & _A_SUBDIR ) - continue; - } - - if ( !stricmp( findData.name, "." ) ) - continue; - - if ( !stricmp( findData.name, ".." ) ) - continue; - - char fileName[MAX_PATH]; - strcpy( fileName, sourcePath ); - strcat( fileName, findData.name ); - - int j = fileList.AddToTail(); - fileList[j].fileName.Set( fileName ); - fileList[j].timeWrite = findData.time_write; - } - while ( !_findnext( h, &findData ) ); - - _findclose( h ); -#elif defined(POSIX) - FIND_DATA findData; - Q_FixSlashes( fullPath ); - void *h = FindFirstFile( fullPath, &findData ); - if ( (int)h == -1 ) - { - return 0; - } - - do - { - // dos attribute complexities i.e. _A_NORMAL is 0 - if ( bFindDirs ) - { - // skip non dirs - if ( !( findData.dwFileAttributes & S_IFDIR ) ) - continue; - } - else - { - // skip dirs - if ( findData.dwFileAttributes & S_IFDIR ) - continue; - } - - if ( !stricmp( findData.cFileName, "." ) ) - continue; - - if ( !stricmp( findData.cFileName, ".." ) ) - continue; - - char fileName[MAX_PATH]; - strcpy( fileName, sourcePath ); - strcat( fileName, findData.cFileName ); - - int j = fileList.AddToTail(); - fileList[j].fileName.Set( fileName ); - struct stat statbuf; - if ( stat( fileName, &statbuf ) ) -#ifdef OSX - fileList[j].timeWrite = statbuf.st_mtimespec.tv_sec; -#else - fileList[j].timeWrite = statbuf.st_mtime; -#endif - else - fileList[j].timeWrite = 0; - } - while ( !FindNextFile( h, &findData ) ); - - FindClose( h ); - -#else -#error -#endif - - - return fileList.Count(); -} - -//----------------------------------------------------------------------------- -// Purpose: Recursively determine directory tree -//----------------------------------------------------------------------------- -void CScriptLib::RecurseFileTree_r( const char* pDirPath, int depth, CUtlVector< CUtlString > &dirList ) -{ - // recurse from source directory, get directories only - CUtlVector< fileList_t > fileList; - int dirCount = GetFileList( pDirPath, "\\", fileList ); - if ( !dirCount ) - { - // add directory name to search tree - int j = dirList.AddToTail(); - dirList[j].Set( pDirPath ); - return; - } - - for ( int i=0; i &fileList ) -{ - char dirPath[MAX_PATH]; - char pattern[MAX_PATH]; - char extension[MAX_PATH]; - - // get path only - strcpy( dirPath, pFileMask ); - V_StripFilename( dirPath ); - - // get pattern only - V_FileBase( pFileMask, pattern, sizeof( pattern ) ); - V_ExtractFileExtension( pFileMask, extension, sizeof( extension ) ); - if ( extension[0] ) - { - strcat( pattern, "." ); - strcat( pattern, extension ); - } - - if ( !bRecurse ) - { - GetFileList( dirPath, pattern, fileList ); - } - else - { - // recurse and get the tree - CUtlVector< fileList_t > tempList; - CUtlVector< CUtlString > dirList; - RecurseFileTree_r( dirPath, 0, dirList ); - for ( int i=0; i +#endif +/* +============================================================================= + + PARSING STUFF + +============================================================================= +*/ + +typedef struct +{ + char filename[1024]; + char *buffer,*script_p,*end_p; + int line; + + char macrobuffer[4096]; + char *macroparam[64]; + char *macrovalue[64]; + int nummacroparams; + +} script_t; + +#define MAX_INCLUDES 64 +script_t scriptstack[MAX_INCLUDES]; +script_t *script = NULL; +int scriptline; + +char token[MAXTOKEN]; +qboolean endofscript; +qboolean tokenready; // only true if UnGetToken was just called + +typedef struct +{ + char *param; + char *value; +} variable_t; + +CUtlVector g_definevariable; + +/* +Callback stuff +*/ + +void DefaultScriptLoadedCallback( char const *pFilenameLoaded, char const *pIncludedFromFileName, int nIncludeLineNumber ) +{ + NULL; +} + +SCRIPT_LOADED_CALLBACK g_pfnCallback = DefaultScriptLoadedCallback; + +SCRIPT_LOADED_CALLBACK SetScriptLoadedCallback( SCRIPT_LOADED_CALLBACK pfnNewScriptLoadedCallback ) +{ + SCRIPT_LOADED_CALLBACK pfnCallback = g_pfnCallback; + g_pfnCallback = pfnNewScriptLoadedCallback; + return pfnCallback; +} + +/* +============== +AddScriptToStack +============== +*/ +void AddScriptToStack (char *filename, ScriptPathMode_t pathMode = SCRIPT_USE_ABSOLUTE_PATH) +{ + int size; + + script++; + if (script == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + + if ( pathMode == SCRIPT_USE_RELATIVE_PATH ) + Q_strncpy( script->filename, filename, sizeof( script->filename ) ); + else + Q_strncpy (script->filename, ExpandPath (filename), sizeof( script->filename ) ); + + size = LoadFile (script->filename, (void **)&script->buffer); + + // printf ("entering %s\n", script->filename); + if ( g_pfnCallback ) + { + if ( script == scriptstack + 1 ) + g_pfnCallback( script->filename, NULL, 0 ); + else + g_pfnCallback( script->filename, script[-1].filename, script[-1].line ); + } + + script->line = 1; + + script->script_p = script->buffer; + script->end_p = script->buffer + size; +} + + +/* +============== +LoadScriptFile +============== +*/ +void LoadScriptFile (char *filename, ScriptPathMode_t pathMode) +{ + script = scriptstack; + AddScriptToStack (filename, pathMode); + + endofscript = false; + tokenready = false; +} + + +/* +============== +============== +*/ + +script_t *macrolist[256]; +int nummacros; + +void DefineMacro( char *macroname ) +{ + script_t *pmacro = (script_t *)malloc( sizeof( script_t ) ); + + strcpy( pmacro->filename, macroname ); + pmacro->line = script->line; + pmacro->nummacroparams = 0; + + char *mp = pmacro->macrobuffer; + char *cp = script->script_p; + + while (TokenAvailable( )) + { + GetToken( false ); + + if (token[0] == '\\' && token[1] == '\\') + { + break; + } + cp = script->script_p; + + pmacro->macroparam[pmacro->nummacroparams++] = mp; + + strcpy( mp, token ); + mp += strlen( token ) + 1; + + if (mp >= pmacro->macrobuffer + sizeof( pmacro->macrobuffer )) + Error("Macro buffer overflow\n"); + } + // roll back script_p to previous valid location + script->script_p = cp; + + // find end of macro def + while (*cp && *cp != '\n') + { + //Msg("%d ", *cp ); + if (*cp == '\\' && *(cp+1) == '\\') + { + // skip till end of line + while (*cp && *cp != '\n') + { + *cp = ' '; // replace with spaces + cp++; + } + + if (*cp) + { + cp++; + } + } + else + { + cp++; + } + } + + int size = (cp - script->script_p); + + pmacro->buffer = (char *)malloc( size + 1); + memcpy( pmacro->buffer, script->script_p, size ); + pmacro->buffer[size] = '\0'; + pmacro->end_p = &pmacro->buffer[size]; + + macrolist[nummacros++] = pmacro; + + script->script_p = cp; +} + + +void DefineVariable( char *variablename ) +{ + variable_t v; + + v.param = strdup( variablename ); + + GetToken( false ); + + v.value = strdup( token ); + + g_definevariable.AddToTail( v ); +} + + + +/* +============== +============== +*/ +bool AddMacroToStack( char *macroname ) +{ + // lookup macro + if (macroname[0] != '$') + return false; + + int i; + for (i = 0; i < nummacros; i++) + { + if (strcmpi( macrolist[i]->filename, ¯oname[1] ) == 0) + { + break; + } + } + if (i == nummacros) + return false; + + script_t *pmacro = macrolist[i]; + + // get tokens + script_t *pnext = script + 1; + + pnext++; + if (pnext == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + + // get tokens + char *cp = pnext->macrobuffer; + + pnext->nummacroparams = pmacro->nummacroparams; + + for (i = 0; i < pnext->nummacroparams; i++) + { + GetToken(false); + + strcpy( cp, token ); + pnext->macroparam[i] = pmacro->macroparam[i]; + pnext->macrovalue[i] = cp; + + cp += strlen( token ) + 1; + + if (cp >= pnext->macrobuffer + sizeof( pnext->macrobuffer )) + Error("Macro buffer overflow\n"); + } + + script = pnext; + strcpy( script->filename, pmacro->filename ); + + int size = pmacro->end_p - pmacro->buffer; + script->buffer = (char *)malloc( size + 1 ); + memcpy( script->buffer, pmacro->buffer, size ); + pmacro->buffer[size] = '\0'; + script->script_p = script->buffer; + script->end_p = script->buffer + size; + script->line = pmacro->line; + + return true; +} + + + +bool ExpandMacroToken( char *&token_p ) +{ + if ( script->nummacroparams && *script->script_p == '$' ) + { + char *cp = script->script_p + 1; + + while ( *cp > 32 && *cp != '$' ) + { + cp++; + } + + // found a word with $'s on either end? + if (*cp != '$') + return false; + + // get token pointer + char *tp = script->script_p + 1; + int len = (cp - tp); + *(tp + len) = '\0'; + + // lookup macro parameter + int index = 0; + for (index = 0; index < script->nummacroparams; index++) + { + if (stricmp( script->macroparam[index], tp ) == 0) + break; + } + if (index >= script->nummacroparams) + { + Error("unknown macro token \"%s\" in %s\n", tp, script->filename ); + } + + // paste token into + len = strlen( script->macrovalue[index] ); + strcpy( token_p, script->macrovalue[index] ); + token_p += len; + + script->script_p = cp + 1; + + if (script->script_p >= script->end_p) + Error ("Macro expand overflow\n"); + + if (token_p >= &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + + return true; + } + return false; +} + + + +/* +============== +============== +*/ +// FIXME: this should create a new script context so the individual tokens in the variable can be parsed +bool ExpandVariableToken( char *&token_p ) +{ + if ( *script->script_p == '$' ) + { + char *cp = script->script_p + 1; + + while ( *cp > 32 && *cp != '$' ) + { + cp++; + } + + // found a word with $'s on either end? + if (*cp != '$') + return false; + + // get token pointer + char *tp = script->script_p + 1; + int len = (cp - tp); + *(tp + len) = '\0'; + + // lookup macro parameter + + int index; + for (index = 0; index < g_definevariable.Count(); index++) + { + if (Q_strnicmp( g_definevariable[index].param, tp, len ) == 0) + break; + } + + if (index >= g_definevariable.Count() ) + { + Error("unknown variable token \"%s\" in %s\n", tp, script->filename ); + } + + // paste token into + len = strlen( g_definevariable[index].value ); + strcpy( token_p, g_definevariable[index].value ); + token_p += len; + + script->script_p = cp + 1; + + if (script->script_p >= script->end_p) + Error ("Macro expand overflow\n"); + + if (token_p >= &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + + return true; + } + return false; +} + + + +/* +============== +ParseFromMemory +============== +*/ +void ParseFromMemory (char *buffer, int size) +{ + script = scriptstack; + script++; + if (script == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + strcpy (script->filename, "memory buffer" ); + + script->buffer = buffer; + script->line = 1; + script->script_p = script->buffer; + script->end_p = script->buffer + size; + + endofscript = false; + tokenready = false; +} + + +//----------------------------------------------------------------------------- +// Used instead of ParseFromMemory to temporarily add a memory buffer +// to the script stack. ParseFromMemory just blows away the stack. +//----------------------------------------------------------------------------- +void PushMemoryScript( char *pszBuffer, const int nSize ) +{ + if ( script == NULL ) + { + script = scriptstack; + } + script++; + if ( script == &scriptstack[MAX_INCLUDES] ) + { + Error ( "script file exceeded MAX_INCLUDES" ); + } + strcpy (script->filename, "memory buffer" ); + + script->buffer = pszBuffer; + script->line = 1; + script->script_p = script->buffer; + script->end_p = script->buffer + nSize; + + endofscript = false; + tokenready = false; +} + + +//----------------------------------------------------------------------------- +// Used after calling PushMemoryScript to clean up the memory buffer +// added to the script stack. The normal end of script terminates +// all parsing at the end of a memory buffer even if there are more scripts +// remaining on the script stack +//----------------------------------------------------------------------------- +bool PopMemoryScript() +{ + if ( V_stricmp( script->filename, "memory buffer" ) ) + return false; + + if ( script == scriptstack ) + { + endofscript = true; + return false; + } + script--; + scriptline = script->line; + + endofscript = false; + + return true; +} + + +/* +============== +UnGetToken + +Signals that the current token was not used, and should be reported +for the next GetToken. Note that + +GetToken (true); +UnGetToken (); +GetToken (false); + +could cross a line boundary. +============== +*/ +void UnGetToken (void) +{ + tokenready = true; +} + + +qboolean EndOfScript (qboolean crossline) +{ + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + + if (!strcmp (script->filename, "memory buffer")) + { + endofscript = true; + return false; + } + + free (script->buffer); + script->buffer = NULL; + if (script == scriptstack+1) + { + endofscript = true; + return false; + } + script--; + scriptline = script->line; + // printf ("returning to %s\n", script->filename); + return GetToken (crossline); +} + + +//----------------------------------------------------------------------------- +// Purpose: Given an absolute path, do a find first find next on it and build +// a list of files. Physical file system only +//----------------------------------------------------------------------------- +static void FindFileAbsoluteList( CUtlVector< CUtlString > &outAbsolutePathNames, const char *pszFindName ) +{ + char szPath[MAX_PATH]; + V_strncpy( szPath, pszFindName, sizeof( szPath ) ); + V_StripFilename( szPath ); + + char szResult[MAX_PATH]; + FileFindHandle_t hFile = FILESYSTEM_INVALID_FIND_HANDLE; + + for ( const char *pszFoundFile = g_pFullFileSystem->FindFirst( pszFindName, &hFile ); pszFoundFile && hFile != FILESYSTEM_INVALID_FIND_HANDLE; pszFoundFile = g_pFullFileSystem->FindNext( hFile ) ) + { + V_ComposeFileName( szPath, pszFoundFile, szResult, sizeof( szResult ) ); + outAbsolutePathNames.AddToTail( szResult ); + } + + g_pFullFileSystem->FindClose( hFile ); +} + + +//----------------------------------------------------------------------------- +// Data for checking for single character tokens while parsing +//----------------------------------------------------------------------------- +bool g_bCheckSingleCharTokens = false; +CUtlString g_sSingleCharTokens; + + +//----------------------------------------------------------------------------- +// Sets whether the scriplib parser will do a special check for single +// character tokens. Returns previous state of whether single character +// tokens will be checked. +//----------------------------------------------------------------------------- +bool SetCheckSingleCharTokens( bool bCheck ) +{ + const bool bRetVal = g_bCheckSingleCharTokens; + + g_bCheckSingleCharTokens = bCheck; + + return bRetVal; +} + + +//----------------------------------------------------------------------------- +// Sets the list of single character tokens to check if SetCheckSingleCharTokens +// is turned on. +//----------------------------------------------------------------------------- +CUtlString SetSingleCharTokenList( const char *pszSingleCharTokenList ) +{ + const CUtlString sRetVal = g_sSingleCharTokens; + + if ( pszSingleCharTokenList ) + { + g_sSingleCharTokens = pszSingleCharTokenList; + } + + return sRetVal; +} + + +/* +============== +GetToken +============== +*/ +qboolean GetToken (qboolean crossline) +{ + char *token_p; + + if (tokenready) // is a token allready waiting? + { + tokenready = false; + return true; + } + + // printf("script_p %x (%x)\n", script->script_p, script->end_p ); fflush( stdout ); + + if (script->script_p >= script->end_p) + { + return EndOfScript (crossline); + } + + tokenready = false; + + // skip space, ctrl chars +skipspace: + while (*script->script_p <= 32) + { + if (script->script_p >= script->end_p) + { + return EndOfScript (crossline); + } + if (*(script->script_p++) == '\n') + { + if (!crossline) + { + Error ("Line %i is incomplete\n",scriptline); + } + scriptline = ++script->line; + } + } + + if (script->script_p >= script->end_p) + { + return EndOfScript (crossline); + } + + // strip single line comments + if (*script->script_p == ';' || *script->script_p == '#' || // semicolon and # is comment field + (*script->script_p == '/' && *((script->script_p)+1) == '/')) // also make // a comment field + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + while (*script->script_p++ != '\n') + { + if (script->script_p >= script->end_p) + { + return EndOfScript (crossline); + } + } + scriptline = ++script->line; + goto skipspace; + } + + // strip out matching /* */ comments + if (*script->script_p == '/' && *((script->script_p)+1) == '*') + { + script->script_p += 2; + while (*script->script_p != '*' || *((script->script_p)+1) != '/') + { + if (*script->script_p++ != '\n') + { + if (script->script_p >= script->end_p) + { + return EndOfScript (crossline); + } + + scriptline = ++script->line; + } + } + script->script_p += 2; + goto skipspace; + } + + // copy token to buffer + token_p = token; + + if (*script->script_p == '"') + { + // quoted token + script->script_p++; + while (*script->script_p != '"') + { + *token_p++ = *script->script_p++; + if (script->script_p == script->end_p) + break; + if (token_p == &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + } + script->script_p++; + } + else if ( g_bCheckSingleCharTokens && !g_sSingleCharTokens.IsEmpty() && strchr( g_sSingleCharTokens.String(), *script->script_p ) != NULL ) + { + *token_p++ = *script->script_p++; + } + else // regular token + while ( *script->script_p > 32 && *script->script_p != ';') + { + if ( !ExpandMacroToken( token_p ) ) + { + if ( !ExpandVariableToken( token_p ) ) + { + *token_p++ = *script->script_p++; + if (script->script_p == script->end_p) + break; + if (token_p == &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + + } + } + } + + // add null to end of token + *token_p = 0; + + // check for other commands + if ( !stricmp( token, "$include" ) ) + { + GetToken( false ); + + bool bFallbackToToken = true; + + CUtlVector< CUtlString > expandedPathList; + + if ( CmdLib_ExpandWithBasePaths( expandedPathList, token ) > 0 ) + { + for ( int i = 0; i < expandedPathList.Count(); ++i ) + { + CUtlVector< CUtlString > findFileList; + FindFileAbsoluteList( findFileList, expandedPathList[i].String() ); + + if ( findFileList.Count() > 0 ) + { + bFallbackToToken = false; + + // Only add the first set of glob matches from the first base path + for ( int j = 0; j < findFileList.Count(); ++j ) + { + AddScriptToStack( const_cast< char * >( findFileList[j].String() ) ); + } + + break; + } + } + } + + if ( bFallbackToToken ) + { + AddScriptToStack( token ); + } + + return GetToken( crossline ); + } + else if (!stricmp (token, "$definemacro")) + { + GetToken (false); + DefineMacro(token); + return GetToken (crossline); + } + else if (!stricmp (token, "$definevariable")) + { + GetToken (false); + DefineVariable(token); + return GetToken (crossline); + } + else if (AddMacroToStack( token )) + { + return GetToken (crossline); + } + + return true; +} + + +/* +============== +GetExprToken - use C mathematical operator parsing rules to split tokens instead of whitespace +============== +*/ +qboolean GetExprToken (qboolean crossline) +{ + char *token_p; + + if (tokenready) // is a token allready waiting? + { + tokenready = false; + return true; + } + + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + + tokenready = false; + +// +// skip space +// +skipspace: + while (*script->script_p <= 32) + { + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + if (*script->script_p++ == '\n') + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + scriptline = ++script->line; + } + } + + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + + if (*script->script_p == ';' || *script->script_p == '#' || // semicolon and # is comment field + (*script->script_p == '/' && *((script->script_p)+1) == '/')) // also make // a comment field + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + while (*script->script_p++ != '\n') + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + goto skipspace; + } + +// +// copy token +// + token_p = token; + + if (*script->script_p == '"') + { + // quoted token + script->script_p++; + while (*script->script_p != '"') + { + *token_p++ = *script->script_p++; + if (script->script_p == script->end_p) + break; + if (token_p == &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + } + script->script_p++; + } + else + { + if ( V_isalpha( *script->script_p ) || *script->script_p == '_' ) + { + // regular token + while ( V_isalnum( *script->script_p ) || *script->script_p == '_' ) + { + *token_p++ = *script->script_p++; + if (script->script_p == script->end_p) + break; + if (token_p == &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + } + } + else if ( V_isdigit( *script->script_p ) || *script->script_p == '.' ) + { + // regular token + while ( V_isdigit( *script->script_p ) || *script->script_p == '.' ) + { + *token_p++ = *script->script_p++; + if (script->script_p == script->end_p) + break; + if (token_p == &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + } + } + else + { + // single char + *token_p++ = *script->script_p++; + } + } + + *token_p = 0; + + if (!stricmp (token, "$include")) + { + GetToken (false); + AddScriptToStack (token); + return GetToken (crossline); + } + + return true; +} + + +/* +============== +TokenAvailable + +Returns true if there is another token on the line +============== +*/ +qboolean TokenAvailable (void) +{ + char *search_p; + + if (tokenready) // is a token allready waiting? + { + return true; + } + + search_p = script->script_p; + + if (search_p >= script->end_p) + return false; + + while ( *search_p <= 32) + { + if (*search_p == '\n') + return false; + search_p++; + if (search_p == script->end_p) + return false; + + } + + if (*search_p == ';' || *search_p == '#' || // semicolon and # is comment field + (*search_p == '/' && *((search_p)+1) == '/')) // also make // a comment field + return false; + + return true; +} + +qboolean GetTokenizerStatus( char **pFilename, int *pLine ) +{ + // is this the default state? + if (!script) + return false; + + if (script->script_p >= script->end_p) + return false; + + if (pFilename) + { + *pFilename = script->filename; + } + if (pLine) + { + *pLine = script->line; + } + return true; +} + + +#include +#include +#ifdef WIN32 +#include +#include +#include +#endif +#include +#include +#include +#include +#include "tier1/utlbuffer.h" + +class CScriptLib : public IScriptLib +{ +public: + virtual bool ReadFileToBuffer( const char *pSourceName, CUtlBuffer &buffer, bool bText = false, bool bNoOpenFailureWarning = false ); + virtual bool WriteBufferToFile( const char *pTargetName, CUtlBuffer &buffer, DiskWriteMode_t writeMode ); + virtual int FindFiles( char* pFileMask, bool bRecurse, CUtlVector &fileList ); + virtual char *MakeTemporaryFilename( char const *pchModPath, char *pPath, int pathSize ); + virtual void DeleteTemporaryFiles( const char *pFileMask ); + virtual int CompareFileTime( const char *pFilenameA, const char *pFilenameB ); + virtual bool DoesFileExist( const char *pFilename ); + +private: + + int GetFileList( const char* pDirPath, const char* pPattern, CUtlVector< fileList_t > &fileList ); + void RecurseFileTree_r( const char* pDirPath, int depth, CUtlVector< CUtlString > &dirList ); +}; + +static CScriptLib g_ScriptLib; +IScriptLib *scriptlib = &g_ScriptLib; +IScriptLib *g_pScriptLib = &g_ScriptLib; + +//----------------------------------------------------------------------------- +// Existence check +//----------------------------------------------------------------------------- +bool CScriptLib::DoesFileExist( const char *pFilename ) +{ + return g_pFullFileSystem->FileExists( pFilename ); +} + +//----------------------------------------------------------------------------- +// Purpose: Helper utility, read file into buffer +//----------------------------------------------------------------------------- +bool CScriptLib::ReadFileToBuffer( const char *pSourceName, CUtlBuffer &buffer, bool bText, bool bNoOpenFailureWarning ) +{ + bool bSuccess = true; + + if ( !g_pFullFileSystem->ReadFile( pSourceName, NULL, buffer ) ) + { + if ( !bNoOpenFailureWarning ) + { + Msg( "ReadFileToBuffer(): Error opening %s: %s\n", pSourceName, strerror( errno ) ); + } + return false; + } + + if ( bText ) + { + // force it into text mode + buffer.SetBufferType( true, true ); + } + else + { + buffer.SetBufferType( false, false ); + } + + return bSuccess; +} + +//----------------------------------------------------------------------------- +// Purpose: Helper utility, Write buffer to file +//----------------------------------------------------------------------------- +bool CScriptLib::WriteBufferToFile( const char *pTargetName, CUtlBuffer &buffer, DiskWriteMode_t writeMode ) +{ + char* ptr; + char dirPath[MAX_PATH]; + + bool bSuccess = true; + + // create path + // prime and skip to first seperator + strcpy( dirPath, pTargetName ); + ptr = strchr( dirPath, '\\' ); + while ( ptr ) + { + ptr = strchr( ptr+1, '\\' ); + if ( ptr ) + { + *ptr = '\0'; + _mkdir( dirPath ); + *ptr = '\\'; + } + } + + bool bDoWrite = false; + if ( writeMode == WRITE_TO_DISK_ALWAYS ) + { + bDoWrite = true; + } + else if ( writeMode == WRITE_TO_DISK_UPDATE ) + { + if ( DoesFileExist( pTargetName ) ) + { + bDoWrite = true; + } + } + + if ( bDoWrite ) + { + bSuccess = g_pFullFileSystem->WriteFile( pTargetName, NULL, buffer ); + } + + return bSuccess; +} + +//----------------------------------------------------------------------------- +// Returns -1, 0, or 1. +//----------------------------------------------------------------------------- +int CScriptLib::CompareFileTime( const char *pFilenameA, const char *pFilenameB ) +{ + int timeA = g_pFullFileSystem->GetFileTime( (char *)pFilenameA ); + int timeB = g_pFullFileSystem->GetFileTime( (char *)pFilenameB ); + + if ( timeA == -1) + { + // file a not exist + timeA = 0; + } + if ( timeB == -1 ) + { + // file b not exist + timeB = 0; + } + + if ( (unsigned int)timeA < (unsigned int)timeB ) + { + return -1; + } + else if ( (unsigned int)timeA > (unsigned int)timeB ) + { + return 1; + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Make a temporary filename +//----------------------------------------------------------------------------- +char *CScriptLib::MakeTemporaryFilename( char const *pchModPath, char *pPath, int pathSize ) +{ + char *pBuffer = _tempnam( pchModPath, "mgd_" ); + if ( pBuffer[0] == '\\' ) + { + pBuffer++; + } + if ( pBuffer[strlen( pBuffer )-1] == '.' ) + { + pBuffer[strlen( pBuffer )-1] = '\0'; + } + V_snprintf( pPath, pathSize, "%s.tmp", pBuffer ); + + free( pBuffer ); + + return pPath; +} + +//----------------------------------------------------------------------------- +// Delete temporary files +//----------------------------------------------------------------------------- +void CScriptLib::DeleteTemporaryFiles( const char *pFileMask ) +{ +#if !defined( _X360 ) + const char *pEnv = getenv( "temp" ); + if ( !pEnv ) + { + pEnv = getenv( "tmp" ); + } + + if ( pEnv ) + { + char tempPath[MAX_PATH]; + strcpy( tempPath, pEnv ); + V_AppendSlash( tempPath, sizeof( tempPath ) ); + strcat( tempPath, pFileMask ); + + CUtlVector fileList; + FindFiles( tempPath, false, fileList ); + for ( int i=0; i &fileList ) +{ + char sourcePath[MAX_PATH]; + char fullPath[MAX_PATH]; + bool bFindDirs; + + fileList.Purge(); + + strcpy( sourcePath, pDirPath ); + int len = (int)strlen( sourcePath ); + if ( !len ) + { + strcpy( sourcePath, ".\\" ); + } + else if ( sourcePath[len-1] != '\\' ) + { + sourcePath[len] = '\\'; + sourcePath[len+1] = '\0'; + } + + strcpy( fullPath, sourcePath ); + if ( pPattern[0] == '\\' && pPattern[1] == '\0' ) + { + // find directories only + bFindDirs = true; + strcat( fullPath, "*" ); + } + else + { + // find files, use provided pattern + bFindDirs = false; + strcat( fullPath, pPattern ); + } + +#ifdef WIN32 + struct _finddata_t findData; + intptr_t h = _findfirst( fullPath, &findData ); + if ( h == -1 ) + { + return 0; + } + + do + { + // dos attribute complexities i.e. _A_NORMAL is 0 + if ( bFindDirs ) + { + // skip non dirs + if ( !( findData.attrib & _A_SUBDIR ) ) + continue; + } + else + { + // skip dirs + if ( findData.attrib & _A_SUBDIR ) + continue; + } + + if ( !stricmp( findData.name, "." ) ) + continue; + + if ( !stricmp( findData.name, ".." ) ) + continue; + + char fileName[MAX_PATH]; + strcpy( fileName, sourcePath ); + strcat( fileName, findData.name ); + + int j = fileList.AddToTail(); + fileList[j].fileName.Set( fileName ); + fileList[j].timeWrite = findData.time_write; + } + while ( !_findnext( h, &findData ) ); + + _findclose( h ); +#elif defined(POSIX) + FIND_DATA findData; + Q_FixSlashes( fullPath ); + void *h = FindFirstFile( fullPath, &findData ); + if ( (int)h == -1 ) + { + return 0; + } + + do + { + // dos attribute complexities i.e. _A_NORMAL is 0 + if ( bFindDirs ) + { + // skip non dirs + if ( !( findData.dwFileAttributes & S_IFDIR ) ) + continue; + } + else + { + // skip dirs + if ( findData.dwFileAttributes & S_IFDIR ) + continue; + } + + if ( !stricmp( findData.cFileName, "." ) ) + continue; + + if ( !stricmp( findData.cFileName, ".." ) ) + continue; + + char fileName[MAX_PATH]; + strcpy( fileName, sourcePath ); + strcat( fileName, findData.cFileName ); + + int j = fileList.AddToTail(); + fileList[j].fileName.Set( fileName ); + struct stat statbuf; + if ( stat( fileName, &statbuf ) ) +#ifdef OSX + fileList[j].timeWrite = statbuf.st_mtimespec.tv_sec; +#else + fileList[j].timeWrite = statbuf.st_mtime; +#endif + else + fileList[j].timeWrite = 0; + } + while ( !FindNextFile( h, &findData ) ); + + FindClose( h ); + +#else +#error +#endif + + + return fileList.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: Recursively determine directory tree +//----------------------------------------------------------------------------- +void CScriptLib::RecurseFileTree_r( const char* pDirPath, int depth, CUtlVector< CUtlString > &dirList ) +{ + // recurse from source directory, get directories only + CUtlVector< fileList_t > fileList; + int dirCount = GetFileList( pDirPath, "\\", fileList ); + if ( !dirCount ) + { + // add directory name to search tree + int j = dirList.AddToTail(); + dirList[j].Set( pDirPath ); + return; + } + + for ( int i=0; i &fileList ) +{ + char dirPath[MAX_PATH]; + char pattern[MAX_PATH]; + char extension[MAX_PATH]; + + // get path only + strcpy( dirPath, pFileMask ); + V_StripFilename( dirPath ); + + // get pattern only + V_FileBase( pFileMask, pattern, sizeof( pattern ) ); + V_ExtractFileExtension( pFileMask, extension, sizeof( extension ) ); + if ( extension[0] ) + { + strcat( pattern, "." ); + strcat( pattern, extension ); + } + + if ( !bRecurse ) + { + GetFileList( dirPath, pattern, fileList ); + } + else + { + // recurse and get the tree + CUtlVector< fileList_t > tempList; + CUtlVector< CUtlString > dirList; + RecurseFileTree_r( dirPath, 0, dirList ); + for ( int i=0; i &fileList ) = 0; - virtual char *MakeTemporaryFilename( char const *pchModPath, char *pPath, int pathSize ) = 0; - virtual void DeleteTemporaryFiles( const char *pFileMask ) = 0; - virtual int CompareFileTime( const char *pFilenameA, const char *pFilenameB ) = 0; - virtual bool DoesFileExist( const char *pFilename ) = 0; -}; - -extern IScriptLib *scriptlib; - - -#endif // SCRIPLIB_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef SCRIPLIB_H +#define SCRIPLIB_H + +#ifdef _WIN32 +#pragma once +#endif + + +enum ScriptPathMode_t +{ + SCRIPT_USE_ABSOLUTE_PATH, + SCRIPT_USE_RELATIVE_PATH +}; + + +// scriplib.h + +#define MAXTOKEN 1024 + +extern char token[MAXTOKEN]; +extern char *scriptbuffer,*script_p,*scriptend_p; +extern int grabbed; +extern int scriptline; +extern qboolean endofscript; + + +// If pathMode is SCRIPT_USE_ABSOLUTE_PATH, then it uses ExpandPath() on the filename before +// trying to open it. Otherwise, it passes the filename straight into the filesystem +// (so you can leave it as a relative path). +void LoadScriptFile (char *filename, ScriptPathMode_t pathMode=SCRIPT_USE_ABSOLUTE_PATH); +void ParseFromMemory (char *buffer, int size); + +qboolean GetToken (qboolean crossline); +qboolean GetExprToken (qboolean crossline); +void UnGetToken (void); +qboolean TokenAvailable (void); +qboolean GetTokenizerStatus( char **pFilename, int *pLine ); +bool SetCheckSingleCharTokens( bool bCheck ); + +// SCRIPT_LOADED_CALLBACK: +// Is called after the contents of a file is loaded. +// pFilenameLoaded is the path of a file that got loaded. +// pIncludedFromFileName is the name of the parent file or NULL if loaded because of "LoadScriptFile" toplevel call. +// nIncludeLineNumber is the number of the line in the parent file with $include statement or 0 in case of "LoadScriptFile" +typedef void ( * SCRIPT_LOADED_CALLBACK )( char const *pFilenameLoaded, char const *pIncludedFromFileName, int nIncludeLineNumber ); + +// SetScriptLoadedCallback: +// Sets the new callback for script loading. +// Returns the previous callback function. +SCRIPT_LOADED_CALLBACK SetScriptLoadedCallback( SCRIPT_LOADED_CALLBACK pfnNewScriptLoadedCallback ); + +#include "tier1/utlstring.h" +#include "tier1/utlvector.h" + +CUtlString SetSingleCharTokenList( const char *pszSingleCharTokenList ); + +class CUtlBuffer; + +enum DiskWriteMode_t +{ + WRITE_TO_DISK_NEVER, + WRITE_TO_DISK_ALWAYS, + WRITE_TO_DISK_UPDATE, // file must exist +}; + +struct fileList_t +{ + CUtlString fileName; + time_t timeWrite; +}; + +class IScriptLib +{ +public: + virtual bool ReadFileToBuffer( const char *pSourceName, CUtlBuffer &buffer, bool bText = false, bool bNoOpenFailureWarning = false ) = 0; + virtual bool WriteBufferToFile( const char *pTargetName, CUtlBuffer &buffer, DiskWriteMode_t writeMode ) = 0; + virtual int FindFiles( char* pFileMask, bool bRecurse, CUtlVector &fileList ) = 0; + virtual char *MakeTemporaryFilename( char const *pchModPath, char *pPath, int pathSize ) = 0; + virtual void DeleteTemporaryFiles( const char *pFileMask ) = 0; + virtual int CompareFileTime( const char *pFilenameA, const char *pFilenameB ) = 0; + virtual bool DoesFileExist( const char *pFilename ) = 0; +}; + +extern IScriptLib *scriptlib; + + +#endif // SCRIPLIB_H diff --git a/mp/src/utils/common/threads.cpp b/mp/src/utils/common/threads.cpp index 344943c9..74e457a9 100644 --- a/mp/src/utils/common/threads.cpp +++ b/mp/src/utils/common/threads.cpp @@ -1,257 +1,257 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -#define USED - -#include -#include "cmdlib.h" -#define NO_THREAD_NAMES -#include "threads.h" -#include "pacifier.h" - -#define MAX_THREADS 16 - - -class CRunThreadsData -{ -public: - int m_iThread; - void *m_pUserData; - RunThreadsFn m_Fn; -}; - -CRunThreadsData g_RunThreadsData[MAX_THREADS]; - - -int dispatch; -int workcount; -qboolean pacifier; - -qboolean threaded; -bool g_bLowPriorityThreads = false; - -HANDLE g_ThreadHandles[MAX_THREADS]; - - - -/* -============= -GetThreadWork - -============= -*/ -int GetThreadWork (void) -{ - int r; - - ThreadLock (); - - if (dispatch == workcount) - { - ThreadUnlock (); - return -1; - } - - UpdatePacifier( (float)dispatch / workcount ); - - r = dispatch; - dispatch++; - ThreadUnlock (); - - return r; -} - - -ThreadWorkerFn workfunction; - -void ThreadWorkerFunction( int iThread, void *pUserData ) -{ - int work; - - while (1) - { - work = GetThreadWork (); - if (work == -1) - break; - - workfunction( iThread, work ); - } -} - -void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, ThreadWorkerFn func) -{ - if (numthreads == -1) - ThreadSetDefault (); - - workfunction = func; - RunThreadsOn (workcnt, showpacifier, ThreadWorkerFunction); -} - - -/* -=================================================================== - -WIN32 - -=================================================================== -*/ - -int numthreads = -1; -CRITICAL_SECTION crit; -static int enter; - - -class CCritInit -{ -public: - CCritInit() - { - InitializeCriticalSection (&crit); - } -} g_CritInit; - - - -void SetLowPriority() -{ - SetPriorityClass( GetCurrentProcess(), IDLE_PRIORITY_CLASS ); -} - - -void ThreadSetDefault (void) -{ - SYSTEM_INFO info; - - if (numthreads == -1) // not set manually - { - GetSystemInfo (&info); - numthreads = info.dwNumberOfProcessors; - if (numthreads < 1 || numthreads > 32) - numthreads = 1; - } - - Msg ("%i threads\n", numthreads); -} - - -void ThreadLock (void) -{ - if (!threaded) - return; - EnterCriticalSection (&crit); - if (enter) - Error ("Recursive ThreadLock\n"); - enter = 1; -} - -void ThreadUnlock (void) -{ - if (!threaded) - return; - if (!enter) - Error ("ThreadUnlock without lock\n"); - enter = 0; - LeaveCriticalSection (&crit); -} - - -// This runs in the thread and dispatches a RunThreadsFn call. -DWORD WINAPI InternalRunThreadsFn( LPVOID pParameter ) -{ - CRunThreadsData *pData = (CRunThreadsData*)pParameter; - pData->m_Fn( pData->m_iThread, pData->m_pUserData ); - return 0; -} - - -void RunThreads_Start( RunThreadsFn fn, void *pUserData, ERunThreadsPriority ePriority ) -{ - Assert( numthreads > 0 ); - threaded = true; - - if ( numthreads > MAX_TOOL_THREADS ) - numthreads = MAX_TOOL_THREADS; - - for ( int i=0; i < numthreads ;i++ ) - { - g_RunThreadsData[i].m_iThread = i; - g_RunThreadsData[i].m_pUserData = pUserData; - g_RunThreadsData[i].m_Fn = fn; - - DWORD dwDummy; - g_ThreadHandles[i] = CreateThread( - NULL, // LPSECURITY_ATTRIBUTES lpsa, - 0, // DWORD cbStack, - InternalRunThreadsFn, // LPTHREAD_START_ROUTINE lpStartAddr, - &g_RunThreadsData[i], // LPVOID lpvThreadParm, - 0, // DWORD fdwCreate, - &dwDummy ); - - if ( ePriority == k_eRunThreadsPriority_UseGlobalState ) - { - if( g_bLowPriorityThreads ) - SetThreadPriority( g_ThreadHandles[i], THREAD_PRIORITY_LOWEST ); - } - else if ( ePriority == k_eRunThreadsPriority_Idle ) - { - SetThreadPriority( g_ThreadHandles[i], THREAD_PRIORITY_IDLE ); - } - } -} - - -void RunThreads_End() -{ - WaitForMultipleObjects( numthreads, g_ThreadHandles, TRUE, INFINITE ); - for ( int i=0; i < numthreads; i++ ) - CloseHandle( g_ThreadHandles[i] ); - - threaded = false; -} - - -/* -============= -RunThreadsOn -============= -*/ -void RunThreadsOn( int workcnt, qboolean showpacifier, RunThreadsFn fn, void *pUserData ) -{ - int start, end; - - start = Plat_FloatTime(); - dispatch = 0; - workcount = workcnt; - StartPacifier(""); - pacifier = showpacifier; - -#ifdef _PROFILE - threaded = false; - (*func)( 0 ); - return; -#endif - - - RunThreads_Start( fn, pUserData ); - RunThreads_End(); - - - end = Plat_FloatTime(); - if (pacifier) - { - EndPacifier(false); - printf (" (%i)\n", end-start); - } -} - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#define USED + +#include +#include "cmdlib.h" +#define NO_THREAD_NAMES +#include "threads.h" +#include "pacifier.h" + +#define MAX_THREADS 16 + + +class CRunThreadsData +{ +public: + int m_iThread; + void *m_pUserData; + RunThreadsFn m_Fn; +}; + +CRunThreadsData g_RunThreadsData[MAX_THREADS]; + + +int dispatch; +int workcount; +qboolean pacifier; + +qboolean threaded; +bool g_bLowPriorityThreads = false; + +HANDLE g_ThreadHandles[MAX_THREADS]; + + + +/* +============= +GetThreadWork + +============= +*/ +int GetThreadWork (void) +{ + int r; + + ThreadLock (); + + if (dispatch == workcount) + { + ThreadUnlock (); + return -1; + } + + UpdatePacifier( (float)dispatch / workcount ); + + r = dispatch; + dispatch++; + ThreadUnlock (); + + return r; +} + + +ThreadWorkerFn workfunction; + +void ThreadWorkerFunction( int iThread, void *pUserData ) +{ + int work; + + while (1) + { + work = GetThreadWork (); + if (work == -1) + break; + + workfunction( iThread, work ); + } +} + +void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, ThreadWorkerFn func) +{ + if (numthreads == -1) + ThreadSetDefault (); + + workfunction = func; + RunThreadsOn (workcnt, showpacifier, ThreadWorkerFunction); +} + + +/* +=================================================================== + +WIN32 + +=================================================================== +*/ + +int numthreads = -1; +CRITICAL_SECTION crit; +static int enter; + + +class CCritInit +{ +public: + CCritInit() + { + InitializeCriticalSection (&crit); + } +} g_CritInit; + + + +void SetLowPriority() +{ + SetPriorityClass( GetCurrentProcess(), IDLE_PRIORITY_CLASS ); +} + + +void ThreadSetDefault (void) +{ + SYSTEM_INFO info; + + if (numthreads == -1) // not set manually + { + GetSystemInfo (&info); + numthreads = info.dwNumberOfProcessors; + if (numthreads < 1 || numthreads > 32) + numthreads = 1; + } + + Msg ("%i threads\n", numthreads); +} + + +void ThreadLock (void) +{ + if (!threaded) + return; + EnterCriticalSection (&crit); + if (enter) + Error ("Recursive ThreadLock\n"); + enter = 1; +} + +void ThreadUnlock (void) +{ + if (!threaded) + return; + if (!enter) + Error ("ThreadUnlock without lock\n"); + enter = 0; + LeaveCriticalSection (&crit); +} + + +// This runs in the thread and dispatches a RunThreadsFn call. +DWORD WINAPI InternalRunThreadsFn( LPVOID pParameter ) +{ + CRunThreadsData *pData = (CRunThreadsData*)pParameter; + pData->m_Fn( pData->m_iThread, pData->m_pUserData ); + return 0; +} + + +void RunThreads_Start( RunThreadsFn fn, void *pUserData, ERunThreadsPriority ePriority ) +{ + Assert( numthreads > 0 ); + threaded = true; + + if ( numthreads > MAX_TOOL_THREADS ) + numthreads = MAX_TOOL_THREADS; + + for ( int i=0; i < numthreads ;i++ ) + { + g_RunThreadsData[i].m_iThread = i; + g_RunThreadsData[i].m_pUserData = pUserData; + g_RunThreadsData[i].m_Fn = fn; + + DWORD dwDummy; + g_ThreadHandles[i] = CreateThread( + NULL, // LPSECURITY_ATTRIBUTES lpsa, + 0, // DWORD cbStack, + InternalRunThreadsFn, // LPTHREAD_START_ROUTINE lpStartAddr, + &g_RunThreadsData[i], // LPVOID lpvThreadParm, + 0, // DWORD fdwCreate, + &dwDummy ); + + if ( ePriority == k_eRunThreadsPriority_UseGlobalState ) + { + if( g_bLowPriorityThreads ) + SetThreadPriority( g_ThreadHandles[i], THREAD_PRIORITY_LOWEST ); + } + else if ( ePriority == k_eRunThreadsPriority_Idle ) + { + SetThreadPriority( g_ThreadHandles[i], THREAD_PRIORITY_IDLE ); + } + } +} + + +void RunThreads_End() +{ + WaitForMultipleObjects( numthreads, g_ThreadHandles, TRUE, INFINITE ); + for ( int i=0; i < numthreads; i++ ) + CloseHandle( g_ThreadHandles[i] ); + + threaded = false; +} + + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn( int workcnt, qboolean showpacifier, RunThreadsFn fn, void *pUserData ) +{ + int start, end; + + start = Plat_FloatTime(); + dispatch = 0; + workcount = workcnt; + StartPacifier(""); + pacifier = showpacifier; + +#ifdef _PROFILE + threaded = false; + (*func)( 0 ); + return; +#endif + + + RunThreads_Start( fn, pUserData ); + RunThreads_End(); + + + end = Plat_FloatTime(); + if (pacifier) + { + EndPacifier(false); + printf (" (%i)\n", end-start); + } +} + + diff --git a/mp/src/utils/common/threads.h b/mp/src/utils/common/threads.h index e29e9aab..0908b67a 100644 --- a/mp/src/utils/common/threads.h +++ b/mp/src/utils/common/threads.h @@ -1,65 +1,65 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef THREADS_H -#define THREADS_H -#pragma once - - -// Arrays that are indexed by thread should always be MAX_TOOL_THREADS+1 -// large so THREADINDEX_MAIN can be used from the main thread. -#define MAX_TOOL_THREADS 16 -#define THREADINDEX_MAIN (MAX_TOOL_THREADS) - - -extern int numthreads; - -// If set to true, then all the threads that are created are low priority. -extern bool g_bLowPriorityThreads; - -typedef void (*ThreadWorkerFn)( int iThread, int iWorkItem ); -typedef void (*RunThreadsFn)( int iThread, void *pUserData ); - - -enum ERunThreadsPriority -{ - k_eRunThreadsPriority_UseGlobalState=0, // Default.. uses g_bLowPriorityThreads to decide what to set the priority to. - k_eRunThreadsPriority_Normal, // Doesn't touch thread priorities. - k_eRunThreadsPriority_Idle // Sets threads to idle priority. -}; - - -// Put the process into an idle priority class so it doesn't hog the UI. -void SetLowPriority(); - -void ThreadSetDefault (void); -int GetThreadWork (void); - -void RunThreadsOnIndividual ( int workcnt, qboolean showpacifier, ThreadWorkerFn fn ); - -void RunThreadsOn ( int workcnt, qboolean showpacifier, RunThreadsFn fn, void *pUserData=NULL ); - -// This version doesn't track work items - it just runs your function and waits for it to finish. -void RunThreads_Start( RunThreadsFn fn, void *pUserData, ERunThreadsPriority ePriority=k_eRunThreadsPriority_UseGlobalState ); -void RunThreads_End(); - -void ThreadLock (void); -void ThreadUnlock (void); - - -#ifndef NO_THREAD_NAMES -#define RunThreadsOn(n,p,f) { if (p) printf("%-20s ", #f ":"); RunThreadsOn(n,p,f); } -#define RunThreadsOnIndividual(n,p,f) { if (p) printf("%-20s ", #f ":"); RunThreadsOnIndividual(n,p,f); } -#endif - -#endif // THREADS_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef THREADS_H +#define THREADS_H +#pragma once + + +// Arrays that are indexed by thread should always be MAX_TOOL_THREADS+1 +// large so THREADINDEX_MAIN can be used from the main thread. +#define MAX_TOOL_THREADS 16 +#define THREADINDEX_MAIN (MAX_TOOL_THREADS) + + +extern int numthreads; + +// If set to true, then all the threads that are created are low priority. +extern bool g_bLowPriorityThreads; + +typedef void (*ThreadWorkerFn)( int iThread, int iWorkItem ); +typedef void (*RunThreadsFn)( int iThread, void *pUserData ); + + +enum ERunThreadsPriority +{ + k_eRunThreadsPriority_UseGlobalState=0, // Default.. uses g_bLowPriorityThreads to decide what to set the priority to. + k_eRunThreadsPriority_Normal, // Doesn't touch thread priorities. + k_eRunThreadsPriority_Idle // Sets threads to idle priority. +}; + + +// Put the process into an idle priority class so it doesn't hog the UI. +void SetLowPriority(); + +void ThreadSetDefault (void); +int GetThreadWork (void); + +void RunThreadsOnIndividual ( int workcnt, qboolean showpacifier, ThreadWorkerFn fn ); + +void RunThreadsOn ( int workcnt, qboolean showpacifier, RunThreadsFn fn, void *pUserData=NULL ); + +// This version doesn't track work items - it just runs your function and waits for it to finish. +void RunThreads_Start( RunThreadsFn fn, void *pUserData, ERunThreadsPriority ePriority=k_eRunThreadsPriority_UseGlobalState ); +void RunThreads_End(); + +void ThreadLock (void); +void ThreadUnlock (void); + + +#ifndef NO_THREAD_NAMES +#define RunThreadsOn(n,p,f) { if (p) printf("%-20s ", #f ":"); RunThreadsOn(n,p,f); } +#define RunThreadsOnIndividual(n,p,f) { if (p) printf("%-20s ", #f ":"); RunThreadsOnIndividual(n,p,f); } +#endif + +#endif // THREADS_H diff --git a/mp/src/utils/common/tools_minidump.cpp b/mp/src/utils/common/tools_minidump.cpp index a0c84209..a7659200 100644 --- a/mp/src/utils/common/tools_minidump.cpp +++ b/mp/src/utils/common/tools_minidump.cpp @@ -1,61 +1,61 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include -#include -#include "tier0/minidump.h" -#include "tools_minidump.h" - -static bool g_bToolsWriteFullMinidumps = false; -static ToolsExceptionHandler g_pCustomExceptionHandler = NULL; - - -// --------------------------------------------------------------------------------- // -// Internal helpers. -// --------------------------------------------------------------------------------- // - -static LONG __stdcall ToolsExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo ) -{ - // Non VMPI workers write a minidump and show a crash dialog like normal. - int iType = MiniDumpNormal; - if ( g_bToolsWriteFullMinidumps ) - iType = MiniDumpWithDataSegs | MiniDumpWithIndirectlyReferencedMemory; - - WriteMiniDumpUsingExceptionInfo( ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo, (MINIDUMP_TYPE)iType ); - return EXCEPTION_CONTINUE_SEARCH; -} - - -static LONG __stdcall ToolsExceptionFilter_Custom( struct _EXCEPTION_POINTERS *ExceptionInfo ) -{ - // Run their custom handler. - g_pCustomExceptionHandler( ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo ); - return EXCEPTION_EXECUTE_HANDLER; // (never gets here anyway) -} - - -// --------------------------------------------------------------------------------- // -// Interface functions. -// --------------------------------------------------------------------------------- // - -void EnableFullMinidumps( bool bFull ) -{ - g_bToolsWriteFullMinidumps = bFull; -} - - -void SetupDefaultToolsMinidumpHandler() -{ - SetUnhandledExceptionFilter( ToolsExceptionFilter ); -} - - -void SetupToolsMinidumpHandler( ToolsExceptionHandler fn ) -{ - g_pCustomExceptionHandler = fn; - SetUnhandledExceptionFilter( ToolsExceptionFilter_Custom ); -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include +#include +#include "tier0/minidump.h" +#include "tools_minidump.h" + +static bool g_bToolsWriteFullMinidumps = false; +static ToolsExceptionHandler g_pCustomExceptionHandler = NULL; + + +// --------------------------------------------------------------------------------- // +// Internal helpers. +// --------------------------------------------------------------------------------- // + +static LONG __stdcall ToolsExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo ) +{ + // Non VMPI workers write a minidump and show a crash dialog like normal. + int iType = MiniDumpNormal; + if ( g_bToolsWriteFullMinidumps ) + iType = MiniDumpWithDataSegs | MiniDumpWithIndirectlyReferencedMemory; + + WriteMiniDumpUsingExceptionInfo( ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo, (MINIDUMP_TYPE)iType ); + return EXCEPTION_CONTINUE_SEARCH; +} + + +static LONG __stdcall ToolsExceptionFilter_Custom( struct _EXCEPTION_POINTERS *ExceptionInfo ) +{ + // Run their custom handler. + g_pCustomExceptionHandler( ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo ); + return EXCEPTION_EXECUTE_HANDLER; // (never gets here anyway) +} + + +// --------------------------------------------------------------------------------- // +// Interface functions. +// --------------------------------------------------------------------------------- // + +void EnableFullMinidumps( bool bFull ) +{ + g_bToolsWriteFullMinidumps = bFull; +} + + +void SetupDefaultToolsMinidumpHandler() +{ + SetUnhandledExceptionFilter( ToolsExceptionFilter ); +} + + +void SetupToolsMinidumpHandler( ToolsExceptionHandler fn ) +{ + g_pCustomExceptionHandler = fn; + SetUnhandledExceptionFilter( ToolsExceptionFilter_Custom ); +} diff --git a/mp/src/utils/common/tools_minidump.h b/mp/src/utils/common/tools_minidump.h index dfb44a9b..2e5f12cb 100644 --- a/mp/src/utils/common/tools_minidump.h +++ b/mp/src/utils/common/tools_minidump.h @@ -1,35 +1,35 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef TOOLS_MINIDUMP_H -#define TOOLS_MINIDUMP_H -#ifdef _WIN32 -#pragma once -#endif - - - -// Defaults to false. If true, it'll write larger minidump files with the contents -// of global variables and following pointers from where the crash occurred. -void EnableFullMinidumps( bool bFull ); - - -// This handler catches any crash, writes a minidump, and runs the default system -// crash handler (which usually shows a dialog). -void SetupDefaultToolsMinidumpHandler(); - - -// (Used by VMPI) - you specify your own crash handler. -// Arguments passed to ToolsExceptionHandler -// exceptionCode - exception code -// pvExceptionInfo - on Win32 platform points to "struct _EXCEPTION_POINTERS" -// otherwise NULL -// -typedef void (*ToolsExceptionHandler)( unsigned long exceptionCode, void *pvExceptionInfo ); -void SetupToolsMinidumpHandler( ToolsExceptionHandler fn ); - - -#endif // MINIDUMP_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TOOLS_MINIDUMP_H +#define TOOLS_MINIDUMP_H +#ifdef _WIN32 +#pragma once +#endif + + + +// Defaults to false. If true, it'll write larger minidump files with the contents +// of global variables and following pointers from where the crash occurred. +void EnableFullMinidumps( bool bFull ); + + +// This handler catches any crash, writes a minidump, and runs the default system +// crash handler (which usually shows a dialog). +void SetupDefaultToolsMinidumpHandler(); + + +// (Used by VMPI) - you specify your own crash handler. +// Arguments passed to ToolsExceptionHandler +// exceptionCode - exception code +// pvExceptionInfo - on Win32 platform points to "struct _EXCEPTION_POINTERS" +// otherwise NULL +// +typedef void (*ToolsExceptionHandler)( unsigned long exceptionCode, void *pvExceptionInfo ); +void SetupToolsMinidumpHandler( ToolsExceptionHandler fn ); + + +#endif // MINIDUMP_H diff --git a/mp/src/utils/common/utilmatlib.cpp b/mp/src/utils/common/utilmatlib.cpp index 962bb3f5..f2dc49fa 100644 --- a/mp/src/utils/common/utilmatlib.cpp +++ b/mp/src/utils/common/utilmatlib.cpp @@ -1,184 +1,184 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// - -// C callable material system interface for the utils. - -#include "materialsystem/imaterialsystem.h" -#include "materialsystem/imaterial.h" -#include "materialsystem/imaterialvar.h" -#include -#include "utilmatlib.h" -#include "tier0/dbg.h" -#include -#include "filesystem.h" -#include "materialsystem/materialsystem_config.h" -#include "mathlib/Mathlib.h" - -void LoadMaterialSystemInterface( CreateInterfaceFn fileSystemFactory ) -{ - if( g_pMaterialSystem ) - return; - - // materialsystem.dll should be in the path, it's in bin along with vbsp. - const char *pDllName = "materialsystem.dll"; - CSysModule *materialSystemDLLHInst; - materialSystemDLLHInst = g_pFullFileSystem->LoadModule( pDllName ); - if( !materialSystemDLLHInst ) - { - Error( "Can't load MaterialSystem.dll\n" ); - } - - CreateInterfaceFn clientFactory = Sys_GetFactory( materialSystemDLLHInst ); - if ( clientFactory ) - { - g_pMaterialSystem = (IMaterialSystem *)clientFactory( MATERIAL_SYSTEM_INTERFACE_VERSION, NULL ); - if ( !g_pMaterialSystem ) - { - Error( "Could not get the material system interface from materialsystem.dll (" __FILE__ ")" ); - } - } - else - { - Error( "Could not find factory interface in library MaterialSystem.dll" ); - } - - if (!g_pMaterialSystem->Init( "shaderapiempty.dll", 0, fileSystemFactory )) - { - Error( "Could not start the empty shader (shaderapiempty.dll)!" ); - } -} - -void InitMaterialSystem( const char *materialBaseDirPath, CreateInterfaceFn fileSystemFactory ) -{ - LoadMaterialSystemInterface( fileSystemFactory ); - MaterialSystem_Config_t config; - g_pMaterialSystem->OverrideConfig( config, false ); -} - -void ShutdownMaterialSystem( ) -{ - if ( g_pMaterialSystem ) - { - g_pMaterialSystem->Shutdown(); - g_pMaterialSystem = NULL; - } -} - -MaterialSystemMaterial_t FindMaterial( const char *materialName, bool *pFound, bool bComplain ) -{ - IMaterial *pMat = g_pMaterialSystem->FindMaterial( materialName, TEXTURE_GROUP_OTHER, bComplain ); - MaterialSystemMaterial_t matHandle = pMat; - - if ( pFound ) - { - *pFound = true; - if ( IsErrorMaterial( pMat ) ) - *pFound = false; - } - - return matHandle; -} - -void GetMaterialDimensions( MaterialSystemMaterial_t materialHandle, int *width, int *height ) -{ - PreviewImageRetVal_t retVal; - ImageFormat dummyImageFormat; - IMaterial *material = ( IMaterial * )materialHandle; - bool translucent; - retVal = material->GetPreviewImageProperties( width, height, &dummyImageFormat, &translucent ); - if (retVal != MATERIAL_PREVIEW_IMAGE_OK ) - { -#if 0 - if (retVal == MATERIAL_PREVIEW_IMAGE_BAD ) - { - Error( "problem getting preview image for %s", - g_pMaterialSystem->GetMaterialName( materialInfo[matID].materialHandle ) ); - } -#else - *width = 128; - *height = 128; -#endif - } -} - -void GetMaterialReflectivity( MaterialSystemMaterial_t materialHandle, float *reflectivityVect ) -{ - IMaterial *material = ( IMaterial * )materialHandle; - const IMaterialVar *reflectivityVar; - - bool found; - reflectivityVar = material->FindVar( "$reflectivity", &found, false ); - if( !found ) - { - Vector tmp; - material->GetReflectivity( tmp ); - VectorCopy( tmp.Base(), reflectivityVect ); - } - else - { - reflectivityVar->GetVecValue( reflectivityVect, 3 ); - } -} - -int GetMaterialShaderPropertyBool( MaterialSystemMaterial_t materialHandle, int propID ) -{ - IMaterial *material = ( IMaterial * )materialHandle; - switch( propID ) - { - case UTILMATLIB_NEEDS_BUMPED_LIGHTMAPS: - return material->GetPropertyFlag( MATERIAL_PROPERTY_NEEDS_BUMPED_LIGHTMAPS ); - - case UTILMATLIB_NEEDS_LIGHTMAP: - return material->GetPropertyFlag( MATERIAL_PROPERTY_NEEDS_LIGHTMAP ); - - default: - Assert( 0 ); - return 0; - } -} - -int GetMaterialShaderPropertyInt( MaterialSystemMaterial_t materialHandle, int propID ) -{ - IMaterial *material = ( IMaterial * )materialHandle; - switch( propID ) - { - case UTILMATLIB_OPACITY: - if (material->IsTranslucent()) - return UTILMATLIB_TRANSLUCENT; - if (material->IsAlphaTested()) - return UTILMATLIB_ALPHATEST; - return UTILMATLIB_OPAQUE; - - default: - Assert( 0 ); - return 0; - } -} - -const char *GetMaterialVar( MaterialSystemMaterial_t materialHandle, const char *propertyName ) -{ - IMaterial *material = ( IMaterial * )materialHandle; - IMaterialVar *var; - bool found; - var = material->FindVar( propertyName, &found, false ); - if( found ) - { - return var->GetStringValue(); - } - else - { - return NULL; - } -} - -const char *GetMaterialShaderName( MaterialSystemMaterial_t materialHandle ) -{ - IMaterial *material = ( IMaterial * )materialHandle; - return material->GetShaderName(); -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +// C callable material system interface for the utils. + +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/imaterialvar.h" +#include +#include "utilmatlib.h" +#include "tier0/dbg.h" +#include +#include "filesystem.h" +#include "materialsystem/materialsystem_config.h" +#include "mathlib/Mathlib.h" + +void LoadMaterialSystemInterface( CreateInterfaceFn fileSystemFactory ) +{ + if( g_pMaterialSystem ) + return; + + // materialsystem.dll should be in the path, it's in bin along with vbsp. + const char *pDllName = "materialsystem.dll"; + CSysModule *materialSystemDLLHInst; + materialSystemDLLHInst = g_pFullFileSystem->LoadModule( pDllName ); + if( !materialSystemDLLHInst ) + { + Error( "Can't load MaterialSystem.dll\n" ); + } + + CreateInterfaceFn clientFactory = Sys_GetFactory( materialSystemDLLHInst ); + if ( clientFactory ) + { + g_pMaterialSystem = (IMaterialSystem *)clientFactory( MATERIAL_SYSTEM_INTERFACE_VERSION, NULL ); + if ( !g_pMaterialSystem ) + { + Error( "Could not get the material system interface from materialsystem.dll (" __FILE__ ")" ); + } + } + else + { + Error( "Could not find factory interface in library MaterialSystem.dll" ); + } + + if (!g_pMaterialSystem->Init( "shaderapiempty.dll", 0, fileSystemFactory )) + { + Error( "Could not start the empty shader (shaderapiempty.dll)!" ); + } +} + +void InitMaterialSystem( const char *materialBaseDirPath, CreateInterfaceFn fileSystemFactory ) +{ + LoadMaterialSystemInterface( fileSystemFactory ); + MaterialSystem_Config_t config; + g_pMaterialSystem->OverrideConfig( config, false ); +} + +void ShutdownMaterialSystem( ) +{ + if ( g_pMaterialSystem ) + { + g_pMaterialSystem->Shutdown(); + g_pMaterialSystem = NULL; + } +} + +MaterialSystemMaterial_t FindMaterial( const char *materialName, bool *pFound, bool bComplain ) +{ + IMaterial *pMat = g_pMaterialSystem->FindMaterial( materialName, TEXTURE_GROUP_OTHER, bComplain ); + MaterialSystemMaterial_t matHandle = pMat; + + if ( pFound ) + { + *pFound = true; + if ( IsErrorMaterial( pMat ) ) + *pFound = false; + } + + return matHandle; +} + +void GetMaterialDimensions( MaterialSystemMaterial_t materialHandle, int *width, int *height ) +{ + PreviewImageRetVal_t retVal; + ImageFormat dummyImageFormat; + IMaterial *material = ( IMaterial * )materialHandle; + bool translucent; + retVal = material->GetPreviewImageProperties( width, height, &dummyImageFormat, &translucent ); + if (retVal != MATERIAL_PREVIEW_IMAGE_OK ) + { +#if 0 + if (retVal == MATERIAL_PREVIEW_IMAGE_BAD ) + { + Error( "problem getting preview image for %s", + g_pMaterialSystem->GetMaterialName( materialInfo[matID].materialHandle ) ); + } +#else + *width = 128; + *height = 128; +#endif + } +} + +void GetMaterialReflectivity( MaterialSystemMaterial_t materialHandle, float *reflectivityVect ) +{ + IMaterial *material = ( IMaterial * )materialHandle; + const IMaterialVar *reflectivityVar; + + bool found; + reflectivityVar = material->FindVar( "$reflectivity", &found, false ); + if( !found ) + { + Vector tmp; + material->GetReflectivity( tmp ); + VectorCopy( tmp.Base(), reflectivityVect ); + } + else + { + reflectivityVar->GetVecValue( reflectivityVect, 3 ); + } +} + +int GetMaterialShaderPropertyBool( MaterialSystemMaterial_t materialHandle, int propID ) +{ + IMaterial *material = ( IMaterial * )materialHandle; + switch( propID ) + { + case UTILMATLIB_NEEDS_BUMPED_LIGHTMAPS: + return material->GetPropertyFlag( MATERIAL_PROPERTY_NEEDS_BUMPED_LIGHTMAPS ); + + case UTILMATLIB_NEEDS_LIGHTMAP: + return material->GetPropertyFlag( MATERIAL_PROPERTY_NEEDS_LIGHTMAP ); + + default: + Assert( 0 ); + return 0; + } +} + +int GetMaterialShaderPropertyInt( MaterialSystemMaterial_t materialHandle, int propID ) +{ + IMaterial *material = ( IMaterial * )materialHandle; + switch( propID ) + { + case UTILMATLIB_OPACITY: + if (material->IsTranslucent()) + return UTILMATLIB_TRANSLUCENT; + if (material->IsAlphaTested()) + return UTILMATLIB_ALPHATEST; + return UTILMATLIB_OPAQUE; + + default: + Assert( 0 ); + return 0; + } +} + +const char *GetMaterialVar( MaterialSystemMaterial_t materialHandle, const char *propertyName ) +{ + IMaterial *material = ( IMaterial * )materialHandle; + IMaterialVar *var; + bool found; + var = material->FindVar( propertyName, &found, false ); + if( found ) + { + return var->GetStringValue(); + } + else + { + return NULL; + } +} + +const char *GetMaterialShaderName( MaterialSystemMaterial_t materialHandle ) +{ + IMaterial *material = ( IMaterial * )materialHandle; + return material->GetShaderName(); +} diff --git a/mp/src/utils/common/utilmatlib.h b/mp/src/utils/common/utilmatlib.h index f73a73d0..9d2e0b57 100644 --- a/mp/src/utils/common/utilmatlib.h +++ b/mp/src/utils/common/utilmatlib.h @@ -1,41 +1,41 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// - -#ifndef UTILMATLIB_H -#define UTILMATLIB_H - -#ifdef _WIN32 -#pragma once -#endif - -#define MATERIAL_NOT_FOUND NULL - -class IMaterialSystem; -extern IMaterialSystem *g_pMaterialSystem; - -typedef void *MaterialSystemMaterial_t; - -#define UTILMATLIB_NEEDS_BUMPED_LIGHTMAPS 0 -#define UTILMATLIB_NEEDS_LIGHTMAP 1 -#define UTILMATLIB_OPACITY 2 - -enum { UTILMATLIB_ALPHATEST = 0, UTILMATLIB_OPAQUE, UTILMATLIB_TRANSLUCENT }; - -void InitMaterialSystem( const char *materialBaseDirPath, CreateInterfaceFn fileSystemFactory ); -void ShutdownMaterialSystem( ); -MaterialSystemMaterial_t FindMaterial( const char *materialName, bool *pFound, bool bComplain = true ); -void GetMaterialDimensions( MaterialSystemMaterial_t materialHandle, int *width, int *height ); -int GetMaterialShaderPropertyBool( MaterialSystemMaterial_t materialHandle, int propID ); -int GetMaterialShaderPropertyInt( MaterialSystemMaterial_t materialHandle, int propID ); -const char *GetMaterialVar( MaterialSystemMaterial_t materialHandle, const char *propertyName ); -void GetMaterialReflectivity( MaterialSystemMaterial_t materialHandle, float *reflectivityVect ); -const char *GetMaterialShaderName( MaterialSystemMaterial_t materialHandle ); - - -#endif // UTILMATLIB_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef UTILMATLIB_H +#define UTILMATLIB_H + +#ifdef _WIN32 +#pragma once +#endif + +#define MATERIAL_NOT_FOUND NULL + +class IMaterialSystem; +extern IMaterialSystem *g_pMaterialSystem; + +typedef void *MaterialSystemMaterial_t; + +#define UTILMATLIB_NEEDS_BUMPED_LIGHTMAPS 0 +#define UTILMATLIB_NEEDS_LIGHTMAP 1 +#define UTILMATLIB_OPACITY 2 + +enum { UTILMATLIB_ALPHATEST = 0, UTILMATLIB_OPAQUE, UTILMATLIB_TRANSLUCENT }; + +void InitMaterialSystem( const char *materialBaseDirPath, CreateInterfaceFn fileSystemFactory ); +void ShutdownMaterialSystem( ); +MaterialSystemMaterial_t FindMaterial( const char *materialName, bool *pFound, bool bComplain = true ); +void GetMaterialDimensions( MaterialSystemMaterial_t materialHandle, int *width, int *height ); +int GetMaterialShaderPropertyBool( MaterialSystemMaterial_t materialHandle, int propID ); +int GetMaterialShaderPropertyInt( MaterialSystemMaterial_t materialHandle, int propID ); +const char *GetMaterialVar( MaterialSystemMaterial_t materialHandle, const char *propertyName ); +void GetMaterialReflectivity( MaterialSystemMaterial_t materialHandle, float *reflectivityVect ); +const char *GetMaterialShaderName( MaterialSystemMaterial_t materialHandle ); + + +#endif // UTILMATLIB_H diff --git a/mp/src/utils/common/vmpi_tools_shared.cpp b/mp/src/utils/common/vmpi_tools_shared.cpp index c753ce11..247569f4 100644 --- a/mp/src/utils/common/vmpi_tools_shared.cpp +++ b/mp/src/utils/common/vmpi_tools_shared.cpp @@ -1,374 +1,374 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#include -#include -#include "vmpi.h" -#include "cmdlib.h" -#include "vmpi_tools_shared.h" -#include "tier1/strtools.h" -#include "mpi_stats.h" -#include "iphelpers.h" -#include "tier0/minidump.h" - - -// ----------------------------------------------------------------------------- // -// Globals. -// ----------------------------------------------------------------------------- // - -static bool g_bReceivedDirectoryInfo = false; // Have we gotten the qdir info yet? - -static bool g_bReceivedDBInfo = false; -static CDBInfo g_DBInfo; -static unsigned long g_JobPrimaryID; - -static int g_nDisconnects = 0; // Tracks how many remote processes have disconnected ungracefully. - - -// ----------------------------------------------------------------------------- // -// Shared dispatch code. -// ----------------------------------------------------------------------------- // - -bool SharedDispatch( MessageBuffer *pBuf, int iSource, int iPacketID ) -{ - char *pInPos = &pBuf->data[2]; - - switch ( pBuf->data[1] ) - { - case VMPI_SUBPACKETID_DIRECTORIES: - { - Q_strncpy( gamedir, pInPos, sizeof( gamedir ) ); - pInPos += strlen( pInPos ) + 1; - - Q_strncpy( qdir, pInPos, sizeof( qdir ) ); - - g_bReceivedDirectoryInfo = true; - } - return true; - - case VMPI_SUBPACKETID_DBINFO: - { - g_DBInfo = *((CDBInfo*)pInPos); - pInPos += sizeof( CDBInfo ); - g_JobPrimaryID = *((unsigned long*)pInPos); - - g_bReceivedDBInfo = true; - } - return true; - - case VMPI_SUBPACKETID_CRASH: - { - char const chCrashInfoType = *pInPos; - pInPos += 2; - switch ( chCrashInfoType ) - { - case 't': - Warning( "\nWorker '%s' dead: %s\n", VMPI_GetMachineName( iSource ), pInPos ); - break; - case 'f': - { - int iFileSize = * reinterpret_cast< int const * >( pInPos ); - pInPos += sizeof( iFileSize ); - - // Temp folder - char const *szFolder = NULL; - if ( !szFolder ) szFolder = getenv( "TEMP" ); - if ( !szFolder ) szFolder = getenv( "TMP" ); - if ( !szFolder ) szFolder = "c:"; - - // Base module name - char chModuleName[_MAX_PATH], *pModuleName = chModuleName; - ::GetModuleFileName( NULL, chModuleName, sizeof( chModuleName ) / sizeof( chModuleName[0] ) ); - - if ( char *pch = strrchr( chModuleName, '.' ) ) - *pch = 0; - if ( char *pch = strrchr( chModuleName, '\\' ) ) - *pch = 0, pModuleName = pch + 1; - - // Current time - time_t currTime = ::time( NULL ); - struct tm * pTime = ::localtime( &currTime ); - - // Number of minidumps this run - static int s_numMiniDumps = 0; - ++ s_numMiniDumps; - - // Prepare the filename - char chSaveFileName[ 2 * _MAX_PATH ] = { 0 }; - sprintf( chSaveFileName, "%s\\vmpi_%s_on_%s_%d%.2d%2d%.2d%.2d%.2d_%d.mdmp", - szFolder, - pModuleName, - VMPI_GetMachineName( iSource ), - pTime->tm_year + 1900, /* Year less 2000 */ - pTime->tm_mon + 1, /* month (0 - 11 : 0 = January) */ - pTime->tm_mday, /* day of month (1 - 31) */ - pTime->tm_hour, /* hour (0 - 23) */ - pTime->tm_min, /* minutes (0 - 59) */ - pTime->tm_sec, /* seconds (0 - 59) */ - s_numMiniDumps - ); - - if ( FILE *fDump = fopen( chSaveFileName, "wb" ) ) - { - fwrite( pInPos, 1, iFileSize, fDump ); - fclose( fDump ); - - Warning( "\nSaved worker crash minidump '%s', size %d byte(s).\n", - chSaveFileName, iFileSize ); - } - else - { - Warning( "\nReceived worker crash minidump size %d byte(s), failed to save.\n", iFileSize ); - } - } - break; - } - } - return true; - } - - return false; -} - -CDispatchReg g_SharedDispatchReg( VMPI_SHARED_PACKET_ID, SharedDispatch ); - - - -// ----------------------------------------------------------------------------- // -// Module interfaces. -// ----------------------------------------------------------------------------- // - -void SendQDirInfo() -{ - char cPacketID[2] = { VMPI_SHARED_PACKET_ID, VMPI_SUBPACKETID_DIRECTORIES }; - - MessageBuffer mb; - mb.write( cPacketID, 2 ); - mb.write( gamedir, strlen( gamedir ) + 1 ); - mb.write( qdir, strlen( qdir ) + 1 ); - - VMPI_SendData( mb.data, mb.getLen(), VMPI_PERSISTENT ); -} - - -void RecvQDirInfo() -{ - while ( !g_bReceivedDirectoryInfo ) - VMPI_DispatchNextMessage(); -} - - -void SendDBInfo( const CDBInfo *pInfo, unsigned long jobPrimaryID ) -{ - char cPacketInfo[2] = { VMPI_SHARED_PACKET_ID, VMPI_SUBPACKETID_DBINFO }; - const void *pChunks[] = { cPacketInfo, pInfo, &jobPrimaryID }; - int chunkLengths[] = { 2, sizeof( CDBInfo ), sizeof( jobPrimaryID ) }; - - VMPI_SendChunks( pChunks, chunkLengths, ARRAYSIZE( pChunks ), VMPI_PERSISTENT ); -} - - -void RecvDBInfo( CDBInfo *pInfo, unsigned long *pJobPrimaryID ) -{ - while ( !g_bReceivedDBInfo ) - VMPI_DispatchNextMessage(); - - *pInfo = g_DBInfo; - *pJobPrimaryID = g_JobPrimaryID; -} - -// If the file is successfully opened, read and sent returns the size of the file in bytes -// otherwise returns 0 and nothing is sent -int VMPI_SendFileChunk( const void *pvChunkPrefix, int lenPrefix, tchar const *ptchFileName ) -{ - HANDLE hFile = NULL; - HANDLE hMapping = NULL; - void const *pvMappedData = NULL; - int iResult = 0; - - hFile = ::CreateFile( ptchFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); - if ( !hFile || ( hFile == INVALID_HANDLE_VALUE ) ) - goto done; - - hMapping = ::CreateFileMapping( hFile, NULL, PAGE_READONLY, 0, 0, NULL ); - if ( !hMapping || ( hMapping == INVALID_HANDLE_VALUE ) ) - goto done; - - pvMappedData = ::MapViewOfFile( hMapping, FILE_MAP_READ, 0, 0, 0 ); - if ( !pvMappedData ) - goto done; - - int iMappedFileSize = ::GetFileSize( hFile, NULL ); - if ( INVALID_FILE_SIZE == iMappedFileSize ) - goto done; - - // Send the data over VMPI - if ( VMPI_Send3Chunks( - pvChunkPrefix, lenPrefix, - &iMappedFileSize, sizeof( iMappedFileSize ), - pvMappedData, iMappedFileSize, - VMPI_MASTER_ID ) ) - iResult = iMappedFileSize; - - // Fall-through for cleanup code to execute -done: - if ( pvMappedData ) - ::UnmapViewOfFile( pvMappedData ); - - if ( hMapping && ( hMapping != INVALID_HANDLE_VALUE ) ) - ::CloseHandle( hMapping ); - - if ( hFile && ( hFile != INVALID_HANDLE_VALUE ) ) - ::CloseHandle( hFile ); - - return iResult; -} - -void VMPI_HandleCrash( const char *pMessage, void *pvExceptionInfo, bool bAssert ) -{ - static LONG crashHandlerCount = 0; - if ( InterlockedIncrement( &crashHandlerCount ) == 1 ) - { - Msg( "\nFAILURE: '%s' (assert: %d)\n", pMessage, bAssert ); - - // Send a message to the master. - char crashMsg[4] = { VMPI_SHARED_PACKET_ID, VMPI_SUBPACKETID_CRASH, 't', ':' }; - - VMPI_Send2Chunks( - crashMsg, - sizeof( crashMsg ), - pMessage, - strlen( pMessage ) + 1, - VMPI_MASTER_ID ); - - // Now attempt to create a minidump with the given exception information - if ( pvExceptionInfo ) - { - struct _EXCEPTION_POINTERS *pvExPointers = ( struct _EXCEPTION_POINTERS * ) pvExceptionInfo; - tchar tchMinidumpFileName[_MAX_PATH] = { 0 }; - bool bSucceededWritingMinidump = WriteMiniDumpUsingExceptionInfo( - pvExPointers->ExceptionRecord->ExceptionCode, - pvExPointers, - ( MINIDUMP_TYPE )( MiniDumpWithDataSegs | MiniDumpWithIndirectlyReferencedMemory | MiniDumpWithProcessThreadData ), - // ( MINIDUMP_TYPE )( MiniDumpWithDataSegs | MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithUnloadedModules | MiniDumpWithIndirectlyReferencedMemory | MiniDumpWithProcessThreadData | MiniDumpWithPrivateReadWriteMemory ), - // ( MINIDUMP_TYPE )( MiniDumpNormal ), - tchMinidumpFileName ); - if ( bSucceededWritingMinidump ) - { - crashMsg[2] = 'f'; - VMPI_SendFileChunk( crashMsg, sizeof( crashMsg ), tchMinidumpFileName ); - ::DeleteFile( tchMinidumpFileName ); - } - } - - // Let the messages go out. - Sleep( 500 ); - } - - InterlockedDecrement( &crashHandlerCount ); -} - - -// This is called if we crash inside our crash handler. It just terminates the process immediately. -LONG __stdcall VMPI_SecondExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo ) -{ - TerminateProcess( GetCurrentProcess(), 2 ); - return EXCEPTION_EXECUTE_HANDLER; // (never gets here anyway) -} - - -void VMPI_ExceptionFilter( unsigned long uCode, void *pvExceptionInfo ) -{ - // This is called if we crash inside our crash handler. It just terminates the process immediately. - SetUnhandledExceptionFilter( VMPI_SecondExceptionFilter ); - - //DWORD code = ExceptionInfo->ExceptionRecord->ExceptionCode; - - #define ERR_RECORD( name ) { name, #name } - struct - { - int code; - char *pReason; - } errors[] = - { - ERR_RECORD( EXCEPTION_ACCESS_VIOLATION ), - ERR_RECORD( EXCEPTION_ARRAY_BOUNDS_EXCEEDED ), - ERR_RECORD( EXCEPTION_BREAKPOINT ), - ERR_RECORD( EXCEPTION_DATATYPE_MISALIGNMENT ), - ERR_RECORD( EXCEPTION_FLT_DENORMAL_OPERAND ), - ERR_RECORD( EXCEPTION_FLT_DIVIDE_BY_ZERO ), - ERR_RECORD( EXCEPTION_FLT_INEXACT_RESULT ), - ERR_RECORD( EXCEPTION_FLT_INVALID_OPERATION ), - ERR_RECORD( EXCEPTION_FLT_OVERFLOW ), - ERR_RECORD( EXCEPTION_FLT_STACK_CHECK ), - ERR_RECORD( EXCEPTION_FLT_UNDERFLOW ), - ERR_RECORD( EXCEPTION_ILLEGAL_INSTRUCTION ), - ERR_RECORD( EXCEPTION_IN_PAGE_ERROR ), - ERR_RECORD( EXCEPTION_INT_DIVIDE_BY_ZERO ), - ERR_RECORD( EXCEPTION_INT_OVERFLOW ), - ERR_RECORD( EXCEPTION_INVALID_DISPOSITION ), - ERR_RECORD( EXCEPTION_NONCONTINUABLE_EXCEPTION ), - ERR_RECORD( EXCEPTION_PRIV_INSTRUCTION ), - ERR_RECORD( EXCEPTION_SINGLE_STEP ), - ERR_RECORD( EXCEPTION_STACK_OVERFLOW ), - ERR_RECORD( EXCEPTION_ACCESS_VIOLATION ), - }; - - int nErrors = sizeof( errors ) / sizeof( errors[0] ); - int i=0; - char *pchReason = NULL; - char chUnknownBuffer[32]; - for ( i; ( i < nErrors ) && !pchReason; i++ ) - { - if ( errors[i].code == uCode ) - pchReason = errors[i].pReason; - } - - if ( i == nErrors ) - { - sprintf( chUnknownBuffer, "Error code 0x%08X", uCode ); - pchReason = chUnknownBuffer; - } - - VMPI_HandleCrash( pchReason, pvExceptionInfo, true ); - - TerminateProcess( GetCurrentProcess(), 1 ); -} - - -void HandleMPIDisconnect( int procID, const char *pReason ) -{ - int nLiveWorkers = VMPI_GetCurrentNumberOfConnections() - g_nDisconnects - 1; - - // We ran into the size limit before and it wasn't readily apparent that the size limit had - // been breached, so make sure to show errors about invalid packet sizes.. - bool bOldSuppress = g_bSuppressPrintfOutput; - g_bSuppressPrintfOutput = ( Q_stristr( pReason, "invalid packet size" ) == 0 ); - - Warning( "\n\n--- WARNING: lost connection to '%s' (%s).\n", VMPI_GetMachineName( procID ), pReason ); - - if ( g_bMPIMaster ) - { - Warning( "%d workers remain.\n\n", nLiveWorkers ); - - ++g_nDisconnects; - /* - if ( VMPI_GetCurrentNumberOfConnections() - g_nDisconnects <= 1 ) - { - Error( "All machines disconnected!" ); - } - */ - } - else - { - VMPI_HandleAutoRestart(); - Error( "Worker quitting." ); - } - - g_bSuppressPrintfOutput = bOldSuppress; -} - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include +#include +#include "vmpi.h" +#include "cmdlib.h" +#include "vmpi_tools_shared.h" +#include "tier1/strtools.h" +#include "mpi_stats.h" +#include "iphelpers.h" +#include "tier0/minidump.h" + + +// ----------------------------------------------------------------------------- // +// Globals. +// ----------------------------------------------------------------------------- // + +static bool g_bReceivedDirectoryInfo = false; // Have we gotten the qdir info yet? + +static bool g_bReceivedDBInfo = false; +static CDBInfo g_DBInfo; +static unsigned long g_JobPrimaryID; + +static int g_nDisconnects = 0; // Tracks how many remote processes have disconnected ungracefully. + + +// ----------------------------------------------------------------------------- // +// Shared dispatch code. +// ----------------------------------------------------------------------------- // + +bool SharedDispatch( MessageBuffer *pBuf, int iSource, int iPacketID ) +{ + char *pInPos = &pBuf->data[2]; + + switch ( pBuf->data[1] ) + { + case VMPI_SUBPACKETID_DIRECTORIES: + { + Q_strncpy( gamedir, pInPos, sizeof( gamedir ) ); + pInPos += strlen( pInPos ) + 1; + + Q_strncpy( qdir, pInPos, sizeof( qdir ) ); + + g_bReceivedDirectoryInfo = true; + } + return true; + + case VMPI_SUBPACKETID_DBINFO: + { + g_DBInfo = *((CDBInfo*)pInPos); + pInPos += sizeof( CDBInfo ); + g_JobPrimaryID = *((unsigned long*)pInPos); + + g_bReceivedDBInfo = true; + } + return true; + + case VMPI_SUBPACKETID_CRASH: + { + char const chCrashInfoType = *pInPos; + pInPos += 2; + switch ( chCrashInfoType ) + { + case 't': + Warning( "\nWorker '%s' dead: %s\n", VMPI_GetMachineName( iSource ), pInPos ); + break; + case 'f': + { + int iFileSize = * reinterpret_cast< int const * >( pInPos ); + pInPos += sizeof( iFileSize ); + + // Temp folder + char const *szFolder = NULL; + if ( !szFolder ) szFolder = getenv( "TEMP" ); + if ( !szFolder ) szFolder = getenv( "TMP" ); + if ( !szFolder ) szFolder = "c:"; + + // Base module name + char chModuleName[_MAX_PATH], *pModuleName = chModuleName; + ::GetModuleFileName( NULL, chModuleName, sizeof( chModuleName ) / sizeof( chModuleName[0] ) ); + + if ( char *pch = strrchr( chModuleName, '.' ) ) + *pch = 0; + if ( char *pch = strrchr( chModuleName, '\\' ) ) + *pch = 0, pModuleName = pch + 1; + + // Current time + time_t currTime = ::time( NULL ); + struct tm * pTime = ::localtime( &currTime ); + + // Number of minidumps this run + static int s_numMiniDumps = 0; + ++ s_numMiniDumps; + + // Prepare the filename + char chSaveFileName[ 2 * _MAX_PATH ] = { 0 }; + sprintf( chSaveFileName, "%s\\vmpi_%s_on_%s_%d%.2d%2d%.2d%.2d%.2d_%d.mdmp", + szFolder, + pModuleName, + VMPI_GetMachineName( iSource ), + pTime->tm_year + 1900, /* Year less 2000 */ + pTime->tm_mon + 1, /* month (0 - 11 : 0 = January) */ + pTime->tm_mday, /* day of month (1 - 31) */ + pTime->tm_hour, /* hour (0 - 23) */ + pTime->tm_min, /* minutes (0 - 59) */ + pTime->tm_sec, /* seconds (0 - 59) */ + s_numMiniDumps + ); + + if ( FILE *fDump = fopen( chSaveFileName, "wb" ) ) + { + fwrite( pInPos, 1, iFileSize, fDump ); + fclose( fDump ); + + Warning( "\nSaved worker crash minidump '%s', size %d byte(s).\n", + chSaveFileName, iFileSize ); + } + else + { + Warning( "\nReceived worker crash minidump size %d byte(s), failed to save.\n", iFileSize ); + } + } + break; + } + } + return true; + } + + return false; +} + +CDispatchReg g_SharedDispatchReg( VMPI_SHARED_PACKET_ID, SharedDispatch ); + + + +// ----------------------------------------------------------------------------- // +// Module interfaces. +// ----------------------------------------------------------------------------- // + +void SendQDirInfo() +{ + char cPacketID[2] = { VMPI_SHARED_PACKET_ID, VMPI_SUBPACKETID_DIRECTORIES }; + + MessageBuffer mb; + mb.write( cPacketID, 2 ); + mb.write( gamedir, strlen( gamedir ) + 1 ); + mb.write( qdir, strlen( qdir ) + 1 ); + + VMPI_SendData( mb.data, mb.getLen(), VMPI_PERSISTENT ); +} + + +void RecvQDirInfo() +{ + while ( !g_bReceivedDirectoryInfo ) + VMPI_DispatchNextMessage(); +} + + +void SendDBInfo( const CDBInfo *pInfo, unsigned long jobPrimaryID ) +{ + char cPacketInfo[2] = { VMPI_SHARED_PACKET_ID, VMPI_SUBPACKETID_DBINFO }; + const void *pChunks[] = { cPacketInfo, pInfo, &jobPrimaryID }; + int chunkLengths[] = { 2, sizeof( CDBInfo ), sizeof( jobPrimaryID ) }; + + VMPI_SendChunks( pChunks, chunkLengths, ARRAYSIZE( pChunks ), VMPI_PERSISTENT ); +} + + +void RecvDBInfo( CDBInfo *pInfo, unsigned long *pJobPrimaryID ) +{ + while ( !g_bReceivedDBInfo ) + VMPI_DispatchNextMessage(); + + *pInfo = g_DBInfo; + *pJobPrimaryID = g_JobPrimaryID; +} + +// If the file is successfully opened, read and sent returns the size of the file in bytes +// otherwise returns 0 and nothing is sent +int VMPI_SendFileChunk( const void *pvChunkPrefix, int lenPrefix, tchar const *ptchFileName ) +{ + HANDLE hFile = NULL; + HANDLE hMapping = NULL; + void const *pvMappedData = NULL; + int iResult = 0; + + hFile = ::CreateFile( ptchFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); + if ( !hFile || ( hFile == INVALID_HANDLE_VALUE ) ) + goto done; + + hMapping = ::CreateFileMapping( hFile, NULL, PAGE_READONLY, 0, 0, NULL ); + if ( !hMapping || ( hMapping == INVALID_HANDLE_VALUE ) ) + goto done; + + pvMappedData = ::MapViewOfFile( hMapping, FILE_MAP_READ, 0, 0, 0 ); + if ( !pvMappedData ) + goto done; + + int iMappedFileSize = ::GetFileSize( hFile, NULL ); + if ( INVALID_FILE_SIZE == iMappedFileSize ) + goto done; + + // Send the data over VMPI + if ( VMPI_Send3Chunks( + pvChunkPrefix, lenPrefix, + &iMappedFileSize, sizeof( iMappedFileSize ), + pvMappedData, iMappedFileSize, + VMPI_MASTER_ID ) ) + iResult = iMappedFileSize; + + // Fall-through for cleanup code to execute +done: + if ( pvMappedData ) + ::UnmapViewOfFile( pvMappedData ); + + if ( hMapping && ( hMapping != INVALID_HANDLE_VALUE ) ) + ::CloseHandle( hMapping ); + + if ( hFile && ( hFile != INVALID_HANDLE_VALUE ) ) + ::CloseHandle( hFile ); + + return iResult; +} + +void VMPI_HandleCrash( const char *pMessage, void *pvExceptionInfo, bool bAssert ) +{ + static LONG crashHandlerCount = 0; + if ( InterlockedIncrement( &crashHandlerCount ) == 1 ) + { + Msg( "\nFAILURE: '%s' (assert: %d)\n", pMessage, bAssert ); + + // Send a message to the master. + char crashMsg[4] = { VMPI_SHARED_PACKET_ID, VMPI_SUBPACKETID_CRASH, 't', ':' }; + + VMPI_Send2Chunks( + crashMsg, + sizeof( crashMsg ), + pMessage, + strlen( pMessage ) + 1, + VMPI_MASTER_ID ); + + // Now attempt to create a minidump with the given exception information + if ( pvExceptionInfo ) + { + struct _EXCEPTION_POINTERS *pvExPointers = ( struct _EXCEPTION_POINTERS * ) pvExceptionInfo; + tchar tchMinidumpFileName[_MAX_PATH] = { 0 }; + bool bSucceededWritingMinidump = WriteMiniDumpUsingExceptionInfo( + pvExPointers->ExceptionRecord->ExceptionCode, + pvExPointers, + ( MINIDUMP_TYPE )( MiniDumpWithDataSegs | MiniDumpWithIndirectlyReferencedMemory | MiniDumpWithProcessThreadData ), + // ( MINIDUMP_TYPE )( MiniDumpWithDataSegs | MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithUnloadedModules | MiniDumpWithIndirectlyReferencedMemory | MiniDumpWithProcessThreadData | MiniDumpWithPrivateReadWriteMemory ), + // ( MINIDUMP_TYPE )( MiniDumpNormal ), + tchMinidumpFileName ); + if ( bSucceededWritingMinidump ) + { + crashMsg[2] = 'f'; + VMPI_SendFileChunk( crashMsg, sizeof( crashMsg ), tchMinidumpFileName ); + ::DeleteFile( tchMinidumpFileName ); + } + } + + // Let the messages go out. + Sleep( 500 ); + } + + InterlockedDecrement( &crashHandlerCount ); +} + + +// This is called if we crash inside our crash handler. It just terminates the process immediately. +LONG __stdcall VMPI_SecondExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo ) +{ + TerminateProcess( GetCurrentProcess(), 2 ); + return EXCEPTION_EXECUTE_HANDLER; // (never gets here anyway) +} + + +void VMPI_ExceptionFilter( unsigned long uCode, void *pvExceptionInfo ) +{ + // This is called if we crash inside our crash handler. It just terminates the process immediately. + SetUnhandledExceptionFilter( VMPI_SecondExceptionFilter ); + + //DWORD code = ExceptionInfo->ExceptionRecord->ExceptionCode; + + #define ERR_RECORD( name ) { name, #name } + struct + { + int code; + char *pReason; + } errors[] = + { + ERR_RECORD( EXCEPTION_ACCESS_VIOLATION ), + ERR_RECORD( EXCEPTION_ARRAY_BOUNDS_EXCEEDED ), + ERR_RECORD( EXCEPTION_BREAKPOINT ), + ERR_RECORD( EXCEPTION_DATATYPE_MISALIGNMENT ), + ERR_RECORD( EXCEPTION_FLT_DENORMAL_OPERAND ), + ERR_RECORD( EXCEPTION_FLT_DIVIDE_BY_ZERO ), + ERR_RECORD( EXCEPTION_FLT_INEXACT_RESULT ), + ERR_RECORD( EXCEPTION_FLT_INVALID_OPERATION ), + ERR_RECORD( EXCEPTION_FLT_OVERFLOW ), + ERR_RECORD( EXCEPTION_FLT_STACK_CHECK ), + ERR_RECORD( EXCEPTION_FLT_UNDERFLOW ), + ERR_RECORD( EXCEPTION_ILLEGAL_INSTRUCTION ), + ERR_RECORD( EXCEPTION_IN_PAGE_ERROR ), + ERR_RECORD( EXCEPTION_INT_DIVIDE_BY_ZERO ), + ERR_RECORD( EXCEPTION_INT_OVERFLOW ), + ERR_RECORD( EXCEPTION_INVALID_DISPOSITION ), + ERR_RECORD( EXCEPTION_NONCONTINUABLE_EXCEPTION ), + ERR_RECORD( EXCEPTION_PRIV_INSTRUCTION ), + ERR_RECORD( EXCEPTION_SINGLE_STEP ), + ERR_RECORD( EXCEPTION_STACK_OVERFLOW ), + ERR_RECORD( EXCEPTION_ACCESS_VIOLATION ), + }; + + int nErrors = sizeof( errors ) / sizeof( errors[0] ); + int i=0; + char *pchReason = NULL; + char chUnknownBuffer[32]; + for ( i; ( i < nErrors ) && !pchReason; i++ ) + { + if ( errors[i].code == uCode ) + pchReason = errors[i].pReason; + } + + if ( i == nErrors ) + { + sprintf( chUnknownBuffer, "Error code 0x%08X", uCode ); + pchReason = chUnknownBuffer; + } + + VMPI_HandleCrash( pchReason, pvExceptionInfo, true ); + + TerminateProcess( GetCurrentProcess(), 1 ); +} + + +void HandleMPIDisconnect( int procID, const char *pReason ) +{ + int nLiveWorkers = VMPI_GetCurrentNumberOfConnections() - g_nDisconnects - 1; + + // We ran into the size limit before and it wasn't readily apparent that the size limit had + // been breached, so make sure to show errors about invalid packet sizes.. + bool bOldSuppress = g_bSuppressPrintfOutput; + g_bSuppressPrintfOutput = ( Q_stristr( pReason, "invalid packet size" ) == 0 ); + + Warning( "\n\n--- WARNING: lost connection to '%s' (%s).\n", VMPI_GetMachineName( procID ), pReason ); + + if ( g_bMPIMaster ) + { + Warning( "%d workers remain.\n\n", nLiveWorkers ); + + ++g_nDisconnects; + /* + if ( VMPI_GetCurrentNumberOfConnections() - g_nDisconnects <= 1 ) + { + Error( "All machines disconnected!" ); + } + */ + } + else + { + VMPI_HandleAutoRestart(); + Error( "Worker quitting." ); + } + + g_bSuppressPrintfOutput = bOldSuppress; +} + + diff --git a/mp/src/utils/common/vmpi_tools_shared.h b/mp/src/utils/common/vmpi_tools_shared.h index 7c22201f..980552e8 100644 --- a/mp/src/utils/common/vmpi_tools_shared.h +++ b/mp/src/utils/common/vmpi_tools_shared.h @@ -1,45 +1,45 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef VMPI_TOOLS_SHARED_H -#define VMPI_TOOLS_SHARED_H -#ifdef _WIN32 -#pragma once -#endif - - -// Packet IDs. - #define VMPI_SUBPACKETID_DIRECTORIES 0 // qdir directories. - #define VMPI_SUBPACKETID_DBINFO 1 // MySQL database info. - #define VMPI_SUBPACKETID_CRASH 3 // A worker saying it crashed. - #define VMPI_SUBPACKETID_MULTICAST_ADDR 4 // Filesystem multicast address. - - -class CDBInfo; -class CIPAddr; - - -// Send/receive the qdir info. -void SendQDirInfo(); -void RecvQDirInfo(); - -void SendDBInfo( const CDBInfo *pInfo, unsigned long jobPrimaryID ); -void RecvDBInfo( CDBInfo *pInfo, unsigned long *pJobPrimaryID ); - -void SendMulticastIP( const CIPAddr *pAddr ); -void RecvMulticastIP( CIPAddr *pAddr ); - -void VMPI_HandleCrash( const char *pMessage, void *pvExceptionInfo, bool bAssert ); - -// Call this from an exception handler (set by SetUnhandledExceptionHandler). -// uCode = ExceptionInfo->ExceptionRecord->ExceptionCode. -// pvExceptionInfo = ExceptionInfo -void VMPI_ExceptionFilter( unsigned long uCode, void *pvExceptionInfo ); - -void HandleMPIDisconnect( int procID, const char *pReason ); - - -#endif // VMPI_TOOLS_SHARED_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef VMPI_TOOLS_SHARED_H +#define VMPI_TOOLS_SHARED_H +#ifdef _WIN32 +#pragma once +#endif + + +// Packet IDs. + #define VMPI_SUBPACKETID_DIRECTORIES 0 // qdir directories. + #define VMPI_SUBPACKETID_DBINFO 1 // MySQL database info. + #define VMPI_SUBPACKETID_CRASH 3 // A worker saying it crashed. + #define VMPI_SUBPACKETID_MULTICAST_ADDR 4 // Filesystem multicast address. + + +class CDBInfo; +class CIPAddr; + + +// Send/receive the qdir info. +void SendQDirInfo(); +void RecvQDirInfo(); + +void SendDBInfo( const CDBInfo *pInfo, unsigned long jobPrimaryID ); +void RecvDBInfo( CDBInfo *pInfo, unsigned long *pJobPrimaryID ); + +void SendMulticastIP( const CIPAddr *pAddr ); +void RecvMulticastIP( CIPAddr *pAddr ); + +void VMPI_HandleCrash( const char *pMessage, void *pvExceptionInfo, bool bAssert ); + +// Call this from an exception handler (set by SetUnhandledExceptionHandler). +// uCode = ExceptionInfo->ExceptionRecord->ExceptionCode. +// pvExceptionInfo = ExceptionInfo +void VMPI_ExceptionFilter( unsigned long uCode, void *pvExceptionInfo ); + +void HandleMPIDisconnect( int procID, const char *pReason ); + + +#endif // VMPI_TOOLS_SHARED_H diff --git a/mp/src/utils/common/wadlib.c b/mp/src/utils/common/wadlib.c index 2b5bb6b1..4aff972d 100644 --- a/mp/src/utils/common/wadlib.c +++ b/mp/src/utils/common/wadlib.c @@ -1,334 +1,334 @@ -//========= Copyright © 1996-2005, Valve LLC, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// wad2lib.c - -#include -#include -#include -#include -#include -#include -#include -//#include -#include - -#ifdef NeXT -#include -#endif -#include "cmdlib.h" -#include "wadlib.h" -#include "commonmacros.h" - -/* -============================================================================ - - WAD READING - -============================================================================ -*/ - - -lumpinfo_t *lumpinfo; // location of each lump on disk -int numlumps; - -wadinfo_t header; -FILE *wadhandle; - - -/* -==================== -W_OpenWad -==================== -*/ -void W_OpenWad (char *filename) -{ - lumpinfo_t *lump_p; - unsigned i; - int length; - -// -// open the file and add to directory -// - wadhandle = SafeOpenRead (filename); - SafeRead (wadhandle, &header, sizeof(header)); - - if (!STRING_MATCHES_ID(header.identification,WAD_ID)) - Error ("Wad file %s doesn't have %s identifier\n",filename, WAD_IDNAME); - - header.numlumps = LittleLong(header.numlumps); - header.infotableofs = LittleLong(header.infotableofs); - - numlumps = header.numlumps; - - length = numlumps*sizeof(lumpinfo_t); - lumpinfo = malloc (length); - lump_p = lumpinfo; - - fseek (wadhandle, header.infotableofs, SEEK_SET); - SafeRead (wadhandle, lumpinfo, length); - -// -// Fill in lumpinfo -// - - for (i=0 ; ifilepos = LittleLong(lump_p->filepos); - lump_p->size = LittleLong(lump_p->size); - } -} - - - -void CleanupName (char *in, char *out) -{ - int i; - - for (i=0 ; iname ) ; i++ ) - { - if (!in[i]) - break; - - out[i] = toupper(in[i]); - } - - for ( ; iname ); i++ ) - out[i] = 0; -} - - -/* -==================== -W_CheckNumForName - -Returns -1 if name not found -==================== -*/ -int W_CheckNumForName (char *name) -{ - char cleanname[TEXTURE_NAME_LENGTH]; - int v1,v2, v3, v4; - int i; - lumpinfo_t *lump_p; - - CleanupName (name, cleanname); - -// make the name into four integers for easy compares - - v1 = *(int *)cleanname; - v2 = *(int *)&cleanname[4]; - v3 = *(int *)&cleanname[8]; - v4 = *(int *)&cleanname[12]; - -// find it - - lump_p = lumpinfo; - for (i=0 ; iname == v1 - && *(int *)&lump_p->name[4] == v2 - && *(int *)&lump_p->name[8] == v3 - && *(int *)&lump_p->name[12] == v4 - && !strcmp( lump_p->name, cleanname ) ) - return i; - } - - return -1; -} - - -/* -==================== -W_GetNumForName - -Calls W_CheckNumForName, but bombs out if not found -==================== -*/ -int W_GetNumForName (char *name) -{ - int i; - - i = W_CheckNumForName (name); - if (i != -1) - return i; - - Error ("W_GetNumForName: %s not found!",name); - return -1; -} - - -/* -==================== -W_LumpLength - -Returns the buffer size needed to load the given lump -==================== -*/ -int W_LumpLength (int lump) -{ - if (lump >= numlumps) - Error ("W_LumpLength: %i >= numlumps",lump); - return lumpinfo[lump].size; -} - - -/* -==================== -W_ReadLumpNum - -Loads the lump into the given buffer, which must be >= W_LumpLength() -==================== -*/ -void W_ReadLumpNum (int lump, void *dest) -{ - lumpinfo_t *l; - - if (lump >= numlumps) - Error ("W_ReadLump: %i >= numlumps",lump); - l = lumpinfo+lump; - - fseek (wadhandle, l->filepos, SEEK_SET); - SafeRead (wadhandle, dest, l->size); -} - - - -/* -==================== -W_LoadLumpNum -==================== -*/ -void *W_LoadLumpNum (int lump) -{ - void *buf; - - if ((unsigned)lump >= numlumps) - Error ("W_CacheLumpNum: %i >= numlumps",lump); - - buf = malloc (W_LumpLength (lump)); - W_ReadLumpNum (lump, buf); - - return buf; -} - - -/* -==================== -W_LoadLumpName -==================== -*/ -void *W_LoadLumpName (char *name) -{ - return W_LoadLumpNum (W_GetNumForName(name)); -} - - -/* -=============================================================================== - - WAD CREATION - -=============================================================================== -*/ - -FILE *outwad; - -lumpinfo_t outinfo[4096]; -int outlumps; - -short (*wadshort) (short l); -int (*wadlong) (int l); - -/* -=============== -NewWad -=============== -*/ - -void NewWad (char *pathname, qboolean bigendien) -{ - outwad = SafeOpenWrite (pathname); - fseek (outwad, sizeof(wadinfo_t), SEEK_SET); - memset (outinfo, 0, sizeof(outinfo)); - - if (bigendien) - { - wadshort = BigShort; - wadlong = BigLong; - } - else - { - wadshort = LittleShort; - wadlong = LittleLong; - } - - outlumps = 0; -} - - -/* -=============== -AddLump -=============== -*/ - -void AddLump (char *name, void *buffer, int length, int type, int compress) -{ - lumpinfo_t *info; - int ofs; - - info = &outinfo[outlumps]; - outlumps++; - - memset (info,0,sizeof(info)); - - strcpy (info->name, name); - Q_strupr (info->name); - - ofs = ftell(outwad); - info->filepos = wadlong(ofs); - info->size = info->disksize = wadlong(length); - info->type = type; - info->compression = compress; - -// FIXME: do compression - - SafeWrite (outwad, buffer, length); -} - - -/* -=============== -WriteWad -=============== -*/ - -void WriteWad (int wad3) -{ - wadinfo_t header; - int ofs; - -// write the lumpingo - ofs = ftell(outwad); - - SafeWrite (outwad, outinfo, outlumps*sizeof(lumpinfo_t) ); - -// write the header - -// a program will be able to tell the ednieness of a wad by the id - ID_TO_STRING( WAD_ID, header.identification ); - - header.numlumps = wadlong(outlumps); - header.infotableofs = wadlong(ofs); - - fseek (outwad, 0, SEEK_SET); - SafeWrite (outwad, &header, sizeof(header)); - fclose (outwad); -} - - +//========= Copyright © 1996-2005, Valve LLC, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// wad2lib.c + +#include +#include +#include +#include +#include +#include +#include +//#include +#include + +#ifdef NeXT +#include +#endif +#include "cmdlib.h" +#include "wadlib.h" +#include "commonmacros.h" + +/* +============================================================================ + + WAD READING + +============================================================================ +*/ + + +lumpinfo_t *lumpinfo; // location of each lump on disk +int numlumps; + +wadinfo_t header; +FILE *wadhandle; + + +/* +==================== +W_OpenWad +==================== +*/ +void W_OpenWad (char *filename) +{ + lumpinfo_t *lump_p; + unsigned i; + int length; + +// +// open the file and add to directory +// + wadhandle = SafeOpenRead (filename); + SafeRead (wadhandle, &header, sizeof(header)); + + if (!STRING_MATCHES_ID(header.identification,WAD_ID)) + Error ("Wad file %s doesn't have %s identifier\n",filename, WAD_IDNAME); + + header.numlumps = LittleLong(header.numlumps); + header.infotableofs = LittleLong(header.infotableofs); + + numlumps = header.numlumps; + + length = numlumps*sizeof(lumpinfo_t); + lumpinfo = malloc (length); + lump_p = lumpinfo; + + fseek (wadhandle, header.infotableofs, SEEK_SET); + SafeRead (wadhandle, lumpinfo, length); + +// +// Fill in lumpinfo +// + + for (i=0 ; ifilepos = LittleLong(lump_p->filepos); + lump_p->size = LittleLong(lump_p->size); + } +} + + + +void CleanupName (char *in, char *out) +{ + int i; + + for (i=0 ; iname ) ; i++ ) + { + if (!in[i]) + break; + + out[i] = toupper(in[i]); + } + + for ( ; iname ); i++ ) + out[i] = 0; +} + + +/* +==================== +W_CheckNumForName + +Returns -1 if name not found +==================== +*/ +int W_CheckNumForName (char *name) +{ + char cleanname[TEXTURE_NAME_LENGTH]; + int v1,v2, v3, v4; + int i; + lumpinfo_t *lump_p; + + CleanupName (name, cleanname); + +// make the name into four integers for easy compares + + v1 = *(int *)cleanname; + v2 = *(int *)&cleanname[4]; + v3 = *(int *)&cleanname[8]; + v4 = *(int *)&cleanname[12]; + +// find it + + lump_p = lumpinfo; + for (i=0 ; iname == v1 + && *(int *)&lump_p->name[4] == v2 + && *(int *)&lump_p->name[8] == v3 + && *(int *)&lump_p->name[12] == v4 + && !strcmp( lump_p->name, cleanname ) ) + return i; + } + + return -1; +} + + +/* +==================== +W_GetNumForName + +Calls W_CheckNumForName, but bombs out if not found +==================== +*/ +int W_GetNumForName (char *name) +{ + int i; + + i = W_CheckNumForName (name); + if (i != -1) + return i; + + Error ("W_GetNumForName: %s not found!",name); + return -1; +} + + +/* +==================== +W_LumpLength + +Returns the buffer size needed to load the given lump +==================== +*/ +int W_LumpLength (int lump) +{ + if (lump >= numlumps) + Error ("W_LumpLength: %i >= numlumps",lump); + return lumpinfo[lump].size; +} + + +/* +==================== +W_ReadLumpNum + +Loads the lump into the given buffer, which must be >= W_LumpLength() +==================== +*/ +void W_ReadLumpNum (int lump, void *dest) +{ + lumpinfo_t *l; + + if (lump >= numlumps) + Error ("W_ReadLump: %i >= numlumps",lump); + l = lumpinfo+lump; + + fseek (wadhandle, l->filepos, SEEK_SET); + SafeRead (wadhandle, dest, l->size); +} + + + +/* +==================== +W_LoadLumpNum +==================== +*/ +void *W_LoadLumpNum (int lump) +{ + void *buf; + + if ((unsigned)lump >= numlumps) + Error ("W_CacheLumpNum: %i >= numlumps",lump); + + buf = malloc (W_LumpLength (lump)); + W_ReadLumpNum (lump, buf); + + return buf; +} + + +/* +==================== +W_LoadLumpName +==================== +*/ +void *W_LoadLumpName (char *name) +{ + return W_LoadLumpNum (W_GetNumForName(name)); +} + + +/* +=============================================================================== + + WAD CREATION + +=============================================================================== +*/ + +FILE *outwad; + +lumpinfo_t outinfo[4096]; +int outlumps; + +short (*wadshort) (short l); +int (*wadlong) (int l); + +/* +=============== +NewWad +=============== +*/ + +void NewWad (char *pathname, qboolean bigendien) +{ + outwad = SafeOpenWrite (pathname); + fseek (outwad, sizeof(wadinfo_t), SEEK_SET); + memset (outinfo, 0, sizeof(outinfo)); + + if (bigendien) + { + wadshort = BigShort; + wadlong = BigLong; + } + else + { + wadshort = LittleShort; + wadlong = LittleLong; + } + + outlumps = 0; +} + + +/* +=============== +AddLump +=============== +*/ + +void AddLump (char *name, void *buffer, int length, int type, int compress) +{ + lumpinfo_t *info; + int ofs; + + info = &outinfo[outlumps]; + outlumps++; + + memset (info,0,sizeof(info)); + + strcpy (info->name, name); + Q_strupr (info->name); + + ofs = ftell(outwad); + info->filepos = wadlong(ofs); + info->size = info->disksize = wadlong(length); + info->type = type; + info->compression = compress; + +// FIXME: do compression + + SafeWrite (outwad, buffer, length); +} + + +/* +=============== +WriteWad +=============== +*/ + +void WriteWad (int wad3) +{ + wadinfo_t header; + int ofs; + +// write the lumpingo + ofs = ftell(outwad); + + SafeWrite (outwad, outinfo, outlumps*sizeof(lumpinfo_t) ); + +// write the header + +// a program will be able to tell the ednieness of a wad by the id + ID_TO_STRING( WAD_ID, header.identification ); + + header.numlumps = wadlong(outlumps); + header.infotableofs = wadlong(ofs); + + fseek (outwad, 0, SEEK_SET); + SafeWrite (outwad, &header, sizeof(header)); + fclose (outwad); +} + + diff --git a/mp/src/utils/common/wadlib.h b/mp/src/utils/common/wadlib.h index a8e4e09a..0fa12f50 100644 --- a/mp/src/utils/common/wadlib.h +++ b/mp/src/utils/common/wadlib.h @@ -1,46 +1,46 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -// wadlib.h - -// -// wad reading -// - -#define CMP_NONE 0 -#define CMP_LZSS 1 - -#define TYP_NONE 0 -#define TYP_LABEL 1 -#define TYP_LUMPY 64 // 64 + grab command number - -#ifndef WADTYPES_H -#include "wadtypes.h" -#endif - -extern lumpinfo_t *lumpinfo; // location of each lump on disk -extern int numlumps; -extern wadinfo_t header; - -void W_OpenWad (char *filename); -int W_CheckNumForName (char *name); -int W_GetNumForName (char *name); -int W_LumpLength (int lump); -void W_ReadLumpNum (int lump, void *dest); -void *W_LoadLumpNum (int lump); -void *W_LoadLumpName (char *name); - -void CleanupName (char *in, char *out); - -// -// wad creation -// -void NewWad (char *pathname, qboolean bigendien); -void AddLump (char *name, void *buffer, int length, int type, int compress); -void WriteWad (int wad3); - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +// wadlib.h + +// +// wad reading +// + +#define CMP_NONE 0 +#define CMP_LZSS 1 + +#define TYP_NONE 0 +#define TYP_LABEL 1 +#define TYP_LUMPY 64 // 64 + grab command number + +#ifndef WADTYPES_H +#include "wadtypes.h" +#endif + +extern lumpinfo_t *lumpinfo; // location of each lump on disk +extern int numlumps; +extern wadinfo_t header; + +void W_OpenWad (char *filename); +int W_CheckNumForName (char *name); +int W_GetNumForName (char *name); +int W_LumpLength (int lump); +void W_ReadLumpNum (int lump, void *dest); +void *W_LoadLumpNum (int lump); +void *W_LoadLumpName (char *name); + +void CleanupName (char *in, char *out); + +// +// wad creation +// +void NewWad (char *pathname, qboolean bigendien); +void AddLump (char *name, void *buffer, int length, int type, int compress); +void WriteWad (int wad3); + diff --git a/mp/src/utils/glview/glos.h b/mp/src/utils/glview/glos.h index bc36bb1a..ebaee16e 100644 --- a/mp/src/utils/glview/glos.h +++ b/mp/src/utils/glview/glos.h @@ -1,21 +1,21 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// GLOS.H -// -// This is an OS specific header file - -#include - -// disable data conversion warnings - -#pragma warning(disable : 4244) // MIPS -#pragma warning(disable : 4136) // X86 -#pragma warning(disable : 4051) // ALPHA - - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// GLOS.H +// +// This is an OS specific header file + +#include + +// disable data conversion warnings + +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + + + diff --git a/mp/src/utils/glview/glview.cpp b/mp/src/utils/glview/glview.cpp index d60a6556..d7da07b3 100644 --- a/mp/src/utils/glview/glview.cpp +++ b/mp/src/utils/glview/glview.cpp @@ -1,1427 +1,1427 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#include "glos.h" -#include -#if _MSC_VER < 1600 -#include -#endif -#include -#include -#include -#include -#include -#include "cmdlib.h" -#include "mathlib/mathlib.h" -#include "cmodel.h" -#include "tier1/strtools.h" -#include "physdll.h" -#include "phyfile.h" -#include "vphysics_interface.h" -#include "tier0/icommandline.h" -#include "tier0/vprof.h" - -HDC camdc; -HGLRC baseRC; -HWND camerawindow; -HANDLE main_instance; - -/* YWB: 3/13/98 - You run the program like normal with any file. If you want to read portals for the - file type, you type: glview -portal filename.gl0 (or whatever). glview will then - try to read in the .prt file filename.prt. - - The portals are shown as white lines superimposed over your image. You can toggle the - view between showing portals or not by hitting the '2' key. The '1' key toggles - world polygons. - - The 'b' key toggles blending modes. - - If you don't want to depth buffer the portals, hit 'p'. - - The command line parsing is inelegant but functional. - - I sped up the KB movement and turn speed, too. - */ - -// Vars added by YWB -Vector g_Center; // Center of all read points, so camera is in a sensible place -int g_nTotalPoints = 0; // Total points read, for calculating center -int g_UseBlending = 0; // Toggle to use blending mode or not -BOOL g_bReadPortals = 0; // Did we read in a portal file? -BOOL g_bNoDepthPortals = 0; // Do we zbuffer the lines of the portals? -int g_nPortalHighlight = -1; // The leaf we're viewing -int g_nLeafHighlight = -1; // The leaf we're viewing -BOOL g_bShowList1 = 1; // Show regular polygons? -BOOL g_bShowList2 = 1; // Show portals? -BOOL g_bShowLines = 0; // Show outlines of faces -BOOL g_Active = TRUE; -BOOL g_Update = TRUE; -BOOL g_bDisp = FALSE; -IPhysicsCollision *physcollision = NULL; -// ----------- -static int g_Keys[255]; -void AppKeyDown( int key ); -void AppKeyUp( int key ); - - -BOOL ReadDisplacementFile( const char *filename ); -void DrawDisplacementData( void ); - -#define BENCHMARK_PHY 0 - -/* -================= -Error - -For abnormal program terminations -================= -*/ -void Error (char *error, ...) -{ - va_list argptr; - char text[1024]; - - va_start (argptr,error); - vsprintf (text, error,argptr); - va_end (argptr); - - MessageBox(NULL, text, "Error", 0 /* MB_OK */ ); - - exit (1); -} - -float origin[3] = {32, 32, 48}; -float angles[3]; -float forward[3], right[3], vup[3], vpn[3], vright[3]; -float width = 1024; -float height = 768; - -float g_flMovementSpeed = 320.f; // Units / second (run speed of HL) -#define SPEED_TURN 90 // Degrees / second - -#define VK_COMMA 188 -#define VK_PERIOD 190 - - -void KeyDown (int key) -{ - switch (key) - { - case VK_ESCAPE: - g_Active = FALSE; - break; - - case VK_F1: - glEnable (GL_CULL_FACE); - glCullFace (GL_FRONT); - break; - case 'B': - g_UseBlending ^= 1; - if (g_UseBlending) - glEnable(GL_BLEND);// YWB TESTING - else - glDisable(GL_BLEND); - break; - - case '1': - g_bShowList1 ^= 1; - break; - case '2': - g_bShowList2 ^= 1; - break; - case 'P': - g_bNoDepthPortals ^= 1; - break; - case 'L': - g_bShowLines ^= 1; - break; - } - g_Update = TRUE; -} - -static BOOL g_Capture = FALSE; - -#define MOUSE_SENSITIVITY 0.2f -#define MOUSE_SENSITIVITY_X (MOUSE_SENSITIVITY*1) -#define MOUSE_SENSITIVITY_Y (MOUSE_SENSITIVITY*1) - -void Cam_MouseMoved( void ) -{ - if ( g_Capture ) - { - RECT rect; - int centerx, centery; - float deltax, deltay; - POINT cursorPoint; - - GetWindowRect( camerawindow, &rect ); - - if ( rect.top < 0) - rect.top = 0; - if ( rect.left < 0) - rect.left = 0; - - centerx = ( rect.left + rect.right ) / 2; - centery = ( rect.top + rect.bottom ) / 2; - - GetCursorPos( &cursorPoint ); - SetCursorPos( centerx, centery ); - - deltax = (cursorPoint.x - centerx) * MOUSE_SENSITIVITY_X; - deltay = (cursorPoint.y - centery) * MOUSE_SENSITIVITY_Y; - - angles[1] -= deltax; - angles[0] -= deltay; - - g_Update = TRUE; - } -} - -int Test_Key( int key ) -{ - int r = (g_Keys[ key ] != 0); - - g_Keys[ key ] &= 0x01; // clear out debounce bit - - if (r) - g_Update = TRUE; - - return r; -} - -// UNDONE: Probably should change the controls to match the game - but I don't know who relies on them -// as of now. -void Cam_Update( float frametime ) -{ - if ( Test_Key( 'W' ) ) - { - VectorMA (origin, g_flMovementSpeed*frametime, vpn, origin); - } - if ( Test_Key( 'S' ) ) - { - VectorMA (origin, -g_flMovementSpeed*frametime, vpn, origin); - } - if ( Test_Key( 'A' ) ) - { - VectorMA (origin, -g_flMovementSpeed*frametime, vright, origin); - } - if ( Test_Key( 'D' ) ) - { - VectorMA (origin, g_flMovementSpeed*frametime, vright, origin); - } - - if ( Test_Key( VK_UP ) ) - { - VectorMA (origin, g_flMovementSpeed*frametime, forward, origin); - } - if ( Test_Key( VK_DOWN ) ) - { - VectorMA (origin, -g_flMovementSpeed*frametime, forward, origin); - } - - if ( Test_Key( VK_LEFT ) ) - { - angles[1] += SPEED_TURN * frametime; - } - if ( Test_Key( VK_RIGHT ) ) - { - angles[1] -= SPEED_TURN * frametime; - } - if ( Test_Key( 'F' ) ) - { - origin[2] += g_flMovementSpeed*frametime; - } - if ( Test_Key( 'C' ) ) - { - origin[2] -= g_flMovementSpeed*frametime; - } - if ( Test_Key( VK_INSERT ) ) - { - angles[0] += SPEED_TURN * frametime; - if (angles[0] > 85) - angles[0] = 85; - } - if ( Test_Key( VK_DELETE ) ) - { - angles[0] -= SPEED_TURN * frametime; - if (angles[0] < -85) - angles[0] = -85; - } - Cam_MouseMoved(); -} - -void Cam_BuildMatrix (void) -{ - float xa, ya; - float matrix[4][4]; - int i; - - xa = angles[0]/180*M_PI; - ya = angles[1]/180*M_PI; - - // the movement matrix is kept 2d ?? do we want this? - - forward[0] = cos(ya); - forward[1] = sin(ya); - right[0] = forward[1]; - right[1] = -forward[0]; - - glGetFloatv (GL_PROJECTION_MATRIX, &matrix[0][0]); - - for (i=0 ; i<3 ; i++) - { - vright[i] = matrix[i][0]; - vup[i] = matrix[i][1]; - vpn[i] = matrix[i][2]; - } - - VectorNormalize (vright); - VectorNormalize (vup); - VectorNormalize (vpn); -} - -void Draw (void) -{ - float screenaspect; - float yfov; - - //glClearColor (0.5, 0.5, 0.5, 0); - glClearColor(0.0, 0.0, 0.0, 0); // Black Clearing YWB - glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - // - // set up viewpoint - // - glMatrixMode(GL_PROJECTION); - glLoadIdentity (); - - screenaspect = (float)width/height; - yfov = 2*atan((float)height/width)*180/M_PI; - gluPerspective (yfov, screenaspect, 6, 20000); - - glRotatef (-90, 1, 0, 0); // put Z going up - glRotatef (90, 0, 0, 1); // put Z going up - glRotatef (angles[0], 0, 1, 0); - glRotatef (-angles[1], 0, 0, 1); - glTranslatef (-origin[0], -origin[1], -origin[2]); - - Cam_BuildMatrix (); - - // - // set drawing parms - // - glShadeModel (GL_SMOOTH); - - glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); - glFrontFace(GL_CW); // YWB Carmack goes backward - glCullFace(GL_BACK); // Cull backfaces (qcsg used to spit out two sides, doesn't for -glview now) - glEnable(GL_CULL_FACE); // Enable face culling, just in case... - glDisable(GL_TEXTURE_2D); - - // Blending function if enabled.. - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - if (g_UseBlending) - { - glEnable(GL_BLEND);// YWB TESTING - glDisable(GL_DEPTH_TEST); - glDisable(GL_CULL_FACE); // Enable face culling, just in case... - } - else - { - glDisable(GL_BLEND); - glEnable(GL_DEPTH_TEST); - } - glDepthFunc (GL_LEQUAL); - - if( g_bDisp ) - { - DrawDisplacementData(); - } - else - { - // - // draw the list - // - if (g_bShowList1) - glCallList (1); - - if (g_bReadPortals) - { - if (g_bNoDepthPortals) - glDisable(GL_DEPTH_TEST); - glDisable(GL_CULL_FACE); // Disable face culling - if (g_bShowList2) - glCallList(2); - }; - - if (g_bShowLines) - glCallList(3); - } -} - -void ReadPolyFileType(const char *name, int nList, BOOL drawLines) -{ - FILE *f; - int i, j, numverts; - float v[8]; - int c; - int r; - float divisor; - - f = fopen (name, "rt"); - if (!f) - Error ("Couldn't open %s", name); - - if (g_bReadPortals) - divisor = 2.0f; - else - divisor = 1.0f; - - c = 0; - glNewList (nList, GL_COMPILE); - - for (i = 0; i < 3; i++) // Find the center point so we can put the viewer there by default - g_Center[i] = 0.0f; - - if (drawLines) // Slight hilite - glLineWidth(1.5); - - while (1) - { - r = fscanf( f, "%i\n", &numverts); - if (!r || r == EOF) - break; - - if ( c > 65534*8) - break; - - if (drawLines || numverts == 2) - glBegin(GL_LINE_LOOP); - else - glBegin (GL_POLYGON); - - for (i=0 ; i 0) // Avoid division by zero - { - for (i = 0; i < 3; i++) - { - g_Center[i] = g_Center[i]/(float)g_nTotalPoints; // Calculate center... - origin[i] = g_Center[i]; - } - } -} - -#if BENCHMARK_PHY -#define NUM_COLLISION_TESTS 2500 -#include "gametrace.h" -#include "fmtstr.h" - - -struct testlist_t -{ - Vector start; - Vector end; - Vector normal; - bool hit; -}; - -const float baselineTotal = 120.16f; -const float baselineRay = 28.25f; -const float baselineBox = 91.91f; -#define IMPROVEMENT_FACTOR(x,baseline) (baseline/(x)) -#define IMPROVEMENT_PERCENT(x,baseline) (((baseline-(x)) / baseline) * 100.0f) - -testlist_t g_Traces[NUM_COLLISION_TESTS]; -void Benchmark_PHY( const CPhysCollide *pCollide ) -{ - int i; - Msg( "Testing collision system\n" ); - Vector start = vec3_origin; - static Vector *targets = NULL; - static bool first = true; - static float test[2] = {1,1}; - if ( first ) - { - float radius = 0; - float theta = 0; - float phi = 0; - for ( int i = 0; i < NUM_COLLISION_TESTS; i++ ) - { - radius += NUM_COLLISION_TESTS * 123.123f; - radius = fabs(fmod(radius, 128)); - theta += NUM_COLLISION_TESTS * 0.76f; - theta = fabs(fmod(theta, DEG2RAD(360))); - phi += NUM_COLLISION_TESTS * 0.16666666f; - phi = fabs(fmod(phi, DEG2RAD(180))); - - float st, ct, sp, cp; - SinCos( theta, &st, &ct ); - SinCos( phi, &sp, &cp ); - st = sin(theta); - ct = cos(theta); - sp = sin(phi); - cp = cos(phi); - - g_Traces[i].start.x = radius * ct * sp; - g_Traces[i].start.y = radius * st * sp; - g_Traces[i].start.z = radius * cp; - } - first = false; - } - - float duration = 0; - Vector size[2]; - size[0].Init(0,0,0); - size[1].Init(16,16,16); - unsigned int dots = 0; - -#if VPROF_LEVEL > 0 - g_VProfCurrentProfile.Reset(); - g_VProfCurrentProfile.ResetPeaks(); - g_VProfCurrentProfile.Start(); -#endif - unsigned int hitCount = 0; - double startTime = Plat_FloatTime(); - trace_t tr; - for ( i = 0; i < NUM_COLLISION_TESTS; i++ ) - { - physcollision->TraceBox( g_Traces[i].start, start, -size[0], size[0], pCollide, vec3_origin, vec3_angle, &tr ); - if ( tr.DidHit() ) - { - g_Traces[i].end = tr.endpos; - g_Traces[i].normal = tr.plane.normal; - g_Traces[i].hit = true; - hitCount++; - } - else - { - g_Traces[i].hit = false; - } - } - for ( i = 0; i < NUM_COLLISION_TESTS; i++ ) - { - physcollision->TraceBox( g_Traces[i].start, start, -size[1], size[1], pCollide, vec3_origin, vec3_angle, &tr ); - } - duration = Plat_FloatTime() - startTime; - { - unsigned int msSupp = physcollision->ReadStat( 100 ); - unsigned int msGJK = physcollision->ReadStat( 101 ); - unsigned int msMesh = physcollision->ReadStat( 102 ); - CFmtStr str("%d ms total %d ms gjk %d mesh solve\n", msSupp, msGJK, msMesh ); - OutputDebugStr( str.Access() ); - } - -#if VPROF_LEVEL > 0 - g_VProfCurrentProfile.MarkFrame(); - g_VProfCurrentProfile.Stop(); - g_VProfCurrentProfile.Reset(); - g_VProfCurrentProfile.ResetPeaks(); - g_VProfCurrentProfile.Start(); -#endif - hitCount = 0; - startTime = Plat_FloatTime(); - for ( i = 0; i < NUM_COLLISION_TESTS; i++ ) - { - physcollision->TraceBox( g_Traces[i].start, start, -size[0], size[0], pCollide, vec3_origin, vec3_angle, &tr ); - if ( tr.DidHit() ) - { - g_Traces[i].end = tr.endpos; - g_Traces[i].normal = tr.plane.normal; - g_Traces[i].hit = true; - hitCount++; - } - else - { - g_Traces[i].hit = false; - } -#if VPROF_LEVEL > 0 - g_VProfCurrentProfile.MarkFrame(); -#endif - } - double midTime = Plat_FloatTime(); - for ( i = 0; i < NUM_COLLISION_TESTS; i++ ) - { - physcollision->TraceBox( g_Traces[i].start, start, -size[1], size[1], pCollide, vec3_origin, vec3_angle, &tr ); -#if VPROF_LEVEL > 0 - g_VProfCurrentProfile.MarkFrame(); -#endif - } - double endTime = Plat_FloatTime(); - duration = endTime - startTime; - { - CFmtStr str("%d collisions in %.2f ms [%.2f X] %d hits\n", NUM_COLLISION_TESTS, duration*1000, IMPROVEMENT_FACTOR(duration*1000.0f, baselineTotal), hitCount ); - OutputDebugStr( str.Access() ); - } - { - float rayTime = (midTime - startTime) * 1000.0f; - float boxTime = (endTime - midTime)*1000.0f; - CFmtStr str("%.2f ms rays [%.2f X] %.2f ms boxes [%.2f X]\n", rayTime, IMPROVEMENT_FACTOR(rayTime, baselineRay), boxTime, IMPROVEMENT_FACTOR(boxTime, baselineBox)); - OutputDebugStr( str.Access() ); - } - - { - unsigned int msSupp = physcollision->ReadStat( 100 ); - unsigned int msGJK = physcollision->ReadStat( 101 ); - unsigned int msMesh = physcollision->ReadStat( 102 ); - CFmtStr str("%d ms total %d ms gjk %d mesh solve\n", msSupp, msGJK, msMesh ); - OutputDebugStr( str.Access() ); - } -#if VPROF_LEVEL > 0 - g_VProfCurrentProfile.Stop(); - g_VProfCurrentProfile.OutputReport( VPRT_FULL & ~VPRT_HIERARCHY, NULL ); -#endif - - // draw the traces in yellow - glColor3f( 1.0f, 1.0f, 0.0f ); - glBegin( GL_LINES ); - for ( int i = 0; i < NUM_COLLISION_TESTS; i++ ) - { - if ( !g_Traces[i].hit ) - continue; - glVertex3fv( g_Traces[i].end.Base() ); - Vector tmp = g_Traces[i].end + g_Traces[i].normal * 10.0f; - glVertex3fv( tmp.Base() ); - } - glEnd(); -} -#endif - -struct phyviewparams_t -{ - Vector mins; - Vector maxs; - Vector offset; - QAngle angles; - int outputType; - - void Defaults() - { - ClearBounds(mins, maxs); - offset.Init(); - outputType = GL_POLYGON; - angles.Init(); - } -}; - - -void AddVCollideToList( phyheader_t &header, vcollide_t &collide, phyviewparams_t ¶ms ) -{ - matrix3x4_t xform; - AngleMatrix( params.angles, params.offset, xform ); - ClearBounds( params.mins, params.maxs ); - for ( int i = 0; i < header.solidCount; i++ ) - { - ICollisionQuery *pQuery = physcollision->CreateQueryModel( collide.solids[i] ); - for ( int j = 0; j < pQuery->ConvexCount(); j++ ) - { - for ( int k = 0; k < pQuery->TriangleCount(j); k++ ) - { - Vector verts[3]; - pQuery->GetTriangleVerts( j, k, verts ); - Vector v0,v1,v2; - VectorTransform( verts[0], xform, v0 ); - VectorTransform( verts[1], xform, v1 ); - VectorTransform( verts[2], xform, v2 ); - AddPointToBounds( v0, params.mins, params.maxs ); - AddPointToBounds( v1, params.mins, params.maxs ); - AddPointToBounds( v2, params.mins, params.maxs ); - - glBegin(params.outputType); - glColor3ub( 255, 0, 0 ); - glVertex3fv( v0.Base() ); - glColor3ub( 0, 255, 0 ); - glVertex3fv( v1.Base() ); - glColor3ub( 0, 0, 255 ); - glVertex3fv( v2.Base() ); - glEnd(); - } - } - physcollision->DestroyQueryModel( pQuery ); - } -} - -void GL_DrawLine( const Vector &start, const Vector &dir, float length, int r, int g, int b ) -{ - Vector end = start + (dir*length); - glBegin( GL_LINES ); - glColor3ub(r,g,b); - glVertex3fv( start.Base() ); - glVertex3fv( end.Base() ); - glEnd(); -} - -void GL_DrawBox( Vector origin, float size, int r, int g, int b ) -{ - Vector mins = origin - Vector(size,size,size); - Vector maxs = origin + Vector(size,size,size); - const float *v[2] = {mins.Base(), maxs.Base()}; - - Vector start, end; - { - for ( int i = 0; i < 3; i++ ) - { - int a0 = i; - int a1 = (i+1)%3; - int a2 = (i+2)%3; - for ( int j = 0; j < 2; j++ ) - { - for ( int k = 0; k < 2; k++ ) - { - start[a0] = v[0][a0]; - end[a0] = v[1][a0]; - start[a1] = v[j][a1]; - end[a1] = v[j][a1]; - start[a2] = v[k][a2]; - end[a2] = v[k][a2]; - GL_DrawLine( start, end-start, 1, r, g, b ); - } - } - } - } - for ( int axis = 0; axis < 3; axis++ ) - { - int a0 = axis; - int a1 = (axis+1)%3; - int a2 = (axis+2)%3; - start[a0] = v[0][a0]; - end[a0] = v[1][a0]; - start[a1] = 0.5f *(v[0][a1]+v[1][a1]); - end[a1] = 0.5f *(v[0][a1]+v[1][a1]); - start[a2] = 0.5f *(v[0][a2]+v[1][a2]); - end[a2] = 0.5f *(v[0][a2]+v[1][a2]); - GL_DrawLine( start, end-start, 1, r, g, b ); - } -} - - -void ReadPHYFile(const char *name, phyviewparams_t ¶ms ) -{ - FILE *fp = fopen (name, "rb"); - if (!fp) - Error ("Couldn't open %s", name); - - phyheader_t header; - - fread( &header, sizeof(header), 1, fp ); - if ( header.size != sizeof(header) || header.solidCount <= 0 ) - return; - - int pos = ftell( fp ); - fseek( fp, 0, SEEK_END ); - int fileSize = ftell(fp) - pos; - fseek( fp, pos, SEEK_SET ); - - char *buf = (char *)_alloca( fileSize ); - fread( buf, fileSize, 1, fp ); - fclose( fp ); - - vcollide_t collide; - physcollision->VCollideLoad( &collide, header.solidCount, (const char *)buf, fileSize ); -#if 0 - Vector start0( -3859.1199, -2050.8674, 64.031250 ); - Vector end0(-3859.2246, -2051.2817, 64.031250 ); - Vector modelPosition(-3840,-2068.0000, 82.889099); - QAngle modelAngles(0,90,0); - - { - Ray_t ray; - ray.Init( start0, end0, Vector(-16,-16,0), Vector(16,16,72)); - trace_t tr; - physcollision->TraceBox( ray, collide.solids[0], modelPosition, modelAngles, &tr ); - Assert(!tr.startsolid); - if ( tr.DidHit() ) - { - Ray_t ray2; - ray2.Init( tr.endpos, tr.endpos, Vector(-16,-16,0), Vector(16,16,72)); - trace_t tr2; - physcollision->TraceBox( ray2, collide.solids[0], modelPosition, modelAngles, &tr2 ); - Assert(!tr2.startsolid); - } - } -#endif -#if BENCHMARK_PHY - Benchmark_PHY( collide.solids[0] ); -#endif - AddVCollideToList( header, collide, params ); -} - -void ReadPolyFile (const char *name) -{ - char ext[4]; - Q_ExtractFileExtension( name, ext, 4 ); - - bool isPHY = !Q_stricmp( ext, "phy" ); - if ( isPHY ) - { - CreateInterfaceFn physicsFactory = GetPhysicsFactory(); - physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); - if ( physcollision ) - { - phyviewparams_t params; - params.Defaults(); - glNewList (1, GL_COMPILE); - ReadPHYFile( name, params ); - Vector tmp = (params.mins + params.maxs) * 0.5; - tmp.CopyToArray(origin); - glEndList (); - } - } - else - { - // Read in polys... - ReadPolyFileType(name, 1, false); - - // Make list 3 just the lines... so we can draw outlines - ReadPolyFileType(name, 3, true); - } -} - -void ReadPortalFile (char *name) -{ - FILE *f; - int i, numverts; - float v[8]; - int c; - int r; - - // For Portal type reading... - char szDummy[80]; - int nNumLeafs; - int nNumPortals; - int nLeafIndex[2]; - - f = fopen (name, "r"); - if (!f) - Error ("Couldn't open %s", name); - - c = 0; - - glNewList (2, GL_COMPILE); - - // Read in header - fscanf(f, "%79s\n", szDummy); - fscanf(f, "%i\n", &nNumLeafs); - fscanf(f, "%i\n", &nNumPortals); - - glLineWidth(1.5); - - while (1) - { - r = fscanf(f, "%i %i %i ", &numverts, &nLeafIndex[0], &nLeafIndex[1]); - if (!r || r == EOF) - break; - - glBegin(GL_LINE_LOOP); - for (i=0 ; i= MAX_DISP_COUNT ) - break; - - fileCount = fscanf( pFile, "%f %f %f %f %f %f", - &dispPoints[dispPointCount][0], &dispPoints[dispPointCount][1], &dispPoints[dispPointCount][2], - &dispNormals[dispPointCount][0], &dispNormals[dispPointCount][1], &dispNormals[dispPointCount][2] ); - dispPointCount++; - - // end of file check - if( !fileCount || ( fileCount == EOF ) ) - break; - } - - fclose( pFile ); - - return TRUE; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void DrawDisplacementData( void ) -{ - int i, j; - int width, halfCount; - - GLUquadricObj *pObject = gluNewQuadric(); - - glEnable( GL_DEPTH_TEST ); - - for( i = 0; i < dispPointCount; i++ ) - { - // draw a sphere where the point is (in red) - glColor3f( 1.0f, 0.0f, 0.0f ); - glPushMatrix(); - glTranslatef( dispPoints[i][0], dispPoints[i][1], dispPoints[i][2] ); - gluSphere( pObject, 5, 5, 5 ); - glPopMatrix(); - - // draw the normal (in yellow) - glColor3f( 1.0f, 1.0f, 0.0f ); - glBegin( GL_LINES ); - glVertex3f( dispPoints[i][0], dispPoints[i][1], dispPoints[i][2] ); - glVertex3f( dispPoints[i][0] + ( dispNormals[i][0] * 50.0f ), dispPoints[i][1] + ( dispNormals[i][1] * 50.0f ), dispPoints[i][2] + ( dispNormals[i][2] * 50.0f ) ); - glEnd(); - } - - halfCount = dispPointCount / 2; - - width = sqrt( (float)halfCount ); - - glDisable( GL_CULL_FACE ); - - glColor3f( 0.0f, 0.0f, 1.0f ); - for( i = 0; i < width - 1; i++ ) - { - for( j = 0; j < width - 1; j++ ) - { - glBegin( GL_POLYGON ); - glVertex3f( dispPoints[i*width+j][0], dispPoints[i*width+j][1], dispPoints[i*width+j][2] ); - glVertex3f( dispPoints[(i+1)*width+j][0], dispPoints[(i+1)*width+j][1], dispPoints[(i+1)*width+j][2] ); - glVertex3f( dispPoints[(i+1)*width+(j+1)][0], dispPoints[(i+1)*width+(j+1)][1], dispPoints[(i+1)*width+(j+1)][2] ); - glVertex3f( dispPoints[i*width+(j+1)][0], dispPoints[i*width+(j+1)][1], dispPoints[i*width+(j+1)][2] ); - glEnd(); - } - } - -#if 0 - for( i = 0; i < width - 1; i++ ) - { - for( j = 0; j < width - 1; j++ ) - { - glBegin( GL_POLYGON ); - glVertex3f( dispPoints[halfCount+(i*width+j)][0], dispPoints[halfCount+(i*width+j)][1], dispPoints[halfCount+(i*width+j)][2] ); - glVertex3f( dispPoints[halfCount+((i+1)*width+j)][0], dispPoints[halfCount+(i+1)*width+j][1], dispPoints[halfCount+((i+1)*width+j)][2] ); - glVertex3f( dispPoints[halfCount+((i+1)*width+(j+1))][0], dispPoints[halfCount+(i+1)*width+(j+1)][1], dispPoints[halfCount+((i+1)*width+(j+1))][2] ); - glVertex3f( dispPoints[halfCount+(i*width+(j+1))][0], dispPoints[halfCount+(i*width+(j+1))][1], dispPoints[halfCount+(i*width+(j+1))][2] ); - glEnd(); - } - } -#endif - - glColor3f( 0.0f, 1.0f, 0.0f ); - for( i = 0; i < width - 1; i++ ) - { - for( j = 0; j < width - 1; j++ ) - { - glBegin( GL_POLYGON ); - glVertex3f( dispPoints[i*width+j][0] + ( dispNormals[i*width+j][0] * 150.0f ), - dispPoints[i*width+j][1] + ( dispNormals[i*width+j][1] * 150.0f ), - dispPoints[i*width+j][2] + ( dispNormals[i*width+j][2] * 150.0f ) ); - - glVertex3f( dispPoints[(i+1)*width+j][0] + ( dispNormals[(i+1)*width+j][0] * 150.0f ), - dispPoints[(i+1)*width+j][1] + ( dispNormals[(i+1)*width+j][1] * 150.0f ), - dispPoints[(i+1)*width+j][2] + ( dispNormals[(i+1)*width+j][2] * 150.0f ) ); - - glVertex3f( dispPoints[(i+1)*width+(j+1)][0] + ( dispNormals[(i+1)*width+(j+1)][0] * 150.0f ), - dispPoints[(i+1)*width+(j+1)][1] + ( dispNormals[(i+1)*width+(j+1)][1] * 150.0f ), - dispPoints[(i+1)*width+(j+1)][2] + ( dispNormals[(i+1)*width+(j+1)][2] * 150.0f ) ); - - glVertex3f( dispPoints[i*width+(j+1)][0] + ( dispNormals[i*width+(j+1)][0] * 150.0f ), - dispPoints[i*width+(j+1)][1] + ( dispNormals[i*width+(j+1)][1] * 150.0f ), - dispPoints[i*width+(j+1)][2] + ( dispNormals[i*width+(j+1)][2] * 150.0f ) ); - glEnd(); - } - } - - glDisable( GL_DEPTH_TEST ); - - glColor3f( 0.0f, 0.0f, 1.0f ); - for( i = 0; i < width - 1; i++ ) - { - for( j = 0; j < width - 1; j++ ) - { - glBegin( GL_LINE_LOOP ); - glVertex3f( dispPoints[i*width+j][0] + ( dispNormals[i*width+j][0] * 150.0f ), - dispPoints[i*width+j][1] + ( dispNormals[i*width+j][1] * 150.0f ), - dispPoints[i*width+j][2] + ( dispNormals[i*width+j][2] * 150.0f ) ); - - glVertex3f( dispPoints[(i+1)*width+j][0] + ( dispNormals[(i+1)*width+j][0] * 150.0f ), - dispPoints[(i+1)*width+j][1] + ( dispNormals[(i+1)*width+j][1] * 150.0f ), - dispPoints[(i+1)*width+j][2] + ( dispNormals[(i+1)*width+j][2] * 150.0f ) ); - - glVertex3f( dispPoints[(i+1)*width+(j+1)][0] + ( dispNormals[(i+1)*width+(j+1)][0] * 150.0f ), - dispPoints[(i+1)*width+(j+1)][1] + ( dispNormals[(i+1)*width+(j+1)][1] * 150.0f ), - dispPoints[(i+1)*width+(j+1)][2] + ( dispNormals[(i+1)*width+(j+1)][2] * 150.0f ) ); - - glVertex3f( dispPoints[i*width+(j+1)][0] + ( dispNormals[i*width+(j+1)][0] * 150.0f ), - dispPoints[i*width+(j+1)][1] + ( dispNormals[i*width+(j+1)][1] * 150.0f ), - dispPoints[i*width+(j+1)][2] + ( dispNormals[i*width+(j+1)][2] * 150.0f ) ); - glEnd(); - } - } - - - gluDeleteQuadric( pObject ); -} - - -//===================================================================== - -BOOL bSetupPixelFormat(HDC hDC) -{ - static PIXELFORMATDESCRIPTOR pfd = { - sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd - 1, // version number - PFD_DRAW_TO_WINDOW | // support window - PFD_SUPPORT_OPENGL | // support OpenGL - PFD_DOUBLEBUFFER, // double buffered - PFD_TYPE_RGBA, // RGBA type - 24, // 24-bit color depth - 0, 0, 0, 0, 0, 0, // color bits ignored - 0, // no alpha buffer - 0, // shift bit ignored - 0, // no accumulation buffer - 0, 0, 0, 0, // accum bits ignored - 32, // 32-bit z-buffer - 0, // no stencil buffer - 0, // no auxiliary buffer - PFD_MAIN_PLANE, // main layer - 0, // reserved - 0, 0, 0 // layer masks ignored - }; - - int pixelformat = 0; - - if ( (pixelformat = ChoosePixelFormat(hDC, &pfd)) == 0 ) - Error ("ChoosePixelFormat failed"); - - if (!SetPixelFormat(hDC, pixelformat, &pfd)) - Error ("SetPixelFormat failed"); - - return TRUE; -} - -/* -============ -CameraWndProc -============ -*/ -LONG WINAPI WCam_WndProc ( - HWND hWnd, - UINT uMsg, - WPARAM wParam, - LPARAM lParam) -{ - LONG lRet = 1; - RECT rect; - - GetClientRect(hWnd, &rect); - - switch (uMsg) - { - case WM_CREATE: - { - camdc = GetDC(hWnd); - bSetupPixelFormat(camdc); - - baseRC = wglCreateContext( camdc ); - if (!baseRC) - Error ("wglCreateContext failed"); - if (!wglMakeCurrent( camdc, baseRC )) - Error ("wglMakeCurrent failed"); - glCullFace(GL_FRONT); - glEnable(GL_CULL_FACE); - } - break; - case WM_PAINT: - { - PAINTSTRUCT ps; - - BeginPaint(hWnd, &ps); - if (!wglMakeCurrent( camdc, baseRC )) - Error ("wglMakeCurrent failed"); - Draw (); - SwapBuffers(camdc); - EndPaint(hWnd, &ps); - } - break; - - case WM_KEYDOWN: - KeyDown (wParam); - AppKeyDown( wParam ); - break; - - case WM_KEYUP: - AppKeyUp( wParam ); - break; - - case WM_MBUTTONDOWN: - case WM_RBUTTONDOWN: - case WM_LBUTTONDOWN: - SetCapture (camerawindow); - ShowCursor( FALSE ); - g_Capture = TRUE; - break; - - case WM_MBUTTONUP: - case WM_RBUTTONUP: - case WM_LBUTTONUP: - if (! (wParam & (MK_LBUTTON|MK_RBUTTON|MK_MBUTTON))) - { - g_Capture = FALSE; - ReleaseCapture (); - ShowCursor( TRUE ); - } - break; - - case WM_SIZE: - InvalidateRect(camerawindow, NULL, false); - break; - case WM_NCCALCSIZE:// don't let windows copy pixels - lRet = DefWindowProc (hWnd, uMsg, wParam, lParam); - return WVR_REDRAW; - case WM_CLOSE: - /* call destroy window to cleanup and go away */ - DestroyWindow (hWnd); - break; - - case WM_DESTROY: - { - HGLRC hRC; - HDC hDC; - - /* release and free the device context and rendering context */ - hRC = wglGetCurrentContext(); - hDC = wglGetCurrentDC(); - - wglMakeCurrent(NULL, NULL); - - if (hRC) - wglDeleteContext(hRC); - if (hDC) - ReleaseDC(hWnd, hDC); - - PostQuitMessage (0); - } - break; - - default: - /* pass all unhandled messages to DefWindowProc */ - lRet = DefWindowProc (hWnd, uMsg, wParam, lParam); - break; - } - - /* return 1 if handled message, 0 if not */ - return lRet; -} - - -/* -============== -WCam_Register -============== -*/ -void WCam_Register (HINSTANCE hInstance) -{ - WNDCLASS wc; - - /* Register the camera class */ - memset (&wc, 0, sizeof(wc)); - - wc.style = 0; - wc.lpfnWndProc = (WNDPROC)WCam_WndProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - wc.hInstance = hInstance; - wc.hIcon = 0; - wc.hCursor = LoadCursor (NULL,IDC_ARROW); - wc.hbrBackground = NULL; - wc.lpszMenuName = 0; - wc.lpszClassName = "camera"; - - if (!RegisterClass (&wc) ) - Error ("WCam_Register: failed"); -} - - -void WCam_Create (HINSTANCE hInstance) -{ - // Center it - int nScx, nScy; - int w, h; - int x, y; - - WCam_Register (hInstance); - - w = ::width; - h = ::height; - - nScx = GetSystemMetrics(SM_CXSCREEN); - nScy = GetSystemMetrics(SM_CYSCREEN); - - - x = (nScx - w)/2; - y = (nScy - h)/2; - - camerawindow = CreateWindow ("camera" , - "Camera View", - WS_OVERLAPPED | - WS_CAPTION | - WS_SYSMENU | - WS_THICKFRAME | - WS_MAXIMIZEBOX | - WS_CLIPSIBLINGS | - WS_CLIPCHILDREN, - - x, - y, - w, - h, // size - - NULL, // parent window - 0, // no menu - hInstance, - 0); - if (!camerawindow) - Error ("Couldn't create camerawindow"); - - ShowWindow (camerawindow, SW_SHOWDEFAULT); -} - - -void AppKeyDown( int key ) -{ - key &= 0xFF; - - g_Keys[key] = 0x03; // add debounce bit -} - -void AppKeyUp( int key ) -{ - key &= 0xFF; - - g_Keys[key] &= 0x02; -} - -void AppRender( void ) -{ - static double lastTime = 0; - double time = timeGetTime() * 0.001f; - double frametime = time - lastTime; - - // clamp too large frames (like first frame) - if ( frametime > 0.2 ) - frametime = 0.2; - lastTime = time; - - if (!wglMakeCurrent( camdc, baseRC )) - Error ("wglMakeCurrent failed"); - - Cam_Update( frametime ); - - if (g_Update) - { - Draw (); - SwapBuffers(camdc); - g_Update = FALSE; - } - else - { - Sleep( 1.0 ); - } -} - -SpewRetval_t Sys_SpewFunc( SpewType_t type, const char *pMsg ) -{ - OutputDebugString( pMsg ); - if( type == SPEW_ASSERT ) - return SPEW_DEBUGGER; - else if( type == SPEW_ERROR ) - return SPEW_ABORT; - else - return SPEW_CONTINUE; -} - - -/* -================== -WinMain - -================== -*/ -int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance - ,LPSTR lpCmdLine, int nCmdShow) -{ - CommandLine()->CreateCmdLine( Plat_GetCommandLine() ); - - MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); - MSG msg; - - if (!lpCmdLine || !lpCmdLine[0]) - Error ("No file specified"); - - main_instance = hInstance; - - WCam_Create (hInstance); - - // Last argument is the file name - const char *pFileName = CommandLine()->GetParm( CommandLine()->ParmCount() - 1 ); - CmdLib_InitFileSystem( pFileName ); - - if ( CommandLine()->CheckParm( "-portal") ) - { - g_bReadPortals = 1; - g_nPortalHighlight = CommandLine()->ParmValue( "-portalhighlight", -1 ); - g_nLeafHighlight = CommandLine()->ParmValue( "-leafhighlight", -1 ); - } - g_flMovementSpeed = CommandLine()->ParmValue( "-speed", 320 ); - - if( CommandLine()->CheckParm( "-disp") ) - { - ReadDisplacementFile( pFileName ); - g_bDisp = TRUE; - } - SpewOutputFunc( Sys_SpewFunc ); - - // Any chunk of original left is the filename. - if (pFileName && pFileName[0] && !g_bDisp ) - { - ReadPolyFile( pFileName ); - } - - if (g_bReadPortals) - { - // Copy file again and this time look for the . from .gl? so we can concatenate .prt - // and open the portal file. - char szTempCmd[MAX_PATH]; - strcpy(szTempCmd, pFileName); - char *pTmp = szTempCmd; - while (pTmp && *pTmp && *pTmp != '.') - { - pTmp++; - } - - *pTmp = '\0'; - strcat(szTempCmd, ".prt"); - - ReadPortalFile(szTempCmd); - }; - - /* main window message loop */ - while (g_Active) - { - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - { - TranslateMessage (&msg); - DispatchMessage (&msg); - } - AppRender(); - } - - /* return success of application */ - return TRUE; -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "glos.h" +#include +#if _MSC_VER < 1600 +#include +#endif +#include +#include +#include +#include +#include +#include "cmdlib.h" +#include "mathlib/mathlib.h" +#include "cmodel.h" +#include "tier1/strtools.h" +#include "physdll.h" +#include "phyfile.h" +#include "vphysics_interface.h" +#include "tier0/icommandline.h" +#include "tier0/vprof.h" + +HDC camdc; +HGLRC baseRC; +HWND camerawindow; +HANDLE main_instance; + +/* YWB: 3/13/98 + You run the program like normal with any file. If you want to read portals for the + file type, you type: glview -portal filename.gl0 (or whatever). glview will then + try to read in the .prt file filename.prt. + + The portals are shown as white lines superimposed over your image. You can toggle the + view between showing portals or not by hitting the '2' key. The '1' key toggles + world polygons. + + The 'b' key toggles blending modes. + + If you don't want to depth buffer the portals, hit 'p'. + + The command line parsing is inelegant but functional. + + I sped up the KB movement and turn speed, too. + */ + +// Vars added by YWB +Vector g_Center; // Center of all read points, so camera is in a sensible place +int g_nTotalPoints = 0; // Total points read, for calculating center +int g_UseBlending = 0; // Toggle to use blending mode or not +BOOL g_bReadPortals = 0; // Did we read in a portal file? +BOOL g_bNoDepthPortals = 0; // Do we zbuffer the lines of the portals? +int g_nPortalHighlight = -1; // The leaf we're viewing +int g_nLeafHighlight = -1; // The leaf we're viewing +BOOL g_bShowList1 = 1; // Show regular polygons? +BOOL g_bShowList2 = 1; // Show portals? +BOOL g_bShowLines = 0; // Show outlines of faces +BOOL g_Active = TRUE; +BOOL g_Update = TRUE; +BOOL g_bDisp = FALSE; +IPhysicsCollision *physcollision = NULL; +// ----------- +static int g_Keys[255]; +void AppKeyDown( int key ); +void AppKeyUp( int key ); + + +BOOL ReadDisplacementFile( const char *filename ); +void DrawDisplacementData( void ); + +#define BENCHMARK_PHY 0 + +/* +================= +Error + +For abnormal program terminations +================= +*/ +void Error (char *error, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr,error); + vsprintf (text, error,argptr); + va_end (argptr); + + MessageBox(NULL, text, "Error", 0 /* MB_OK */ ); + + exit (1); +} + +float origin[3] = {32, 32, 48}; +float angles[3]; +float forward[3], right[3], vup[3], vpn[3], vright[3]; +float width = 1024; +float height = 768; + +float g_flMovementSpeed = 320.f; // Units / second (run speed of HL) +#define SPEED_TURN 90 // Degrees / second + +#define VK_COMMA 188 +#define VK_PERIOD 190 + + +void KeyDown (int key) +{ + switch (key) + { + case VK_ESCAPE: + g_Active = FALSE; + break; + + case VK_F1: + glEnable (GL_CULL_FACE); + glCullFace (GL_FRONT); + break; + case 'B': + g_UseBlending ^= 1; + if (g_UseBlending) + glEnable(GL_BLEND);// YWB TESTING + else + glDisable(GL_BLEND); + break; + + case '1': + g_bShowList1 ^= 1; + break; + case '2': + g_bShowList2 ^= 1; + break; + case 'P': + g_bNoDepthPortals ^= 1; + break; + case 'L': + g_bShowLines ^= 1; + break; + } + g_Update = TRUE; +} + +static BOOL g_Capture = FALSE; + +#define MOUSE_SENSITIVITY 0.2f +#define MOUSE_SENSITIVITY_X (MOUSE_SENSITIVITY*1) +#define MOUSE_SENSITIVITY_Y (MOUSE_SENSITIVITY*1) + +void Cam_MouseMoved( void ) +{ + if ( g_Capture ) + { + RECT rect; + int centerx, centery; + float deltax, deltay; + POINT cursorPoint; + + GetWindowRect( camerawindow, &rect ); + + if ( rect.top < 0) + rect.top = 0; + if ( rect.left < 0) + rect.left = 0; + + centerx = ( rect.left + rect.right ) / 2; + centery = ( rect.top + rect.bottom ) / 2; + + GetCursorPos( &cursorPoint ); + SetCursorPos( centerx, centery ); + + deltax = (cursorPoint.x - centerx) * MOUSE_SENSITIVITY_X; + deltay = (cursorPoint.y - centery) * MOUSE_SENSITIVITY_Y; + + angles[1] -= deltax; + angles[0] -= deltay; + + g_Update = TRUE; + } +} + +int Test_Key( int key ) +{ + int r = (g_Keys[ key ] != 0); + + g_Keys[ key ] &= 0x01; // clear out debounce bit + + if (r) + g_Update = TRUE; + + return r; +} + +// UNDONE: Probably should change the controls to match the game - but I don't know who relies on them +// as of now. +void Cam_Update( float frametime ) +{ + if ( Test_Key( 'W' ) ) + { + VectorMA (origin, g_flMovementSpeed*frametime, vpn, origin); + } + if ( Test_Key( 'S' ) ) + { + VectorMA (origin, -g_flMovementSpeed*frametime, vpn, origin); + } + if ( Test_Key( 'A' ) ) + { + VectorMA (origin, -g_flMovementSpeed*frametime, vright, origin); + } + if ( Test_Key( 'D' ) ) + { + VectorMA (origin, g_flMovementSpeed*frametime, vright, origin); + } + + if ( Test_Key( VK_UP ) ) + { + VectorMA (origin, g_flMovementSpeed*frametime, forward, origin); + } + if ( Test_Key( VK_DOWN ) ) + { + VectorMA (origin, -g_flMovementSpeed*frametime, forward, origin); + } + + if ( Test_Key( VK_LEFT ) ) + { + angles[1] += SPEED_TURN * frametime; + } + if ( Test_Key( VK_RIGHT ) ) + { + angles[1] -= SPEED_TURN * frametime; + } + if ( Test_Key( 'F' ) ) + { + origin[2] += g_flMovementSpeed*frametime; + } + if ( Test_Key( 'C' ) ) + { + origin[2] -= g_flMovementSpeed*frametime; + } + if ( Test_Key( VK_INSERT ) ) + { + angles[0] += SPEED_TURN * frametime; + if (angles[0] > 85) + angles[0] = 85; + } + if ( Test_Key( VK_DELETE ) ) + { + angles[0] -= SPEED_TURN * frametime; + if (angles[0] < -85) + angles[0] = -85; + } + Cam_MouseMoved(); +} + +void Cam_BuildMatrix (void) +{ + float xa, ya; + float matrix[4][4]; + int i; + + xa = angles[0]/180*M_PI; + ya = angles[1]/180*M_PI; + + // the movement matrix is kept 2d ?? do we want this? + + forward[0] = cos(ya); + forward[1] = sin(ya); + right[0] = forward[1]; + right[1] = -forward[0]; + + glGetFloatv (GL_PROJECTION_MATRIX, &matrix[0][0]); + + for (i=0 ; i<3 ; i++) + { + vright[i] = matrix[i][0]; + vup[i] = matrix[i][1]; + vpn[i] = matrix[i][2]; + } + + VectorNormalize (vright); + VectorNormalize (vup); + VectorNormalize (vpn); +} + +void Draw (void) +{ + float screenaspect; + float yfov; + + //glClearColor (0.5, 0.5, 0.5, 0); + glClearColor(0.0, 0.0, 0.0, 0); // Black Clearing YWB + glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // + // set up viewpoint + // + glMatrixMode(GL_PROJECTION); + glLoadIdentity (); + + screenaspect = (float)width/height; + yfov = 2*atan((float)height/width)*180/M_PI; + gluPerspective (yfov, screenaspect, 6, 20000); + + glRotatef (-90, 1, 0, 0); // put Z going up + glRotatef (90, 0, 0, 1); // put Z going up + glRotatef (angles[0], 0, 1, 0); + glRotatef (-angles[1], 0, 0, 1); + glTranslatef (-origin[0], -origin[1], -origin[2]); + + Cam_BuildMatrix (); + + // + // set drawing parms + // + glShadeModel (GL_SMOOTH); + + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + glFrontFace(GL_CW); // YWB Carmack goes backward + glCullFace(GL_BACK); // Cull backfaces (qcsg used to spit out two sides, doesn't for -glview now) + glEnable(GL_CULL_FACE); // Enable face culling, just in case... + glDisable(GL_TEXTURE_2D); + + // Blending function if enabled.. + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + if (g_UseBlending) + { + glEnable(GL_BLEND);// YWB TESTING + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); // Enable face culling, just in case... + } + else + { + glDisable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + } + glDepthFunc (GL_LEQUAL); + + if( g_bDisp ) + { + DrawDisplacementData(); + } + else + { + // + // draw the list + // + if (g_bShowList1) + glCallList (1); + + if (g_bReadPortals) + { + if (g_bNoDepthPortals) + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); // Disable face culling + if (g_bShowList2) + glCallList(2); + }; + + if (g_bShowLines) + glCallList(3); + } +} + +void ReadPolyFileType(const char *name, int nList, BOOL drawLines) +{ + FILE *f; + int i, j, numverts; + float v[8]; + int c; + int r; + float divisor; + + f = fopen (name, "rt"); + if (!f) + Error ("Couldn't open %s", name); + + if (g_bReadPortals) + divisor = 2.0f; + else + divisor = 1.0f; + + c = 0; + glNewList (nList, GL_COMPILE); + + for (i = 0; i < 3; i++) // Find the center point so we can put the viewer there by default + g_Center[i] = 0.0f; + + if (drawLines) // Slight hilite + glLineWidth(1.5); + + while (1) + { + r = fscanf( f, "%i\n", &numverts); + if (!r || r == EOF) + break; + + if ( c > 65534*8) + break; + + if (drawLines || numverts == 2) + glBegin(GL_LINE_LOOP); + else + glBegin (GL_POLYGON); + + for (i=0 ; i 0) // Avoid division by zero + { + for (i = 0; i < 3; i++) + { + g_Center[i] = g_Center[i]/(float)g_nTotalPoints; // Calculate center... + origin[i] = g_Center[i]; + } + } +} + +#if BENCHMARK_PHY +#define NUM_COLLISION_TESTS 2500 +#include "gametrace.h" +#include "fmtstr.h" + + +struct testlist_t +{ + Vector start; + Vector end; + Vector normal; + bool hit; +}; + +const float baselineTotal = 120.16f; +const float baselineRay = 28.25f; +const float baselineBox = 91.91f; +#define IMPROVEMENT_FACTOR(x,baseline) (baseline/(x)) +#define IMPROVEMENT_PERCENT(x,baseline) (((baseline-(x)) / baseline) * 100.0f) + +testlist_t g_Traces[NUM_COLLISION_TESTS]; +void Benchmark_PHY( const CPhysCollide *pCollide ) +{ + int i; + Msg( "Testing collision system\n" ); + Vector start = vec3_origin; + static Vector *targets = NULL; + static bool first = true; + static float test[2] = {1,1}; + if ( first ) + { + float radius = 0; + float theta = 0; + float phi = 0; + for ( int i = 0; i < NUM_COLLISION_TESTS; i++ ) + { + radius += NUM_COLLISION_TESTS * 123.123f; + radius = fabs(fmod(radius, 128)); + theta += NUM_COLLISION_TESTS * 0.76f; + theta = fabs(fmod(theta, DEG2RAD(360))); + phi += NUM_COLLISION_TESTS * 0.16666666f; + phi = fabs(fmod(phi, DEG2RAD(180))); + + float st, ct, sp, cp; + SinCos( theta, &st, &ct ); + SinCos( phi, &sp, &cp ); + st = sin(theta); + ct = cos(theta); + sp = sin(phi); + cp = cos(phi); + + g_Traces[i].start.x = radius * ct * sp; + g_Traces[i].start.y = radius * st * sp; + g_Traces[i].start.z = radius * cp; + } + first = false; + } + + float duration = 0; + Vector size[2]; + size[0].Init(0,0,0); + size[1].Init(16,16,16); + unsigned int dots = 0; + +#if VPROF_LEVEL > 0 + g_VProfCurrentProfile.Reset(); + g_VProfCurrentProfile.ResetPeaks(); + g_VProfCurrentProfile.Start(); +#endif + unsigned int hitCount = 0; + double startTime = Plat_FloatTime(); + trace_t tr; + for ( i = 0; i < NUM_COLLISION_TESTS; i++ ) + { + physcollision->TraceBox( g_Traces[i].start, start, -size[0], size[0], pCollide, vec3_origin, vec3_angle, &tr ); + if ( tr.DidHit() ) + { + g_Traces[i].end = tr.endpos; + g_Traces[i].normal = tr.plane.normal; + g_Traces[i].hit = true; + hitCount++; + } + else + { + g_Traces[i].hit = false; + } + } + for ( i = 0; i < NUM_COLLISION_TESTS; i++ ) + { + physcollision->TraceBox( g_Traces[i].start, start, -size[1], size[1], pCollide, vec3_origin, vec3_angle, &tr ); + } + duration = Plat_FloatTime() - startTime; + { + unsigned int msSupp = physcollision->ReadStat( 100 ); + unsigned int msGJK = physcollision->ReadStat( 101 ); + unsigned int msMesh = physcollision->ReadStat( 102 ); + CFmtStr str("%d ms total %d ms gjk %d mesh solve\n", msSupp, msGJK, msMesh ); + OutputDebugStr( str.Access() ); + } + +#if VPROF_LEVEL > 0 + g_VProfCurrentProfile.MarkFrame(); + g_VProfCurrentProfile.Stop(); + g_VProfCurrentProfile.Reset(); + g_VProfCurrentProfile.ResetPeaks(); + g_VProfCurrentProfile.Start(); +#endif + hitCount = 0; + startTime = Plat_FloatTime(); + for ( i = 0; i < NUM_COLLISION_TESTS; i++ ) + { + physcollision->TraceBox( g_Traces[i].start, start, -size[0], size[0], pCollide, vec3_origin, vec3_angle, &tr ); + if ( tr.DidHit() ) + { + g_Traces[i].end = tr.endpos; + g_Traces[i].normal = tr.plane.normal; + g_Traces[i].hit = true; + hitCount++; + } + else + { + g_Traces[i].hit = false; + } +#if VPROF_LEVEL > 0 + g_VProfCurrentProfile.MarkFrame(); +#endif + } + double midTime = Plat_FloatTime(); + for ( i = 0; i < NUM_COLLISION_TESTS; i++ ) + { + physcollision->TraceBox( g_Traces[i].start, start, -size[1], size[1], pCollide, vec3_origin, vec3_angle, &tr ); +#if VPROF_LEVEL > 0 + g_VProfCurrentProfile.MarkFrame(); +#endif + } + double endTime = Plat_FloatTime(); + duration = endTime - startTime; + { + CFmtStr str("%d collisions in %.2f ms [%.2f X] %d hits\n", NUM_COLLISION_TESTS, duration*1000, IMPROVEMENT_FACTOR(duration*1000.0f, baselineTotal), hitCount ); + OutputDebugStr( str.Access() ); + } + { + float rayTime = (midTime - startTime) * 1000.0f; + float boxTime = (endTime - midTime)*1000.0f; + CFmtStr str("%.2f ms rays [%.2f X] %.2f ms boxes [%.2f X]\n", rayTime, IMPROVEMENT_FACTOR(rayTime, baselineRay), boxTime, IMPROVEMENT_FACTOR(boxTime, baselineBox)); + OutputDebugStr( str.Access() ); + } + + { + unsigned int msSupp = physcollision->ReadStat( 100 ); + unsigned int msGJK = physcollision->ReadStat( 101 ); + unsigned int msMesh = physcollision->ReadStat( 102 ); + CFmtStr str("%d ms total %d ms gjk %d mesh solve\n", msSupp, msGJK, msMesh ); + OutputDebugStr( str.Access() ); + } +#if VPROF_LEVEL > 0 + g_VProfCurrentProfile.Stop(); + g_VProfCurrentProfile.OutputReport( VPRT_FULL & ~VPRT_HIERARCHY, NULL ); +#endif + + // draw the traces in yellow + glColor3f( 1.0f, 1.0f, 0.0f ); + glBegin( GL_LINES ); + for ( int i = 0; i < NUM_COLLISION_TESTS; i++ ) + { + if ( !g_Traces[i].hit ) + continue; + glVertex3fv( g_Traces[i].end.Base() ); + Vector tmp = g_Traces[i].end + g_Traces[i].normal * 10.0f; + glVertex3fv( tmp.Base() ); + } + glEnd(); +} +#endif + +struct phyviewparams_t +{ + Vector mins; + Vector maxs; + Vector offset; + QAngle angles; + int outputType; + + void Defaults() + { + ClearBounds(mins, maxs); + offset.Init(); + outputType = GL_POLYGON; + angles.Init(); + } +}; + + +void AddVCollideToList( phyheader_t &header, vcollide_t &collide, phyviewparams_t ¶ms ) +{ + matrix3x4_t xform; + AngleMatrix( params.angles, params.offset, xform ); + ClearBounds( params.mins, params.maxs ); + for ( int i = 0; i < header.solidCount; i++ ) + { + ICollisionQuery *pQuery = physcollision->CreateQueryModel( collide.solids[i] ); + for ( int j = 0; j < pQuery->ConvexCount(); j++ ) + { + for ( int k = 0; k < pQuery->TriangleCount(j); k++ ) + { + Vector verts[3]; + pQuery->GetTriangleVerts( j, k, verts ); + Vector v0,v1,v2; + VectorTransform( verts[0], xform, v0 ); + VectorTransform( verts[1], xform, v1 ); + VectorTransform( verts[2], xform, v2 ); + AddPointToBounds( v0, params.mins, params.maxs ); + AddPointToBounds( v1, params.mins, params.maxs ); + AddPointToBounds( v2, params.mins, params.maxs ); + + glBegin(params.outputType); + glColor3ub( 255, 0, 0 ); + glVertex3fv( v0.Base() ); + glColor3ub( 0, 255, 0 ); + glVertex3fv( v1.Base() ); + glColor3ub( 0, 0, 255 ); + glVertex3fv( v2.Base() ); + glEnd(); + } + } + physcollision->DestroyQueryModel( pQuery ); + } +} + +void GL_DrawLine( const Vector &start, const Vector &dir, float length, int r, int g, int b ) +{ + Vector end = start + (dir*length); + glBegin( GL_LINES ); + glColor3ub(r,g,b); + glVertex3fv( start.Base() ); + glVertex3fv( end.Base() ); + glEnd(); +} + +void GL_DrawBox( Vector origin, float size, int r, int g, int b ) +{ + Vector mins = origin - Vector(size,size,size); + Vector maxs = origin + Vector(size,size,size); + const float *v[2] = {mins.Base(), maxs.Base()}; + + Vector start, end; + { + for ( int i = 0; i < 3; i++ ) + { + int a0 = i; + int a1 = (i+1)%3; + int a2 = (i+2)%3; + for ( int j = 0; j < 2; j++ ) + { + for ( int k = 0; k < 2; k++ ) + { + start[a0] = v[0][a0]; + end[a0] = v[1][a0]; + start[a1] = v[j][a1]; + end[a1] = v[j][a1]; + start[a2] = v[k][a2]; + end[a2] = v[k][a2]; + GL_DrawLine( start, end-start, 1, r, g, b ); + } + } + } + } + for ( int axis = 0; axis < 3; axis++ ) + { + int a0 = axis; + int a1 = (axis+1)%3; + int a2 = (axis+2)%3; + start[a0] = v[0][a0]; + end[a0] = v[1][a0]; + start[a1] = 0.5f *(v[0][a1]+v[1][a1]); + end[a1] = 0.5f *(v[0][a1]+v[1][a1]); + start[a2] = 0.5f *(v[0][a2]+v[1][a2]); + end[a2] = 0.5f *(v[0][a2]+v[1][a2]); + GL_DrawLine( start, end-start, 1, r, g, b ); + } +} + + +void ReadPHYFile(const char *name, phyviewparams_t ¶ms ) +{ + FILE *fp = fopen (name, "rb"); + if (!fp) + Error ("Couldn't open %s", name); + + phyheader_t header; + + fread( &header, sizeof(header), 1, fp ); + if ( header.size != sizeof(header) || header.solidCount <= 0 ) + return; + + int pos = ftell( fp ); + fseek( fp, 0, SEEK_END ); + int fileSize = ftell(fp) - pos; + fseek( fp, pos, SEEK_SET ); + + char *buf = (char *)_alloca( fileSize ); + fread( buf, fileSize, 1, fp ); + fclose( fp ); + + vcollide_t collide; + physcollision->VCollideLoad( &collide, header.solidCount, (const char *)buf, fileSize ); +#if 0 + Vector start0( -3859.1199, -2050.8674, 64.031250 ); + Vector end0(-3859.2246, -2051.2817, 64.031250 ); + Vector modelPosition(-3840,-2068.0000, 82.889099); + QAngle modelAngles(0,90,0); + + { + Ray_t ray; + ray.Init( start0, end0, Vector(-16,-16,0), Vector(16,16,72)); + trace_t tr; + physcollision->TraceBox( ray, collide.solids[0], modelPosition, modelAngles, &tr ); + Assert(!tr.startsolid); + if ( tr.DidHit() ) + { + Ray_t ray2; + ray2.Init( tr.endpos, tr.endpos, Vector(-16,-16,0), Vector(16,16,72)); + trace_t tr2; + physcollision->TraceBox( ray2, collide.solids[0], modelPosition, modelAngles, &tr2 ); + Assert(!tr2.startsolid); + } + } +#endif +#if BENCHMARK_PHY + Benchmark_PHY( collide.solids[0] ); +#endif + AddVCollideToList( header, collide, params ); +} + +void ReadPolyFile (const char *name) +{ + char ext[4]; + Q_ExtractFileExtension( name, ext, 4 ); + + bool isPHY = !Q_stricmp( ext, "phy" ); + if ( isPHY ) + { + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); + physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); + if ( physcollision ) + { + phyviewparams_t params; + params.Defaults(); + glNewList (1, GL_COMPILE); + ReadPHYFile( name, params ); + Vector tmp = (params.mins + params.maxs) * 0.5; + tmp.CopyToArray(origin); + glEndList (); + } + } + else + { + // Read in polys... + ReadPolyFileType(name, 1, false); + + // Make list 3 just the lines... so we can draw outlines + ReadPolyFileType(name, 3, true); + } +} + +void ReadPortalFile (char *name) +{ + FILE *f; + int i, numverts; + float v[8]; + int c; + int r; + + // For Portal type reading... + char szDummy[80]; + int nNumLeafs; + int nNumPortals; + int nLeafIndex[2]; + + f = fopen (name, "r"); + if (!f) + Error ("Couldn't open %s", name); + + c = 0; + + glNewList (2, GL_COMPILE); + + // Read in header + fscanf(f, "%79s\n", szDummy); + fscanf(f, "%i\n", &nNumLeafs); + fscanf(f, "%i\n", &nNumPortals); + + glLineWidth(1.5); + + while (1) + { + r = fscanf(f, "%i %i %i ", &numverts, &nLeafIndex[0], &nLeafIndex[1]); + if (!r || r == EOF) + break; + + glBegin(GL_LINE_LOOP); + for (i=0 ; i= MAX_DISP_COUNT ) + break; + + fileCount = fscanf( pFile, "%f %f %f %f %f %f", + &dispPoints[dispPointCount][0], &dispPoints[dispPointCount][1], &dispPoints[dispPointCount][2], + &dispNormals[dispPointCount][0], &dispNormals[dispPointCount][1], &dispNormals[dispPointCount][2] ); + dispPointCount++; + + // end of file check + if( !fileCount || ( fileCount == EOF ) ) + break; + } + + fclose( pFile ); + + return TRUE; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void DrawDisplacementData( void ) +{ + int i, j; + int width, halfCount; + + GLUquadricObj *pObject = gluNewQuadric(); + + glEnable( GL_DEPTH_TEST ); + + for( i = 0; i < dispPointCount; i++ ) + { + // draw a sphere where the point is (in red) + glColor3f( 1.0f, 0.0f, 0.0f ); + glPushMatrix(); + glTranslatef( dispPoints[i][0], dispPoints[i][1], dispPoints[i][2] ); + gluSphere( pObject, 5, 5, 5 ); + glPopMatrix(); + + // draw the normal (in yellow) + glColor3f( 1.0f, 1.0f, 0.0f ); + glBegin( GL_LINES ); + glVertex3f( dispPoints[i][0], dispPoints[i][1], dispPoints[i][2] ); + glVertex3f( dispPoints[i][0] + ( dispNormals[i][0] * 50.0f ), dispPoints[i][1] + ( dispNormals[i][1] * 50.0f ), dispPoints[i][2] + ( dispNormals[i][2] * 50.0f ) ); + glEnd(); + } + + halfCount = dispPointCount / 2; + + width = sqrt( (float)halfCount ); + + glDisable( GL_CULL_FACE ); + + glColor3f( 0.0f, 0.0f, 1.0f ); + for( i = 0; i < width - 1; i++ ) + { + for( j = 0; j < width - 1; j++ ) + { + glBegin( GL_POLYGON ); + glVertex3f( dispPoints[i*width+j][0], dispPoints[i*width+j][1], dispPoints[i*width+j][2] ); + glVertex3f( dispPoints[(i+1)*width+j][0], dispPoints[(i+1)*width+j][1], dispPoints[(i+1)*width+j][2] ); + glVertex3f( dispPoints[(i+1)*width+(j+1)][0], dispPoints[(i+1)*width+(j+1)][1], dispPoints[(i+1)*width+(j+1)][2] ); + glVertex3f( dispPoints[i*width+(j+1)][0], dispPoints[i*width+(j+1)][1], dispPoints[i*width+(j+1)][2] ); + glEnd(); + } + } + +#if 0 + for( i = 0; i < width - 1; i++ ) + { + for( j = 0; j < width - 1; j++ ) + { + glBegin( GL_POLYGON ); + glVertex3f( dispPoints[halfCount+(i*width+j)][0], dispPoints[halfCount+(i*width+j)][1], dispPoints[halfCount+(i*width+j)][2] ); + glVertex3f( dispPoints[halfCount+((i+1)*width+j)][0], dispPoints[halfCount+(i+1)*width+j][1], dispPoints[halfCount+((i+1)*width+j)][2] ); + glVertex3f( dispPoints[halfCount+((i+1)*width+(j+1))][0], dispPoints[halfCount+(i+1)*width+(j+1)][1], dispPoints[halfCount+((i+1)*width+(j+1))][2] ); + glVertex3f( dispPoints[halfCount+(i*width+(j+1))][0], dispPoints[halfCount+(i*width+(j+1))][1], dispPoints[halfCount+(i*width+(j+1))][2] ); + glEnd(); + } + } +#endif + + glColor3f( 0.0f, 1.0f, 0.0f ); + for( i = 0; i < width - 1; i++ ) + { + for( j = 0; j < width - 1; j++ ) + { + glBegin( GL_POLYGON ); + glVertex3f( dispPoints[i*width+j][0] + ( dispNormals[i*width+j][0] * 150.0f ), + dispPoints[i*width+j][1] + ( dispNormals[i*width+j][1] * 150.0f ), + dispPoints[i*width+j][2] + ( dispNormals[i*width+j][2] * 150.0f ) ); + + glVertex3f( dispPoints[(i+1)*width+j][0] + ( dispNormals[(i+1)*width+j][0] * 150.0f ), + dispPoints[(i+1)*width+j][1] + ( dispNormals[(i+1)*width+j][1] * 150.0f ), + dispPoints[(i+1)*width+j][2] + ( dispNormals[(i+1)*width+j][2] * 150.0f ) ); + + glVertex3f( dispPoints[(i+1)*width+(j+1)][0] + ( dispNormals[(i+1)*width+(j+1)][0] * 150.0f ), + dispPoints[(i+1)*width+(j+1)][1] + ( dispNormals[(i+1)*width+(j+1)][1] * 150.0f ), + dispPoints[(i+1)*width+(j+1)][2] + ( dispNormals[(i+1)*width+(j+1)][2] * 150.0f ) ); + + glVertex3f( dispPoints[i*width+(j+1)][0] + ( dispNormals[i*width+(j+1)][0] * 150.0f ), + dispPoints[i*width+(j+1)][1] + ( dispNormals[i*width+(j+1)][1] * 150.0f ), + dispPoints[i*width+(j+1)][2] + ( dispNormals[i*width+(j+1)][2] * 150.0f ) ); + glEnd(); + } + } + + glDisable( GL_DEPTH_TEST ); + + glColor3f( 0.0f, 0.0f, 1.0f ); + for( i = 0; i < width - 1; i++ ) + { + for( j = 0; j < width - 1; j++ ) + { + glBegin( GL_LINE_LOOP ); + glVertex3f( dispPoints[i*width+j][0] + ( dispNormals[i*width+j][0] * 150.0f ), + dispPoints[i*width+j][1] + ( dispNormals[i*width+j][1] * 150.0f ), + dispPoints[i*width+j][2] + ( dispNormals[i*width+j][2] * 150.0f ) ); + + glVertex3f( dispPoints[(i+1)*width+j][0] + ( dispNormals[(i+1)*width+j][0] * 150.0f ), + dispPoints[(i+1)*width+j][1] + ( dispNormals[(i+1)*width+j][1] * 150.0f ), + dispPoints[(i+1)*width+j][2] + ( dispNormals[(i+1)*width+j][2] * 150.0f ) ); + + glVertex3f( dispPoints[(i+1)*width+(j+1)][0] + ( dispNormals[(i+1)*width+(j+1)][0] * 150.0f ), + dispPoints[(i+1)*width+(j+1)][1] + ( dispNormals[(i+1)*width+(j+1)][1] * 150.0f ), + dispPoints[(i+1)*width+(j+1)][2] + ( dispNormals[(i+1)*width+(j+1)][2] * 150.0f ) ); + + glVertex3f( dispPoints[i*width+(j+1)][0] + ( dispNormals[i*width+(j+1)][0] * 150.0f ), + dispPoints[i*width+(j+1)][1] + ( dispNormals[i*width+(j+1)][1] * 150.0f ), + dispPoints[i*width+(j+1)][2] + ( dispNormals[i*width+(j+1)][2] * 150.0f ) ); + glEnd(); + } + } + + + gluDeleteQuadric( pObject ); +} + + +//===================================================================== + +BOOL bSetupPixelFormat(HDC hDC) +{ + static PIXELFORMATDESCRIPTOR pfd = { + sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd + 1, // version number + PFD_DRAW_TO_WINDOW | // support window + PFD_SUPPORT_OPENGL | // support OpenGL + PFD_DOUBLEBUFFER, // double buffered + PFD_TYPE_RGBA, // RGBA type + 24, // 24-bit color depth + 0, 0, 0, 0, 0, 0, // color bits ignored + 0, // no alpha buffer + 0, // shift bit ignored + 0, // no accumulation buffer + 0, 0, 0, 0, // accum bits ignored + 32, // 32-bit z-buffer + 0, // no stencil buffer + 0, // no auxiliary buffer + PFD_MAIN_PLANE, // main layer + 0, // reserved + 0, 0, 0 // layer masks ignored + }; + + int pixelformat = 0; + + if ( (pixelformat = ChoosePixelFormat(hDC, &pfd)) == 0 ) + Error ("ChoosePixelFormat failed"); + + if (!SetPixelFormat(hDC, pixelformat, &pfd)) + Error ("SetPixelFormat failed"); + + return TRUE; +} + +/* +============ +CameraWndProc +============ +*/ +LONG WINAPI WCam_WndProc ( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) +{ + LONG lRet = 1; + RECT rect; + + GetClientRect(hWnd, &rect); + + switch (uMsg) + { + case WM_CREATE: + { + camdc = GetDC(hWnd); + bSetupPixelFormat(camdc); + + baseRC = wglCreateContext( camdc ); + if (!baseRC) + Error ("wglCreateContext failed"); + if (!wglMakeCurrent( camdc, baseRC )) + Error ("wglMakeCurrent failed"); + glCullFace(GL_FRONT); + glEnable(GL_CULL_FACE); + } + break; + case WM_PAINT: + { + PAINTSTRUCT ps; + + BeginPaint(hWnd, &ps); + if (!wglMakeCurrent( camdc, baseRC )) + Error ("wglMakeCurrent failed"); + Draw (); + SwapBuffers(camdc); + EndPaint(hWnd, &ps); + } + break; + + case WM_KEYDOWN: + KeyDown (wParam); + AppKeyDown( wParam ); + break; + + case WM_KEYUP: + AppKeyUp( wParam ); + break; + + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONDOWN: + SetCapture (camerawindow); + ShowCursor( FALSE ); + g_Capture = TRUE; + break; + + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_LBUTTONUP: + if (! (wParam & (MK_LBUTTON|MK_RBUTTON|MK_MBUTTON))) + { + g_Capture = FALSE; + ReleaseCapture (); + ShowCursor( TRUE ); + } + break; + + case WM_SIZE: + InvalidateRect(camerawindow, NULL, false); + break; + case WM_NCCALCSIZE:// don't let windows copy pixels + lRet = DefWindowProc (hWnd, uMsg, wParam, lParam); + return WVR_REDRAW; + case WM_CLOSE: + /* call destroy window to cleanup and go away */ + DestroyWindow (hWnd); + break; + + case WM_DESTROY: + { + HGLRC hRC; + HDC hDC; + + /* release and free the device context and rendering context */ + hRC = wglGetCurrentContext(); + hDC = wglGetCurrentDC(); + + wglMakeCurrent(NULL, NULL); + + if (hRC) + wglDeleteContext(hRC); + if (hDC) + ReleaseDC(hWnd, hDC); + + PostQuitMessage (0); + } + break; + + default: + /* pass all unhandled messages to DefWindowProc */ + lRet = DefWindowProc (hWnd, uMsg, wParam, lParam); + break; + } + + /* return 1 if handled message, 0 if not */ + return lRet; +} + + +/* +============== +WCam_Register +============== +*/ +void WCam_Register (HINSTANCE hInstance) +{ + WNDCLASS wc; + + /* Register the camera class */ + memset (&wc, 0, sizeof(wc)); + + wc.style = 0; + wc.lpfnWndProc = (WNDPROC)WCam_WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = hInstance; + wc.hIcon = 0; + wc.hCursor = LoadCursor (NULL,IDC_ARROW); + wc.hbrBackground = NULL; + wc.lpszMenuName = 0; + wc.lpszClassName = "camera"; + + if (!RegisterClass (&wc) ) + Error ("WCam_Register: failed"); +} + + +void WCam_Create (HINSTANCE hInstance) +{ + // Center it + int nScx, nScy; + int w, h; + int x, y; + + WCam_Register (hInstance); + + w = ::width; + h = ::height; + + nScx = GetSystemMetrics(SM_CXSCREEN); + nScy = GetSystemMetrics(SM_CYSCREEN); + + + x = (nScx - w)/2; + y = (nScy - h)/2; + + camerawindow = CreateWindow ("camera" , + "Camera View", + WS_OVERLAPPED | + WS_CAPTION | + WS_SYSMENU | + WS_THICKFRAME | + WS_MAXIMIZEBOX | + WS_CLIPSIBLINGS | + WS_CLIPCHILDREN, + + x, + y, + w, + h, // size + + NULL, // parent window + 0, // no menu + hInstance, + 0); + if (!camerawindow) + Error ("Couldn't create camerawindow"); + + ShowWindow (camerawindow, SW_SHOWDEFAULT); +} + + +void AppKeyDown( int key ) +{ + key &= 0xFF; + + g_Keys[key] = 0x03; // add debounce bit +} + +void AppKeyUp( int key ) +{ + key &= 0xFF; + + g_Keys[key] &= 0x02; +} + +void AppRender( void ) +{ + static double lastTime = 0; + double time = timeGetTime() * 0.001f; + double frametime = time - lastTime; + + // clamp too large frames (like first frame) + if ( frametime > 0.2 ) + frametime = 0.2; + lastTime = time; + + if (!wglMakeCurrent( camdc, baseRC )) + Error ("wglMakeCurrent failed"); + + Cam_Update( frametime ); + + if (g_Update) + { + Draw (); + SwapBuffers(camdc); + g_Update = FALSE; + } + else + { + Sleep( 1.0 ); + } +} + +SpewRetval_t Sys_SpewFunc( SpewType_t type, const char *pMsg ) +{ + OutputDebugString( pMsg ); + if( type == SPEW_ASSERT ) + return SPEW_DEBUGGER; + else if( type == SPEW_ERROR ) + return SPEW_ABORT; + else + return SPEW_CONTINUE; +} + + +/* +================== +WinMain + +================== +*/ +int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance + ,LPSTR lpCmdLine, int nCmdShow) +{ + CommandLine()->CreateCmdLine( Plat_GetCommandLine() ); + + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); + MSG msg; + + if (!lpCmdLine || !lpCmdLine[0]) + Error ("No file specified"); + + main_instance = hInstance; + + WCam_Create (hInstance); + + // Last argument is the file name + const char *pFileName = CommandLine()->GetParm( CommandLine()->ParmCount() - 1 ); + CmdLib_InitFileSystem( pFileName ); + + if ( CommandLine()->CheckParm( "-portal") ) + { + g_bReadPortals = 1; + g_nPortalHighlight = CommandLine()->ParmValue( "-portalhighlight", -1 ); + g_nLeafHighlight = CommandLine()->ParmValue( "-leafhighlight", -1 ); + } + g_flMovementSpeed = CommandLine()->ParmValue( "-speed", 320 ); + + if( CommandLine()->CheckParm( "-disp") ) + { + ReadDisplacementFile( pFileName ); + g_bDisp = TRUE; + } + SpewOutputFunc( Sys_SpewFunc ); + + // Any chunk of original left is the filename. + if (pFileName && pFileName[0] && !g_bDisp ) + { + ReadPolyFile( pFileName ); + } + + if (g_bReadPortals) + { + // Copy file again and this time look for the . from .gl? so we can concatenate .prt + // and open the portal file. + char szTempCmd[MAX_PATH]; + strcpy(szTempCmd, pFileName); + char *pTmp = szTempCmd; + while (pTmp && *pTmp && *pTmp != '.') + { + pTmp++; + } + + *pTmp = '\0'; + strcat(szTempCmd, ".prt"); + + ReadPortalFile(szTempCmd); + }; + + /* main window message loop */ + while (g_Active) + { + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + AppRender(); + } + + /* return success of application */ + return TRUE; +} + diff --git a/mp/src/utils/glview/glview.vpc b/mp/src/utils/glview/glview.vpc index 3045d128..fa7f180c 100644 --- a/mp/src/utils/glview/glview.vpc +++ b/mp/src/utils/glview/glview.vpc @@ -1,55 +1,55 @@ -//----------------------------------------------------------------------------- -// GLVIEW.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" - -$Include "$SRCDIR\vpc_scripts\source_exe_base.vpc" - -$Configuration -{ - $Compiler - { - $AdditionalIncludeDirectories "$BASE;..\common" - $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE" - } - - $Linker - { - $AdditionalDependencies "$BASE glu32.lib opengl32.lib odbc32.lib odbccp32.lib winmm.lib" - $AdditionalDependencies "$BASE glaux.lib" [!$VS2010] - } -} - -$Project "Glview" -{ - $Folder "Source Files" - { - $File "glview.cpp" - - $Folder "common files" - { - $File "..\common\cmdlib.cpp" - $File "$SRCDIR\public\filesystem_helpers.cpp" - $File "$SRCDIR\public\filesystem_init.cpp" - $File "..\common\filesystem_tools.cpp" - $File "..\common\physdll.cpp" - } - } - - $Folder "Header Files" - { - $File "..\common\cmdlib.h" - $File "glos.h" - $File "$SRCDIR\public\mathlib\mathlib.h" - } - - $Folder "Link Libraries" - { - $Lib mathlib - $Lib tier2 - } -} +//----------------------------------------------------------------------------- +// GLVIEW.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE;..\common" + $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE" + } + + $Linker + { + $AdditionalDependencies "$BASE glu32.lib opengl32.lib odbc32.lib odbccp32.lib winmm.lib" + $AdditionalDependencies "$BASE glaux.lib" [!$VS2010] + } +} + +$Project "Glview" +{ + $Folder "Source Files" + { + $File "glview.cpp" + + $Folder "common files" + { + $File "..\common\cmdlib.cpp" + $File "$SRCDIR\public\filesystem_helpers.cpp" + $File "$SRCDIR\public\filesystem_init.cpp" + $File "..\common\filesystem_tools.cpp" + $File "..\common\physdll.cpp" + } + } + + $Folder "Header Files" + { + $File "..\common\cmdlib.h" + $File "glos.h" + $File "$SRCDIR\public\mathlib\mathlib.h" + } + + $Folder "Link Libraries" + { + $Lib mathlib + $Lib tier2 + } +} diff --git a/mp/src/utils/height2normal/height2normal.cpp b/mp/src/utils/height2normal/height2normal.cpp index f0dff189..5ff77fa9 100644 --- a/mp/src/utils/height2normal/height2normal.cpp +++ b/mp/src/utils/height2normal/height2normal.cpp @@ -1,343 +1,343 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//===========================================================================// -#include -#include -#include -#include "bitmap/imageformat.h" -#include "tier1/strtools.h" -#include "mathlib/mathlib.h" -#include "bitmap/TGAWriter.h" -#include "bitmap/TGALoader.h" -#include -#include -#include "tier1/utlbuffer.h" -#include "tier2/tier2.h" -#include "filesystem.h" - -static bool g_NoPause = false; -static bool g_Quiet = false; - -static void Pause( void ) -{ - if( !g_NoPause ) - { - printf( "Hit a key to continue\n" ); - getch(); - } -} - -static bool ImageRGBA8888HasAlpha( unsigned char *pImage, int numTexels ) -{ - int i; - for( i = 0; i < numTexels; i++ ) - { - if( pImage[i*4+3] != 255 ) - { - return true; - } - } - return false; -} - -static bool GetKeyValueFromBuffer( CUtlBuffer &buf, char **key, char **val ) -{ - char stringBuf[2048]; - while( buf.IsValid() ) - { - buf.GetLine( stringBuf, sizeof(stringBuf) ); - char *scan = stringBuf; - // search for the first quote for the key. - while( 1 ) - { - if( *scan == '\"' ) - { - *key = ++scan; - break; - } - if( *scan == '#' ) - { - goto next_line; // comment - } - if( *scan == '\0' ) - { - goto next_line; // end of line. - } - scan++; - } - // read the key until another quote. - while( 1 ) - { - if( *scan == '\"' ) - { - *scan = '\0'; - scan++; - break; - } - if( *scan == '\0' ) - { - goto next_line; - } - scan++; - } - // search for the first quote for the value. - while( 1 ) - { - if( *scan == '\"' ) - { - *val = ++scan; - break; - } - if( *scan == '#' ) - { - goto next_line; // comment - } - if( *scan == '\0' ) - { - goto next_line; // end of line. - } - scan++; - } - // read the value until another quote. - while( 1 ) - { - if( *scan == '\"' ) - { - *scan = '\0'; - scan++; - // got a key and a value, so get the hell out of here. - return true; - } - if( *scan == '\0' ) - { - goto next_line; - } - scan++; - } -next_line: - ; - } - return false; -} - -static void LoadConfigFile( const char *pFileName, float *bumpScale, int *startFrame, int *endFrame ) -{ - CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); - if ( !g_pFullFileSystem->ReadFile( pFileName, NULL, buf ) ) - { - fprintf( stderr, "Can't open: %s\n", pFileName ); - Pause(); - exit( -1 ); - } - - char *key = NULL; - char *val = NULL; - while( GetKeyValueFromBuffer( buf, &key, &val ) ) - { - if( stricmp( key, "bumpscale" ) == 0 ) - { - *bumpScale = atof( val ); - } - if( stricmp( key, "startframe" ) == 0 ) - { - *startFrame = atoi( val ); - } - else if( stricmp( key, "endframe" ) == 0 ) - { - *endFrame = atoi( val ); - } - } -} - -static void Usage() -{ - fprintf( stderr, "Usage: height2normal [-nopause] [-quiet] tex1_normal.txt tex2_normal.txt . . .\n" ); - fprintf( stderr, "-quiet : don't print anything out, don't pause for input\n" ); - fprintf( stderr, "-nopause : don't pause for input\n" ); - Pause(); - exit( -1 ); -} - -void ProcessFiles( const char *pNormalFileNameWithoutExtension, - int startFrame, int endFrame, - float bumpScale ) -{ - static char heightTGAFileName[1024]; - static char normalTGAFileName[1024]; - static char buf[1024]; - bool animated = !( startFrame == -1 || endFrame == -1 ); - int numFrames = endFrame - startFrame + 1; - int frameID; - - for( frameID = 0; frameID < numFrames; frameID++ ) - { - if( animated ) - { - sprintf( normalTGAFileName, "%s%03d.tga", pNormalFileNameWithoutExtension, frameID + startFrame ); - } - else - { - sprintf( normalTGAFileName, "%s.tga", pNormalFileNameWithoutExtension ); - } - if( !Q_stristr( pNormalFileNameWithoutExtension, "_normal" ) ) - { - fprintf( stderr, "ERROR: config file name must end in _normal.txt\n" ); - return; - } - - strcpy( buf, pNormalFileNameWithoutExtension ); - - // Strip '_normal' off the end because we're looking for '_height' - char *pcUnderscore = Q_stristr( buf, "_normal" ); - *pcUnderscore = NULL; - - if( animated ) - { - sprintf( heightTGAFileName, "%s_height%03d.tga", buf, frameID + startFrame ); - } - else - { - sprintf( heightTGAFileName, "%s_height.tga", buf ); - } - - enum ImageFormat imageFormat; - int width, height; - float sourceGamma; - CUtlBuffer buf; - if ( !g_pFullFileSystem->ReadFile( heightTGAFileName, NULL, buf ) ) - { - fprintf( stderr, "%s not found\n", heightTGAFileName ); - return; - } - - if ( !TGALoader::GetInfo( buf, &width, &height, &imageFormat, &sourceGamma ) ) - { - fprintf( stderr, "error in %s\n", heightTGAFileName ); - return; - } - - int memRequired = ImageLoader::GetMemRequired( width, height, 1, IMAGE_FORMAT_IA88, false ); - unsigned char *pImageIA88 = new unsigned char[memRequired]; - - buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); - TGALoader::Load( pImageIA88, buf, width, height, IMAGE_FORMAT_IA88, sourceGamma, false ); - - memRequired = ImageLoader::GetMemRequired( width, height, 1, IMAGE_FORMAT_RGBA8888, false ); - unsigned char *pImageRGBA8888 = new unsigned char[memRequired]; - ImageLoader::ConvertIA88ImageToNormalMapRGBA8888( pImageIA88, width, height, pImageRGBA8888, bumpScale ); - - CUtlBuffer normalBuf; - ImageLoader::NormalizeNormalMapRGBA8888( pImageRGBA8888, width * height ); - if( ImageRGBA8888HasAlpha( pImageRGBA8888, width * height ) ) - { - TGAWriter::WriteToBuffer( pImageRGBA8888, normalBuf, width, height, IMAGE_FORMAT_RGBA8888, IMAGE_FORMAT_RGBA8888 ); - } - else - { - memRequired = ImageLoader::GetMemRequired( width, height, 1, IMAGE_FORMAT_RGB888, false ); - unsigned char *pImageRGB888 = new unsigned char[memRequired]; - ImageLoader::ConvertImageFormat( pImageRGBA8888, IMAGE_FORMAT_RGBA8888, - pImageRGB888, IMAGE_FORMAT_RGB888, width, height, 0, 0 ); - TGAWriter::WriteToBuffer( pImageRGB888, normalBuf, width, height, IMAGE_FORMAT_RGB888, IMAGE_FORMAT_RGB888 ); - delete [] pImageRGB888; - } - if ( !g_pFullFileSystem->WriteFile( normalTGAFileName, NULL, normalBuf ) ) - { - fprintf( stderr, "unable to write %s\n", normalTGAFileName ); - return; - } - delete [] pImageIA88; - delete [] pImageRGBA8888; - } -} - -int main( int argc, char **argv ) -{ - if( argc < 2 ) - { - Usage(); - } - MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); - InitDefaultFileSystem(); - - int i = 1; - while( i < argc ) - { - if( stricmp( argv[i], "-quiet" ) == 0 ) - { - i++; - g_Quiet = true; - g_NoPause = true; // no point in pausing if we aren't going to print anything out. - } - if( stricmp( argv[i], "-nopause" ) == 0 ) - { - i++; - g_NoPause = true; - } - else - { - break; - } - } - - char pCurrentDirectory[MAX_PATH]; - if ( _getcwd( pCurrentDirectory, sizeof(pCurrentDirectory) ) == NULL ) - { - fprintf( stderr, "Unable to get the current directory\n" ); - return -1; - } - - Q_FixSlashes( pCurrentDirectory ); - Q_StripTrailingSlash( pCurrentDirectory ); - - for( ; i < argc; i++ ) - { - static char normalFileNameWithoutExtension[1024]; - char *pFileName; - if ( !Q_IsAbsolutePath( argv[i] ) ) - { - Q_snprintf( normalFileNameWithoutExtension, sizeof(normalFileNameWithoutExtension), "%s\\%s", pCurrentDirectory, argv[i] ); - pFileName = normalFileNameWithoutExtension; - } - else - { - pFileName = argv[i]; - } - - if( !g_Quiet ) - { - printf( "file: %s\n", pFileName ); - } - float bumpScale = -1.0f; - int startFrame = -1; - int endFrame = -1; - LoadConfigFile( pFileName, &bumpScale, &startFrame, &endFrame ); - if( bumpScale == -1.0f ) - { - fprintf( stderr, "Must specify \"bumpscale\" in config file\n" ); - Pause(); - continue; - } - if( ( startFrame == -1 && endFrame != -1 ) || - ( startFrame != -1 && endFrame == -1 ) ) - { - fprintf( stderr, "ERROR: If you use startframe, you must use endframe, and vice versa.\n" ); - Pause(); - continue; - } - if( !g_Quiet ) - { - printf( "\tbumpscale: %f\n", bumpScale ); - } - - Q_StripExtension( pFileName, normalFileNameWithoutExtension, sizeof( normalFileNameWithoutExtension ) ); - ProcessFiles( normalFileNameWithoutExtension, - startFrame, endFrame, - bumpScale ); - } - return 0; -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#include +#include +#include +#include "bitmap/imageformat.h" +#include "tier1/strtools.h" +#include "mathlib/mathlib.h" +#include "bitmap/TGAWriter.h" +#include "bitmap/TGALoader.h" +#include +#include +#include "tier1/utlbuffer.h" +#include "tier2/tier2.h" +#include "filesystem.h" + +static bool g_NoPause = false; +static bool g_Quiet = false; + +static void Pause( void ) +{ + if( !g_NoPause ) + { + printf( "Hit a key to continue\n" ); + getch(); + } +} + +static bool ImageRGBA8888HasAlpha( unsigned char *pImage, int numTexels ) +{ + int i; + for( i = 0; i < numTexels; i++ ) + { + if( pImage[i*4+3] != 255 ) + { + return true; + } + } + return false; +} + +static bool GetKeyValueFromBuffer( CUtlBuffer &buf, char **key, char **val ) +{ + char stringBuf[2048]; + while( buf.IsValid() ) + { + buf.GetLine( stringBuf, sizeof(stringBuf) ); + char *scan = stringBuf; + // search for the first quote for the key. + while( 1 ) + { + if( *scan == '\"' ) + { + *key = ++scan; + break; + } + if( *scan == '#' ) + { + goto next_line; // comment + } + if( *scan == '\0' ) + { + goto next_line; // end of line. + } + scan++; + } + // read the key until another quote. + while( 1 ) + { + if( *scan == '\"' ) + { + *scan = '\0'; + scan++; + break; + } + if( *scan == '\0' ) + { + goto next_line; + } + scan++; + } + // search for the first quote for the value. + while( 1 ) + { + if( *scan == '\"' ) + { + *val = ++scan; + break; + } + if( *scan == '#' ) + { + goto next_line; // comment + } + if( *scan == '\0' ) + { + goto next_line; // end of line. + } + scan++; + } + // read the value until another quote. + while( 1 ) + { + if( *scan == '\"' ) + { + *scan = '\0'; + scan++; + // got a key and a value, so get the hell out of here. + return true; + } + if( *scan == '\0' ) + { + goto next_line; + } + scan++; + } +next_line: + ; + } + return false; +} + +static void LoadConfigFile( const char *pFileName, float *bumpScale, int *startFrame, int *endFrame ) +{ + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + if ( !g_pFullFileSystem->ReadFile( pFileName, NULL, buf ) ) + { + fprintf( stderr, "Can't open: %s\n", pFileName ); + Pause(); + exit( -1 ); + } + + char *key = NULL; + char *val = NULL; + while( GetKeyValueFromBuffer( buf, &key, &val ) ) + { + if( stricmp( key, "bumpscale" ) == 0 ) + { + *bumpScale = atof( val ); + } + if( stricmp( key, "startframe" ) == 0 ) + { + *startFrame = atoi( val ); + } + else if( stricmp( key, "endframe" ) == 0 ) + { + *endFrame = atoi( val ); + } + } +} + +static void Usage() +{ + fprintf( stderr, "Usage: height2normal [-nopause] [-quiet] tex1_normal.txt tex2_normal.txt . . .\n" ); + fprintf( stderr, "-quiet : don't print anything out, don't pause for input\n" ); + fprintf( stderr, "-nopause : don't pause for input\n" ); + Pause(); + exit( -1 ); +} + +void ProcessFiles( const char *pNormalFileNameWithoutExtension, + int startFrame, int endFrame, + float bumpScale ) +{ + static char heightTGAFileName[1024]; + static char normalTGAFileName[1024]; + static char buf[1024]; + bool animated = !( startFrame == -1 || endFrame == -1 ); + int numFrames = endFrame - startFrame + 1; + int frameID; + + for( frameID = 0; frameID < numFrames; frameID++ ) + { + if( animated ) + { + sprintf( normalTGAFileName, "%s%03d.tga", pNormalFileNameWithoutExtension, frameID + startFrame ); + } + else + { + sprintf( normalTGAFileName, "%s.tga", pNormalFileNameWithoutExtension ); + } + if( !Q_stristr( pNormalFileNameWithoutExtension, "_normal" ) ) + { + fprintf( stderr, "ERROR: config file name must end in _normal.txt\n" ); + return; + } + + strcpy( buf, pNormalFileNameWithoutExtension ); + + // Strip '_normal' off the end because we're looking for '_height' + char *pcUnderscore = Q_stristr( buf, "_normal" ); + *pcUnderscore = NULL; + + if( animated ) + { + sprintf( heightTGAFileName, "%s_height%03d.tga", buf, frameID + startFrame ); + } + else + { + sprintf( heightTGAFileName, "%s_height.tga", buf ); + } + + enum ImageFormat imageFormat; + int width, height; + float sourceGamma; + CUtlBuffer buf; + if ( !g_pFullFileSystem->ReadFile( heightTGAFileName, NULL, buf ) ) + { + fprintf( stderr, "%s not found\n", heightTGAFileName ); + return; + } + + if ( !TGALoader::GetInfo( buf, &width, &height, &imageFormat, &sourceGamma ) ) + { + fprintf( stderr, "error in %s\n", heightTGAFileName ); + return; + } + + int memRequired = ImageLoader::GetMemRequired( width, height, 1, IMAGE_FORMAT_IA88, false ); + unsigned char *pImageIA88 = new unsigned char[memRequired]; + + buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + TGALoader::Load( pImageIA88, buf, width, height, IMAGE_FORMAT_IA88, sourceGamma, false ); + + memRequired = ImageLoader::GetMemRequired( width, height, 1, IMAGE_FORMAT_RGBA8888, false ); + unsigned char *pImageRGBA8888 = new unsigned char[memRequired]; + ImageLoader::ConvertIA88ImageToNormalMapRGBA8888( pImageIA88, width, height, pImageRGBA8888, bumpScale ); + + CUtlBuffer normalBuf; + ImageLoader::NormalizeNormalMapRGBA8888( pImageRGBA8888, width * height ); + if( ImageRGBA8888HasAlpha( pImageRGBA8888, width * height ) ) + { + TGAWriter::WriteToBuffer( pImageRGBA8888, normalBuf, width, height, IMAGE_FORMAT_RGBA8888, IMAGE_FORMAT_RGBA8888 ); + } + else + { + memRequired = ImageLoader::GetMemRequired( width, height, 1, IMAGE_FORMAT_RGB888, false ); + unsigned char *pImageRGB888 = new unsigned char[memRequired]; + ImageLoader::ConvertImageFormat( pImageRGBA8888, IMAGE_FORMAT_RGBA8888, + pImageRGB888, IMAGE_FORMAT_RGB888, width, height, 0, 0 ); + TGAWriter::WriteToBuffer( pImageRGB888, normalBuf, width, height, IMAGE_FORMAT_RGB888, IMAGE_FORMAT_RGB888 ); + delete [] pImageRGB888; + } + if ( !g_pFullFileSystem->WriteFile( normalTGAFileName, NULL, normalBuf ) ) + { + fprintf( stderr, "unable to write %s\n", normalTGAFileName ); + return; + } + delete [] pImageIA88; + delete [] pImageRGBA8888; + } +} + +int main( int argc, char **argv ) +{ + if( argc < 2 ) + { + Usage(); + } + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); + InitDefaultFileSystem(); + + int i = 1; + while( i < argc ) + { + if( stricmp( argv[i], "-quiet" ) == 0 ) + { + i++; + g_Quiet = true; + g_NoPause = true; // no point in pausing if we aren't going to print anything out. + } + if( stricmp( argv[i], "-nopause" ) == 0 ) + { + i++; + g_NoPause = true; + } + else + { + break; + } + } + + char pCurrentDirectory[MAX_PATH]; + if ( _getcwd( pCurrentDirectory, sizeof(pCurrentDirectory) ) == NULL ) + { + fprintf( stderr, "Unable to get the current directory\n" ); + return -1; + } + + Q_FixSlashes( pCurrentDirectory ); + Q_StripTrailingSlash( pCurrentDirectory ); + + for( ; i < argc; i++ ) + { + static char normalFileNameWithoutExtension[1024]; + char *pFileName; + if ( !Q_IsAbsolutePath( argv[i] ) ) + { + Q_snprintf( normalFileNameWithoutExtension, sizeof(normalFileNameWithoutExtension), "%s\\%s", pCurrentDirectory, argv[i] ); + pFileName = normalFileNameWithoutExtension; + } + else + { + pFileName = argv[i]; + } + + if( !g_Quiet ) + { + printf( "file: %s\n", pFileName ); + } + float bumpScale = -1.0f; + int startFrame = -1; + int endFrame = -1; + LoadConfigFile( pFileName, &bumpScale, &startFrame, &endFrame ); + if( bumpScale == -1.0f ) + { + fprintf( stderr, "Must specify \"bumpscale\" in config file\n" ); + Pause(); + continue; + } + if( ( startFrame == -1 && endFrame != -1 ) || + ( startFrame != -1 && endFrame == -1 ) ) + { + fprintf( stderr, "ERROR: If you use startframe, you must use endframe, and vice versa.\n" ); + Pause(); + continue; + } + if( !g_Quiet ) + { + printf( "\tbumpscale: %f\n", bumpScale ); + } + + Q_StripExtension( pFileName, normalFileNameWithoutExtension, sizeof( normalFileNameWithoutExtension ) ); + ProcessFiles( normalFileNameWithoutExtension, + startFrame, endFrame, + bumpScale ); + } + return 0; +} diff --git a/mp/src/utils/height2normal/height2normal.vpc b/mp/src/utils/height2normal/height2normal.vpc index dc1cb73b..be330ee4 100644 --- a/mp/src/utils/height2normal/height2normal.vpc +++ b/mp/src/utils/height2normal/height2normal.vpc @@ -1,41 +1,41 @@ -//----------------------------------------------------------------------------- -// HEIGHT2NORMAL.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" - -$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" - -$Configuration -{ - $Compiler - { - $AdditionalIncludeDirectories "$BASE,..\common" - $PreprocessorDefinitions "_HAS_ITERATOR_DEBUGGING=0;_DEBUG;_WIN32;_CONSOLE" - } -} - -$Project "Height2normal" -{ - $Folder "Source Files" - { - -$File "$SRCDIR\public\tier0\memoverride.cpp" - $File "height2normal.cpp" - } - - $Folder "Header Files" - { - $File "$SRCDIR\public\tier1\interface.h" - $File "$SRCDIR\public\tier1\utlbuffer.h" - } - - $Folder "Link Libraries" - { - $Lib bitmap - $Lib mathlib - $Lib tier2 - } -} +//----------------------------------------------------------------------------- +// HEIGHT2NORMAL.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\common" + $PreprocessorDefinitions "_HAS_ITERATOR_DEBUGGING=0;_DEBUG;_WIN32;_CONSOLE" + } +} + +$Project "Height2normal" +{ + $Folder "Source Files" + { + -$File "$SRCDIR\public\tier0\memoverride.cpp" + $File "height2normal.cpp" + } + + $Folder "Header Files" + { + $File "$SRCDIR\public\tier1\interface.h" + $File "$SRCDIR\public\tier1\utlbuffer.h" + } + + $Folder "Link Libraries" + { + $Lib bitmap + $Lib mathlib + $Lib tier2 + } +} diff --git a/mp/src/utils/motionmapper/motionmapper.cpp b/mp/src/utils/motionmapper/motionmapper.cpp index 6961e8ec..6825ef39 100644 --- a/mp/src/utils/motionmapper/motionmapper.cpp +++ b/mp/src/utils/motionmapper/motionmapper.cpp @@ -1,3272 +1,3272 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -#include -#include -#include -#include -#include "filesystem_tools.h" -#include "cmdlib.h" -#include "scriplib.h" -#include "mathlib/mathlib.h" -#define EXTERN -#include "studio.h" -#include "motionmapper.h" -#include "tier1/strtools.h" -#include "tier0/icommandline.h" -#include "utldict.h" -#include -#include "UtlBuffer.h" -#include "utlsymbol.h" - -bool g_quiet = false; -bool g_verbose = false; -char g_outfile[1024]; -bool uselogfile = false; - -char g_szFilename[1024]; -FILE *g_fpInput; -char g_szLine[4096]; -int g_iLinecount; - -bool g_bZBrush = false; -bool g_bGaveMissingBoneWarning = false; - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : depth - -// *fmt - -// ... - -//----------------------------------------------------------------------------- -void vprint( int depth, const char *fmt, ... ) -{ - char string[ 8192 ]; - va_list va; - va_start( va, fmt ); - V_vsprintf_safe( string, fmt, va ); - va_end( va ); - - FILE *fp = NULL; - - if ( uselogfile ) - { - fp = fopen( "log.txt", "ab" ); - } - - while ( depth-- > 0 ) - { - vprint( 0, " " ); - OutputDebugString( " " ); - if ( fp ) - { - fprintf( fp, " " ); - } - } - - ::printf( "%s", string ); - OutputDebugString( string ); - - if ( fp ) - { - char *p = string; - while ( *p ) - { - if ( *p == '\n' ) - { - fputc( '\r', fp ); - } - fputc( *p, fp ); - p++; - } - fclose( fp ); - } -} - - -int k_memtotal; -void *kalloc( int num, int size ) -{ - // vprint( 0, "calloc( %d, %d )\n", num, size ); - // vprint( 0, "%d ", num * size ); - k_memtotal += num * size; - return calloc( num, size ); -} - -void kmemset( void *ptr, int value, int size ) -{ - // vprint( 0, "kmemset( %x, %d, %d )\n", ptr, value, size ); - memset( ptr, value, size ); - return; -} - -static bool g_bFirstWarning = true; - -void MdlWarning( const char *fmt, ... ) -{ - va_list args; - static char output[1024]; - - if (g_quiet) - { - if (g_bFirstWarning) - { - vprint( 0, "%s :\n", fullpath ); - g_bFirstWarning = false; - } - vprint( 0, "\t"); - } - - vprint( 0, "WARNING: "); - va_start( args, fmt ); - vprint( 0, fmt, args ); -} - - -void MdlError( char const *fmt, ... ) -{ - va_list args; - - if (g_quiet) - { - if (g_bFirstWarning) - { - vprint( 0, "%s :\n", fullpath ); - g_bFirstWarning = false; - } - vprint( 0, "\t"); - } - - vprint( 0, "ERROR: "); - va_start( args, fmt ); - vprint( 0, fmt, args ); - - exit( -1 ); -} - -int OpenGlobalFile( char *src ) -{ - int time1; - char filename[1024]; - - // local copy of string - strcpy( filename, ExpandPath( src ) ); - - // Ummm, path sanity checking - int pathLength; - int numBasePaths = CmdLib_GetNumBasePaths(); - // This is kinda gross. . . doing the same work in cmdlib on SafeOpenRead. - if( CmdLib_HasBasePath( filename, pathLength ) ) - { - char tmp[1024]; - int i; - for( i = 0; i < numBasePaths; i++ ) - { - strcpy( tmp, CmdLib_GetBasePath( i ) ); - strcat( tmp, filename + pathLength ); - - time1 = FileTime( tmp ); - if( time1 != -1 ) - { - if ((g_fpInput = fopen(tmp, "r")) == 0) - { - MdlWarning( "reader: could not open file '%s'\n", src ); - return 0; - } - else - { - return 1; - } - } - } - return 0; - } - else - { - time1 = FileTime (filename); - if (time1 == -1) - return 0; - - // Whoohooo, FOPEN! - if ((g_fpInput = fopen(filename, "r")) == 0) - { - MdlWarning( "reader: could not open file '%s'\n", src ); - return 0; - } - - return 1; - } -} - -bool IsEnd( char const* pLine ) -{ - if (strncmp( "end", pLine, 3 ) != 0) - return false; - return (pLine[3] == '\0') || (pLine[3] == '\n'); -} - - -//Wrong name for the use of it. -void scale_vertex( Vector &org ) -{ - org[0] = org[0] * g_currentscale; - org[1] = org[1] * g_currentscale; - org[2] = org[2] * g_currentscale; -} - - -void clip_rotations( RadianEuler& rot ) -{ - int j; - // clip everything to : -M_PI <= x < M_PI - - for (j = 0; j < 3; j++) { - while (rot[j] >= M_PI) - rot[j] -= M_PI*2; - while (rot[j] < -M_PI) - rot[j] += M_PI*2; - } -} - - -void clip_rotations( Vector& rot ) -{ - int j; - // clip everything to : -180 <= x < 180 - - for (j = 0; j < 3; j++) { - while (rot[j] >= 180) - rot[j] -= 180*2; - while (rot[j] < -180) - rot[j] += 180*2; - } -} - - -void Build_Reference( s_source_t *psource) -{ - int i, parent; - Vector angle; - - for (i = 0; i < psource->numbones; i++) - { - matrix3x4_t m; - AngleMatrix( psource->rawanim[0][i].rot, m ); - m[0][3] = psource->rawanim[0][i].pos[0]; - m[1][3] = psource->rawanim[0][i].pos[1]; - m[2][3] = psource->rawanim[0][i].pos[2]; - - parent = psource->localBone[i].parent; - if (parent == -1) - { - // scale the done pos. - // calc rotational matrices - MatrixCopy( m, psource->boneToPose[i] ); - } - else - { - // calc compound rotational matrices - // FIXME : Hey, it's orthogical so inv(A) == transpose(A) - ConcatTransforms( psource->boneToPose[parent], m, psource->boneToPose[i] ); - } - // vprint( 0, "%3d %f %f %f\n", i, psource->bonefixup[i].worldorg[0], psource->bonefixup[i].worldorg[1], psource->bonefixup[i].worldorg[2] ); - /* - AngleMatrix( angle, m ); - vprint( 0, "%8.4f %8.4f %8.4f\n", m[0][0], m[1][0], m[2][0] ); - vprint( 0, "%8.4f %8.4f %8.4f\n", m[0][1], m[1][1], m[2][1] ); - vprint( 0, "%8.4f %8.4f %8.4f\n", m[0][2], m[1][2], m[2][2] ); - */ - } -} - -int Grab_Nodes( s_node_t *pnodes ) -{ - // - // s_node_t structure: index is index!! - // - int index; - char name[1024]; - int parent; - int numbones = 0; - - // Init parent to none - for (index = 0; index < MAXSTUDIOSRCBONES; index++) - { - pnodes[index].parent = -1; - } - - // March through nodes lines - while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) - { - g_iLinecount++; - // get tokens - if (sscanf( g_szLine, "%d \"%[^\"]\" %d", &index, name, &parent ) == 3) - { - // check for duplicated bones - /* - if (strlen(pnodes[index].name) != 0) - { - MdlError( "bone \"%s\" exists more than once\n", name ); - } - */ - // copy name to struct array - V_strcpy_safe( pnodes[index].name, name ); - // set parent into struct array - pnodes[index].parent = parent; - // increment numbones - if (index > numbones) - { - numbones = index; - } - } - else - { - return numbones + 1; - } - } - MdlError( "Unexpected EOF at line %d\n", g_iLinecount ); - return 0; -} - -void Grab_Vertexanimation( s_source_t *psource ) -{ - char cmd[1024]; - int index; - Vector pos; - Vector normal; - int t = -1; - int count = 0; - static s_vertanim_t tmpvanim[MAXSTUDIOVERTS*4]; - - while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) - { - g_iLinecount++; - if (sscanf( g_szLine, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &normal[0], &normal[1], &normal[2] ) == 7) - { - if (psource->startframe < 0) - { - MdlError( "Missing frame start(%d) : %s", g_iLinecount, g_szLine ); - } - - if (t < 0) - { - MdlError( "VTA Frame Sync (%d) : %s", g_iLinecount, g_szLine ); - } - - tmpvanim[count].vertex = index; - VectorCopy( pos, tmpvanim[count].pos ); - VectorCopy( normal, tmpvanim[count].normal ); - count++; - - if (index >= psource->numvertices) - psource->numvertices = index + 1; - } - else - { - // flush data - - if (count) - { - psource->numvanims[t] = count; - - psource->vanim[t] = (s_vertanim_t *)kalloc( count, sizeof( s_vertanim_t ) ); - - memcpy( psource->vanim[t], tmpvanim, count * sizeof( s_vertanim_t ) ); - } - else if (t > 0) - { - psource->numvanims[t] = 0; - } - - // next command - if (sscanf( g_szLine, "%1023s %d", cmd, &index )) - { - if (strcmp( cmd, "time" ) == 0) - { - t = index; - count = 0; - - if (t < psource->startframe) - { - MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine ); - } - if (t > psource->endframe) - { - MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine ); - } - - t -= psource->startframe; - } - else if (strcmp( cmd, "end") == 0) - { - psource->numframes = psource->endframe - psource->startframe + 1; - return; - } - else - { - MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine ); - } - - } - else - { - MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine ); - } - } - } - MdlError( "unexpected EOF: %s\n", psource->filename ); -} - -void Grab_Animation( s_source_t *psource ) -{ - Vector pos; - RadianEuler rot; - char cmd[1024]; - int index; - int t = -99999999; - int size; - - // Init startframe - psource->startframe = -1; - - // size per frame - size = psource->numbones * sizeof( s_bone_t ); - - // march through animation - while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) - { - // linecount - g_iLinecount++; - // split if big enoough - if (sscanf( g_szLine, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &rot[0], &rot[1], &rot[2] ) == 7) - { - // startframe is sanity check for having determined time - if (psource->startframe < 0) - { - MdlError( "Missing frame start(%d) : %s", g_iLinecount, g_szLine ); - } - - // scale if pertinent - scale_vertex( pos ); - VectorCopy( pos, psource->rawanim[t][index].pos ); - VectorCopy( rot, psource->rawanim[t][index].rot ); - - clip_rotations( rot ); // !!! - } - else if (sscanf( g_szLine, "%1023s %d", cmd, &index )) - { - // get time - if (strcmp( cmd, "time" ) == 0) - { - // again time IS an index - t = index; - if (psource->startframe == -1) - { - psource->startframe = t; - } - // sanity check time (little funny logic here, see previous IF) - if (t < psource->startframe) - { - MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine ); - } - // bump up endframe? - if (t > psource->endframe) - { - psource->endframe = t; - } - // make t into pure index - t -= psource->startframe; - - // check for memory allocation - if (psource->rawanim[t] == NULL) - { - // Allocate 1 frame of full bonecount - psource->rawanim[t] = (s_bone_t *)kalloc( 1, size ); - - // duplicate previous frames keys?? preventative sanity? - if (t > 0 && psource->rawanim[t-1]) - { - for (int j = 0; j < psource->numbones; j++) - { - VectorCopy( psource->rawanim[t-1][j].pos, psource->rawanim[t][j].pos ); - VectorCopy( psource->rawanim[t-1][j].rot, psource->rawanim[t][j].rot ); - } - } - } - else - { - // MdlError( "%s has duplicated frame %d\n", psource->filename, t ); - } - } - else if (strcmp( cmd, "end") == 0) - { - psource->numframes = psource->endframe - psource->startframe + 1; - - for (t = 0; t < psource->numframes; t++) - { - if (psource->rawanim[t] == NULL) - { - MdlError( "%s is missing frame %d\n", psource->filename, t + psource->startframe ); - } - } - - Build_Reference( psource ); - return; - } - else - { - MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine ); - } - } - else - { - MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine ); - } - } - - MdlError( "unexpected EOF: %s\n", psource->filename ); -} - -int lookup_index( s_source_t *psource, int material, Vector& vertex, Vector& normal, Vector2D texcoord ) -{ - int i; - - for (i = 0; i < numvlist; i++) - { - if (v_listdata[i].m == material - && DotProduct( g_normal[i], normal ) > normal_blend - && VectorCompare( g_vertex[i], vertex ) - && g_texcoord[i][0] == texcoord[0] - && g_texcoord[i][1] == texcoord[1]) - { - v_listdata[i].lastref = numvlist; - return i; - } - } - if (i >= MAXSTUDIOVERTS) { - MdlError( "too many indices in source: \"%s\"\n", psource->filename); - } - - VectorCopy( vertex, g_vertex[i] ); - VectorCopy( normal, g_normal[i] ); - Vector2Copy( texcoord, g_texcoord[i] ); - - v_listdata[i].v = i; - v_listdata[i].m = material; - v_listdata[i].n = i; - v_listdata[i].t = i; - - v_listdata[i].firstref = numvlist; - v_listdata[i].lastref = numvlist; - - numvlist = i + 1; - return i; -} - - -void ParseFaceData( s_source_t *psource, int material, s_face_t *pFace ) -{ - int index[3]; - int i, j; - Vector p; - Vector normal; - Vector2D t; - int iCount, bones[MAXSTUDIOSRCBONES]; - float weights[MAXSTUDIOSRCBONES]; - int bone; - - for (j = 0; j < 3; j++) - { - memset( g_szLine, 0, sizeof( g_szLine ) ); - - if (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) == NULL) - { - MdlError("%s: error on g_szLine %d: %s", g_szFilename, g_iLinecount, g_szLine ); - } - - iCount = 0; - - g_iLinecount++; - i = sscanf( g_szLine, "%d %f %f %f %f %f %f %f %f %d %d %f %d %f %d %f %d %f", - &bone, - &p[0], &p[1], &p[2], - &normal[0], &normal[1], &normal[2], - &t[0], &t[1], - &iCount, - &bones[0], &weights[0], &bones[1], &weights[1], &bones[2], &weights[2], &bones[3], &weights[3] ); - - if (i < 9) - continue; - - if (bone < 0 || bone >= psource->numbones) - { - MdlError("bogus bone index\n%d %s :\n%s", g_iLinecount, g_szFilename, g_szLine ); - } - - //Scale face pos - scale_vertex( p ); - - // continue parsing more bones. - // FIXME: don't we have a built in parser that'll do this? - if (iCount > 4) - { - int k; - int ctr = 0; - char *token; - for (k = 0; k < 18; k++) - { - while (g_szLine[ctr] == ' ') - { - ctr++; - } - token = strtok( &g_szLine[ctr], " " ); - ctr += strlen( token ) + 1; - } - for (k = 4; k < iCount && k < MAXSTUDIOSRCBONES; k++) - { - while (g_szLine[ctr] == ' ') - { - ctr++; - } - token = strtok( &g_szLine[ctr], " " ); - ctr += strlen( token ) + 1; - - bones[k] = atoi(token); - - token = strtok( &g_szLine[ctr], " " ); - ctr += strlen( token ) + 1; - - weights[k] = atof(token); - } - // vprint( 0, "%d ", iCount ); - - //vprint( 0, "\n"); - //exit(1); - } - - // adjust_vertex( p ); - // scale_vertex( p ); - - // move vertex position to object space. - // VectorSubtract( p, psource->bonefixup[bone].worldorg, tmp ); - // VectorTransform(tmp, psource->bonefixup[bone].im, p ); - - // move normal to object space. - // VectorCopy( normal, tmp ); - // VectorTransform(tmp, psource->bonefixup[bone].im, normal ); - // VectorNormalize( normal ); - - // invert v - t[1] = 1.0 - t[1]; - - index[j] = lookup_index( psource, material, p, normal, t ); - - if (i == 9 || iCount == 0) - { - g_bone[index[j]].numbones = 1; - g_bone[index[j]].bone[0] = bone; - g_bone[index[j]].weight[0] = 1.0; - } - else - { - iCount = SortAndBalanceBones( iCount, MAXSTUDIOBONEWEIGHTS, bones, weights ); - - g_bone[index[j]].numbones = iCount; - for (i = 0; i < iCount; i++) - { - g_bone[index[j]].bone[i] = bones[i]; - g_bone[index[j]].weight[i] = weights[i]; - } - } - } - - // pFace->material = material; // BUG - pFace->a = index[0]; - pFace->b = index[1]; - pFace->c = index[2]; - Assert( ((pFace->a & 0xF0000000) == 0) && ((pFace->b & 0xF0000000) == 0) && - ((pFace->c & 0xF0000000) == 0) ); - - if (flip_triangles) - { - j = pFace->b; pFace->b = pFace->c; pFace->c = j; - } -} - -int use_texture_as_material( int textureindex ) -{ - if (g_texture[textureindex].material == -1) - { - // vprint( 0, "%d %d %s\n", textureindex, g_nummaterials, g_texture[textureindex].name ); - g_material[g_nummaterials] = textureindex; - g_texture[textureindex].material = g_nummaterials++; - } - - return g_texture[textureindex].material; -} - -int material_to_texture( int material ) -{ - int i; - for (i = 0; i < g_numtextures; i++) - { - if (g_texture[i].material == material) - { - return i; - } - } - return -1; -} - -int lookup_texture( char *texturename, int maxlen ) -{ - int i; - - Q_StripExtension( texturename, texturename, maxlen ); - - for (i = 0; i < g_numtextures; i++) - { - if (stricmp( g_texture[i].name, texturename ) == 0) - { - return i; - } - } - - if (i >= MAXSTUDIOSKINS) - MdlError("Too many materials used, max %d\n", ( int )MAXSTUDIOSKINS ); - -// vprint( 0, "texture %d = %s\n", i, texturename ); - V_strcpy_safe( g_texture[i].name, texturename ); - - g_texture[i].material = -1; - /* - if (stristr( texturename, "chrome" ) != NULL) { - texture[i].flags = STUDIO_NF_FLATSHADE | STUDIO_NF_CHROME; - } - else { - texture[i].flags = 0; - } - */ - g_numtextures++; - return i; -} - -int SortAndBalanceBones( int iCount, int iMaxCount, int bones[], float weights[] ) -{ - int i; - - // collapse duplicate bone weights - for (i = 0; i < iCount-1; i++) - { - int j; - for (j = i + 1; j < iCount; j++) - { - if (bones[i] == bones[j]) - { - weights[i] += weights[j]; - weights[j] = 0.0; - } - } - } - - // do sleazy bubble sort - int bShouldSort; - do { - bShouldSort = false; - for (i = 0; i < iCount-1; i++) - { - if (weights[i+1] > weights[i]) - { - int j = bones[i+1]; bones[i+1] = bones[i]; bones[i] = j; - float w = weights[i+1]; weights[i+1] = weights[i]; weights[i] = w; - bShouldSort = true; - } - } - } while (bShouldSort); - - // throw away all weights less than 1/20th - while (iCount > 1 && weights[iCount-1] < 0.05) - { - iCount--; - } - - // clip to the top iMaxCount bones - if (iCount > iMaxCount) - { - iCount = iMaxCount; - } - - float t = 0; - for (i = 0; i < iCount; i++) - { - t += weights[i]; - } - - if (t <= 0.0) - { - // missing weights?, go ahead and evenly share? - // FIXME: shouldn't this error out? - t = 1.0 / iCount; - - for (i = 0; i < iCount; i++) - { - weights[i] = t; - } - } - else - { - // scale to sum to 1.0 - t = 1.0 / t; - - for (i = 0; i < iCount; i++) - { - weights[i] = weights[i] * t; - } - } - - return iCount; -} - -int vlistCompare( const void *elem1, const void *elem2 ) -{ - v_unify_t *u1 = &v_listdata[*(int *)elem1]; - v_unify_t *u2 = &v_listdata[*(int *)elem2]; - - // sort by material - if (u1->m < u2->m) - return -1; - if (u1->m > u2->m) - return 1; - - // sort by last used - if (u1->lastref < u2->lastref) - return -1; - if (u1->lastref > u2->lastref) - return 1; - - return 0; -} - -int faceCompare( const void *elem1, const void *elem2 ) -{ - int i1 = *(int *)elem1; - int i2 = *(int *)elem2; - - // sort by material - if (g_face[i1].material < g_face[i2].material) - return -1; - if (g_face[i1].material > g_face[i2].material) - return 1; - - // sort by original usage - if (i1 < i2) - return -1; - if (i1 > i2) - return 1; - - return 0; -} - -#define SMALL_FLOAT 1e-12 - -// NOTE: This routine was taken (and modified) from NVidia's BlinnReflection demo -// Creates basis vectors, based on a vertex and index list. -// See the NVidia white paper 'GDC2K PerPixel Lighting' for a description -// of how this computation works -static void CalcTriangleTangentSpace( s_source_t *pSrc, int v1, int v2, int v3, - Vector &sVect, Vector &tVect ) -{ -/* - static bool firstTime = true; - static FILE *fp = NULL; - if( firstTime ) - { - firstTime = false; - fp = fopen( "crap.out", "w" ); - } -*/ - - /* Compute the partial derivatives of X, Y, and Z with respect to S and T. */ - Vector2D t0( pSrc->texcoord[v1][0], pSrc->texcoord[v1][1] ); - Vector2D t1( pSrc->texcoord[v2][0], pSrc->texcoord[v2][1] ); - Vector2D t2( pSrc->texcoord[v3][0], pSrc->texcoord[v3][1] ); - Vector p0( pSrc->vertex[v1][0], pSrc->vertex[v1][1], pSrc->vertex[v1][2] ); - Vector p1( pSrc->vertex[v2][0], pSrc->vertex[v2][1], pSrc->vertex[v2][2] ); - Vector p2( pSrc->vertex[v3][0], pSrc->vertex[v3][1], pSrc->vertex[v3][2] ); - - sVect.Init( 0.0f, 0.0f, 0.0f ); - tVect.Init( 0.0f, 0.0f, 0.0f ); - - // x, s, t - Vector edge01 = Vector( p1.x - p0.x, t1.x - t0.x, t1.y - t0.y ); - Vector edge02 = Vector( p2.x - p0.x, t2.x - t0.x, t2.y - t0.y ); - - Vector cross; - CrossProduct( edge01, edge02, cross ); - if( fabs( cross.x ) > SMALL_FLOAT ) - { - sVect.x += -cross.y / cross.x; - tVect.x += -cross.z / cross.x; - } - - // y, s, t - edge01 = Vector( p1.y - p0.y, t1.x - t0.x, t1.y - t0.y ); - edge02 = Vector( p2.y - p0.y, t2.x - t0.x, t2.y - t0.y ); - - CrossProduct( edge01, edge02, cross ); - if( fabs( cross.x ) > SMALL_FLOAT ) - { - sVect.y += -cross.y / cross.x; - tVect.y += -cross.z / cross.x; - } - - // z, s, t - edge01 = Vector( p1.z - p0.z, t1.x - t0.x, t1.y - t0.y ); - edge02 = Vector( p2.z - p0.z, t2.x - t0.x, t2.y - t0.y ); - - CrossProduct( edge01, edge02, cross ); - if( fabs( cross.x ) > SMALL_FLOAT ) - { - sVect.z += -cross.y / cross.x; - tVect.z += -cross.z / cross.x; - } - - // Normalize sVect and tVect - VectorNormalize( sVect ); - VectorNormalize( tVect ); - -/* - // Calculate flat normal - Vector flatNormal; - edge01 = p1 - p0; - edge02 = p2 - p0; - CrossProduct( edge02, edge01, flatNormal ); - VectorNormalize( flatNormal ); - - // Get the average position - Vector avgPos = ( p0 + p1 + p2 ) / 3.0f; - - // Draw the svect - Vector endS = avgPos + sVect * .2f; - fvprint( 0, fp, "2\n" ); - fvprint( 0, fp, "%f %f %f 1.0 0.0 0.0\n", endS[0], endS[1], endS[2] ); - fvprint( 0, fp, "%f %f %f 1.0 0.0 0.0\n", avgPos[0], avgPos[1], avgPos[2] ); - - // Draw the tvect - Vector endT = avgPos + tVect * .2f; - fvprint( 0, fp, "2\n" ); - fvprint( 0, fp, "%f %f %f 0.0 1.0 0.0\n", endT[0], endT[1], endT[2] ); - fvprint( 0, fp, "%f %f %f 0.0 1.0 0.0\n", avgPos[0], avgPos[1], avgPos[2] ); - - // Draw the normal - Vector endN = avgPos + flatNormal * .2f; - fvprint( 0, fp, "2\n" ); - fvprint( 0, fp, "%f %f %f 0.0 0.0 1.0\n", endN[0], endN[1], endN[2] ); - fvprint( 0, fp, "%f %f %f 0.0 0.0 1.0\n", avgPos[0], avgPos[1], avgPos[2] ); - - // Draw the wireframe of the triangle in white. - fvprint( 0, fp, "2\n" ); - fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p0[0], p0[1], p0[2] ); - fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p1[0], p1[1], p1[2] ); - fvprint( 0, fp, "2\n" ); - fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p1[0], p1[1], p1[2] ); - fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p2[0], p2[1], p2[2] ); - fvprint( 0, fp, "2\n" ); - fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p2[0], p2[1], p2[2] ); - fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p0[0], p0[1], p0[2] ); - - // Draw a slightly shrunken version of the geometry to hide surfaces - Vector tmp0 = p0 - flatNormal * .1f; - Vector tmp1 = p1 - flatNormal * .1f; - Vector tmp2 = p2 - flatNormal * .1f; - fvprint( 0, fp, "3\n" ); - fvprint( 0, fp, "%f %f %f 0.1 0.1 0.1\n", tmp0[0], tmp0[1], tmp0[2] ); - fvprint( 0, fp, "%f %f %f 0.1 0.1 0.1\n", tmp1[0], tmp1[1], tmp1[2] ); - fvprint( 0, fp, "%f %f %f 0.1 0.1 0.1\n", tmp2[0], tmp2[1], tmp2[2] ); - - fflush( fp ); -*/ -} - -typedef CUtlVector CIntVector; - -void CalcModelTangentSpaces( s_source_t *pSrc ) -{ - // Build a map from vertex to a list of triangles that share the vert. - int meshID; - for( meshID = 0; meshID < pSrc->nummeshes; meshID++ ) - { - s_mesh_t *pMesh = &pSrc->mesh[pSrc->meshindex[meshID]]; - CUtlVector vertToTriMap; - vertToTriMap.AddMultipleToTail( pMesh->numvertices ); - int triID; - for( triID = 0; triID < pMesh->numfaces; triID++ ) - { - s_face_t *pFace = &pSrc->face[triID + pMesh->faceoffset]; - vertToTriMap[pFace->a].AddToTail( triID ); - vertToTriMap[pFace->b].AddToTail( triID ); - vertToTriMap[pFace->c].AddToTail( triID ); - } - - // Calculate the tangent space for each triangle. - CUtlVector triSVect; - CUtlVector triTVect; - triSVect.AddMultipleToTail( pMesh->numfaces ); - triTVect.AddMultipleToTail( pMesh->numfaces ); - for( triID = 0; triID < pMesh->numfaces; triID++ ) - { - s_face_t *pFace = &pSrc->face[triID + pMesh->faceoffset]; - CalcTriangleTangentSpace( pSrc, - pMesh->vertexoffset + pFace->a, - pMesh->vertexoffset + pFace->b, - pMesh->vertexoffset + pFace->c, - triSVect[triID], triTVect[triID] ); - } - - // calculate an average tangent space for each vertex. - int vertID; - for( vertID = 0; vertID < pMesh->numvertices; vertID++ ) - { - const Vector &normal = pSrc->normal[vertID+pMesh->vertexoffset]; - Vector4D &finalSVect = pSrc->tangentS[vertID+pMesh->vertexoffset]; - Vector sVect, tVect; - - sVect.Init( 0.0f, 0.0f, 0.0f ); - tVect.Init( 0.0f, 0.0f, 0.0f ); - for( triID = 0; triID < vertToTriMap[vertID].Size(); triID++ ) - { - sVect += triSVect[vertToTriMap[vertID][triID]]; - tVect += triTVect[vertToTriMap[vertID][triID]]; - } - - // In the case of zbrush, everything needs to be treated as smooth. - if( g_bZBrush ) - { - int vertID2; - Vector vertPos1( pSrc->vertex[vertID][0], pSrc->vertex[vertID][1], pSrc->vertex[vertID][2] ); - for( vertID2 = 0; vertID2 < pMesh->numvertices; vertID2++ ) - { - if( vertID2 == vertID ) - { - continue; - } - Vector vertPos2( pSrc->vertex[vertID2][0], pSrc->vertex[vertID2][1], pSrc->vertex[vertID2][2] ); - if( vertPos1 == vertPos2 ) - { - int triID2; - for( triID2 = 0; triID2 < vertToTriMap[vertID2].Size(); triID2++ ) - { - sVect += triSVect[vertToTriMap[vertID2][triID2]]; - tVect += triTVect[vertToTriMap[vertID2][triID2]]; - } - } - } - } - - // make an orthonormal system. - // need to check if we are left or right handed. - Vector tmpVect; - CrossProduct( sVect, tVect, tmpVect ); - bool leftHanded = DotProduct( tmpVect, normal ) < 0.0f; - if( !leftHanded ) - { - CrossProduct( normal, sVect, tVect ); - CrossProduct( tVect, normal, sVect ); - VectorNormalize( sVect ); - VectorNormalize( tVect ); - finalSVect[0] = sVect[0]; - finalSVect[1] = sVect[1]; - finalSVect[2] = sVect[2]; - finalSVect[3] = 1.0f; - } - else - { - CrossProduct( sVect, normal, tVect ); - CrossProduct( normal, tVect, sVect ); - VectorNormalize( sVect ); - VectorNormalize( tVect ); - finalSVect[0] = sVect[0]; - finalSVect[1] = sVect[1]; - finalSVect[2] = sVect[2]; - finalSVect[3] = -1.0f; - } - } - } -} - -void BuildIndividualMeshes( s_source_t *psource ) -{ - int i, j, k; - - // sort new vertices by materials, last used - static int v_listsort[MAXSTUDIOVERTS]; // map desired order to vlist entry - static int v_ilistsort[MAXSTUDIOVERTS]; // map vlist entry to desired order - - for (i = 0; i < numvlist; i++) - { - v_listsort[i] = i; - } - qsort( v_listsort, numvlist, sizeof( int ), vlistCompare ); - for (i = 0; i < numvlist; i++) - { - v_ilistsort[v_listsort[i]] = i; - } - - - // allocate memory - psource->numvertices = numvlist; - psource->localBoneweight = (s_boneweight_t *)kalloc( psource->numvertices, sizeof( s_boneweight_t ) ); - psource->globalBoneweight = NULL; - psource->vertexInfo = (s_vertexinfo_t *)kalloc( psource->numvertices, sizeof( s_vertexinfo_t ) ); - psource->vertex = new Vector[psource->numvertices]; - psource->normal = new Vector[psource->numvertices]; - psource->tangentS = new Vector4D[psource->numvertices]; - psource->texcoord = (Vector2D *)kalloc( psource->numvertices, sizeof( Vector2D ) ); - - // create arrays of unique vertexes, normals, texcoords. - for (i = 0; i < psource->numvertices; i++) - { - j = v_listsort[i]; - - VectorCopy( g_vertex[v_listdata[j].v], psource->vertex[i] ); - VectorCopy( g_normal[v_listdata[j].n], psource->normal[i] ); - Vector2Copy( g_texcoord[v_listdata[j].t], psource->texcoord[i] ); - - psource->localBoneweight[i].numbones = g_bone[v_listdata[j].v].numbones; - int k; - for( k = 0; k < MAXSTUDIOBONEWEIGHTS; k++ ) - { - psource->localBoneweight[i].bone[k] = g_bone[v_listdata[j].v].bone[k]; - psource->localBoneweight[i].weight[k] = g_bone[v_listdata[j].v].weight[k]; - } - - // store a bunch of other info - psource->vertexInfo[i].material = v_listdata[j].m; - - psource->vertexInfo[i].firstref = v_listdata[j].firstref; - psource->vertexInfo[i].lastref = v_listdata[j].lastref; - // vprint( 0, "%4d : %2d : %6.2f %6.2f %6.2f\n", i, psource->boneweight[i].bone[0], psource->vertex[i][0], psource->vertex[i][1], psource->vertex[i][2] ); - } - - // sort faces by materials, last used. - static int facesort[MAXSTUDIOTRIANGLES]; // map desired order to src_face entry - static int ifacesort[MAXSTUDIOTRIANGLES]; // map src_face entry to desired order - - for (i = 0; i < g_numfaces; i++) - { - facesort[i] = i; - } - qsort( facesort, g_numfaces, sizeof( int ), faceCompare ); - for (i = 0; i < g_numfaces; i++) - { - ifacesort[facesort[i]] = i; - } - - psource->numfaces = g_numfaces; - // find first occurance for each material - for (k = 0; k < MAXSTUDIOSKINS; k++) - { - psource->mesh[k].numvertices = 0; - psource->mesh[k].vertexoffset = psource->numvertices; - - psource->mesh[k].numfaces = 0; - psource->mesh[k].faceoffset = g_numfaces; - } - - // find first and count of indices per material - for (i = 0; i < psource->numvertices; i++) - { - k = psource->vertexInfo[i].material; - psource->mesh[k].numvertices++; - if (psource->mesh[k].vertexoffset > i) - psource->mesh[k].vertexoffset = i; - } - - // find first and count of faces per material - for (i = 0; i < psource->numfaces; i++) - { - k = g_face[facesort[i]].material; - - psource->mesh[k].numfaces++; - if (psource->mesh[k].faceoffset > i) - psource->mesh[k].faceoffset = i; - } - - /* - for (k = 0; k < MAXSTUDIOSKINS; k++) - { - vprint( 0, "%d : %d:%d %d:%d\n", k, psource->mesh[k].numvertices, psource->mesh[k].vertexoffset, psource->mesh[k].numfaces, psource->mesh[k].faceoffset ); - } - */ - - // create remapped faces - psource->face = (s_face_t *)kalloc( psource->numfaces, sizeof( s_face_t )); - for (k = 0; k < MAXSTUDIOSKINS; k++) - { - if (psource->mesh[k].numfaces) - { - psource->meshindex[psource->nummeshes] = k; - - for (i = psource->mesh[k].faceoffset; i < psource->mesh[k].numfaces + psource->mesh[k].faceoffset; i++) - { - j = facesort[i]; - - psource->face[i].a = v_ilistsort[g_src_uface[j].a] - psource->mesh[k].vertexoffset; - psource->face[i].b = v_ilistsort[g_src_uface[j].b] - psource->mesh[k].vertexoffset; - psource->face[i].c = v_ilistsort[g_src_uface[j].c] - psource->mesh[k].vertexoffset; - Assert( ((psource->face[i].a & 0xF0000000) == 0) && ((psource->face[i].b & 0xF0000000) == 0) && - ((psource->face[i].c & 0xF0000000) == 0) ); - // vprint( 0, "%3d : %4d %4d %4d\n", i, psource->face[i].a, psource->face[i].b, psource->face[i].c ); - } - - psource->nummeshes++; - } - } - - CalcModelTangentSpaces( psource ); -} - -void Grab_Triangles( s_source_t *psource ) -{ - int i; - Vector vmin, vmax; - - vmin[0] = vmin[1] = vmin[2] = 99999; - vmax[0] = vmax[1] = vmax[2] = -99999; - - g_numfaces = 0; - numvlist = 0; - - // - // load the base triangles - // - int texture; - int material; - char texturename[64]; - - while (1) - { - if (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) == NULL) - break; - - g_iLinecount++; - - // check for end - if (IsEnd( g_szLine )) - break; - - // Look for extra junk that we may want to avoid... - int nLineLength = strlen( g_szLine ); - if (nLineLength >= 64) - { - MdlWarning("Unexpected data at line %d, (need a texture name) ignoring...\n", g_iLinecount ); - continue; - } - - // strip off trailing smag - V_strcpy_safe( texturename, g_szLine ); - for (i = strlen( texturename ) - 1; i >= 0 && ! isgraph( texturename[i] ); i--) - { - } - texturename[i + 1] = '\0'; - - // funky texture overrides - for (i = 0; i < numrep; i++) - { - if (sourcetexture[i][0] == '\0') - { - strcpy( texturename, defaulttexture[i] ); - break; - } - if (stricmp( texturename, sourcetexture[i]) == 0) - { - strcpy( texturename, defaulttexture[i] ); - break; - } - } - - if (texturename[0] == '\0') - { - // weird source problem, skip them - fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); - fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); - fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); - g_iLinecount += 3; - continue; - } - - if (stricmp( texturename, "null.bmp") == 0 || stricmp( texturename, "null.tga") == 0) - { - // skip all faces with the null texture on them. - fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); - fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); - fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); - g_iLinecount += 3; - continue; - } - - texture = lookup_texture( texturename, sizeof( texturename ) ); - psource->texmap[texture] = texture; // hack, make it 1:1 - material = use_texture_as_material( texture ); - - s_face_t f; - ParseFaceData( psource, material, &f ); - - g_src_uface[g_numfaces] = f; - g_face[g_numfaces].material = material; - g_numfaces++; - } - - BuildIndividualMeshes( psource ); -} - -//-------------------------------------------------------------------- -// Load a SMD file -//-------------------------------------------------------------------- -int Load_SMD ( s_source_t *psource ) -{ - char cmd[1024]; - int option; - - // Open file - if (!OpenGlobalFile( psource->filename )) - return 0; - - // verbose - if( !g_quiet ) - { - printf ("SMD MODEL %s\n", psource->filename); - } - - //March through lines - g_iLinecount = 0; - while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) - { - g_iLinecount++; - int numRead = sscanf( g_szLine, "%s %d", cmd, &option ); - - // Blank line - if ((numRead == EOF) || (numRead == 0)) - continue; - - if (strcmp( cmd, "version" ) == 0) - { - if (option != 1) - { - MdlError("bad version\n"); - } - } - // Get hierarchy? - else if (strcmp( cmd, "nodes" ) == 0) - { - psource->numbones = Grab_Nodes( psource->localBone ); - } - // Get animation?? - else if (strcmp( cmd, "skeleton" ) == 0) - { - Grab_Animation( psource ); - } - // Geo? - else if (strcmp( cmd, "triangles" ) == 0) - { - Grab_Triangles( psource ); - } - // Geo animation - else if (strcmp( cmd, "vertexanimation" ) == 0) - { - Grab_Vertexanimation( psource ); - } - else - { - MdlWarning("unknown studio command\n" ); - } - } - fclose( g_fpInput ); - - is_v1support = true; - - return 1; -} - -//----------------------------------------------------------------------------- -// Checks to see if the model source was already loaded -//----------------------------------------------------------------------------- -static s_source_t *FindCachedSource( char const* name, char const* xext ) -{ - int i; - - if( xext[0] ) - { - // we know what extension is necessary. . look for it. - sprintf (g_szFilename, "%s%s.%s", cddir[numdirs], name, xext ); - for (i = 0; i < g_numsources; i++) - { - if (stricmp( g_szFilename, g_source[i]->filename ) == 0) - return g_source[i]; - } - } - else - { - // we don't know what extension to use, so look for all of 'em. - sprintf (g_szFilename, "%s%s.vrm", cddir[numdirs], name ); - for (i = 0; i < g_numsources; i++) - { - if (stricmp( g_szFilename, g_source[i]->filename ) == 0) - return g_source[i]; - } - sprintf (g_szFilename, "%s%s.smd", cddir[numdirs], name ); - for (i = 0; i < g_numsources; i++) - { - if (stricmp( g_szFilename, g_source[i]->filename ) == 0) - return g_source[i]; - } - /* - sprintf (g_szFilename, "%s%s.vta", cddir[numdirs], name ); - for (i = 0; i < g_numsources; i++) - { - if (stricmp( g_szFilename, g_source[i]->filename ) == 0) - return g_source[i]; - } - */ - } - - // Not found - return 0; -} - -static void FlipFacing( s_source_t *pSrc ) -{ - unsigned short tmp; - - int i, j; - for( i = 0; i < pSrc->nummeshes; i++ ) - { - s_mesh_t *pMesh = &pSrc->mesh[i]; - for( j = 0; j < pMesh->numfaces; j++ ) - { - s_face_t &f = pSrc->face[pMesh->faceoffset + j]; - tmp = f.b; f.b = f.c; f.c = tmp; - } - } -} - -//----------------------------------------------------------------------------- -// Loads an animation source -//----------------------------------------------------------------------------- - -s_source_t *Load_Source( char const *name, const char *ext, bool reverse, bool isActiveModel ) -{ - // Sanity check number of source files - if ( g_numsources >= MAXSTUDIOSEQUENCES ) - MdlError( "Load_Source( %s ) - overflowed g_numsources.", name ); - - // Sanity check file and init - Assert(name); - int namelen = strlen(name) + 1; - char* pTempName = (char*)_alloca( namelen ); - char xext[32]; - int result = false; - - // Local copy of filename - strcpy( pTempName, name ); - - // Sanity check file extension? - Q_ExtractFileExtension( pTempName, xext, sizeof( xext ) ); - if (xext[0] == '\0') - { - V_strcpy_safe( xext, ext ); - } - else - { - Q_StripExtension( pTempName, pTempName, namelen ); - } - - // Cached source, ie: already loaded model, legacy - // s_source_t* pSource = FindCachedSource( pTempName, xext ); - // if (pSource) - // { - // if (isActiveModel) - // pSource->isActiveModel = true; - // return pSource; - // } - - // allocate space and whatnot - g_source[g_numsources] = (s_source_t *)kalloc( 1, sizeof( s_source_t ) ); - V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename ); - - // legacy stuff - if (isActiveModel) - { - g_source[g_numsources]->isActiveModel = true; - } - - // more ext sanity check - if ( ( !result && xext[0] == '\0' ) || stricmp( xext, "smd" ) == 0) - { - Q_snprintf( g_szFilename, sizeof(g_szFilename), "%s%s.smd", cddir[numdirs], pTempName ); - V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename ); - - // Import part, load smd file - result = Load_SMD( g_source[g_numsources] ); - } - - /* - if ( ( !result && xext[0] == '\0' ) || stricmp( xext, "dmx" ) == 0) - { - Q_snprintf( g_szFilename, sizeof(g_szFilename), "%s%s.dmx", cddir[numdirs], pTempName ); - V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename ); - - // Import part, load smd file - result = Load_DMX( g_source[g_numsources] ); - } - */ - - // Oops - if ( !result) - { - MdlError( "could not load file '%s'\n", g_source[g_numsources]->filename ); - } - - // bump up number of sources - g_numsources++; - if( reverse ) - { - FlipFacing( g_source[g_numsources-1] ); - } - return g_source[g_numsources-1]; -} - -void SaveNodes( s_source_t *source, CUtlBuffer& buf ) -{ - if ( source->numbones <= 0 ) - return; - - buf.Printf( "nodes\n" ); - - for ( int i = 0; i < source->numbones; ++i ) - { - s_node_t *bone = &source->localBone[ i ]; - - buf.Printf( "%d \"%s\" %d\n", i, bone->name, bone->parent ); - } - - buf.Printf( "end\n" ); -} - -// FIXME: since we don't us a .qc, we could have problems with scaling, etc.??? -void descale_vertex( Vector &org ) -{ - float invscale = 1.0f / g_currentscale; - - org[0] = org[0] * invscale; - org[1] = org[1] * invscale; - org[2] = org[2] * invscale; -} - -void SaveAnimation( s_source_t *source, CUtlBuffer& buf ) -{ - if ( source->numbones <= 0 ) - return; - - buf.Printf( "skeleton\n" ); - - for ( int frame = 0; frame < source->numframes; ++frame ) - { - buf.Printf( "time %i\n", frame + source->startframe ); - - for ( int i = 0; i < source->numbones; ++i ) - { - s_bone_t *prev = NULL; - if ( frame > 0 ) - { - if ( source->rawanim[ frame - 1 ] ) - { - prev = &source->rawanim[ frame - 1 ][ i ]; - } - } - - Vector pos = source->rawanim[ frame ][ i ].pos; - descale_vertex( pos ); - RadianEuler rot = source->rawanim[ frame ][ i ].rot; - -// If this is enabled, then we delta this pos vs the prev frame and don't write out a sample if it's the same value... -#if 0 - if ( prev ) - { - Vector ppos = source->rawanim[ frame -1 ][ i ].pos; - descale_vertex( pos ); - RadianEuler prot = source->rawanim[ frame -1 ][ i ].rot; - - // Only output it if there's a delta - if ( ( ppos != pos ) || - Q_memcmp( &prot, &rot, sizeof( prot ) ) ) - { - buf.Printf - ( "%d %f %f %f %f %f %f\n", - i, // bone index - pos[ 0 ], - pos[ 1 ], - pos[ 2 ], - rot[ 0 ], - rot[ 1 ], - rot[ 2 ] - ); - } - } - else -#endif - { - buf.Printf - ( "%d %f %f %f %f %f %f\n", - i, // bone index - pos[ 0 ], - pos[ 1 ], - pos[ 2 ], - rot[ 0 ], - rot[ 1 ], - rot[ 2 ] - ); - } - } - } - - buf.Printf( "end\n" ); -} - -void Save_SMD( char const *filename, s_source_t *source ) -{ - // Text buffer - CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); - - buf.Printf( "version 1\n" ); - - SaveNodes( source, buf ); - SaveAnimation( source, buf ); - - FileHandle_t fh = g_pFileSystem->Open( filename, "wb" ); - if ( FILESYSTEM_INVALID_HANDLE != fh ) - { - g_pFileSystem->Write( buf.Base(), buf.TellPut(), fh ); - g_pFileSystem->Close( fh ); - } -} - -//-------------------------------------------------------------------- -// mikes right handed row based linear algebra -//-------------------------------------------------------------------- -struct M_matrix4x4_t -{ - M_matrix4x4_t() { - - m_flMatVal[0][0] = 1.0; m_flMatVal[0][1] = 0.0; m_flMatVal[0][2] = 0.0; m_flMatVal[0][3] = 0.0; - m_flMatVal[1][0] = 0.0; m_flMatVal[1][1] = 1.0; m_flMatVal[1][2] = 0.0; m_flMatVal[1][3] = 0.0; - m_flMatVal[2][0] = 0.0; m_flMatVal[2][1] = 0.0; m_flMatVal[2][2] = 1.0; m_flMatVal[2][3] = 0.0; - m_flMatVal[3][0] = 0.0; m_flMatVal[3][1] = 0.0; m_flMatVal[3][2] = 0.0; m_flMatVal[3][3] = 1.0; - - } - // M_matrix3x4_t( - // float m00, float m01, float m02, - // float m10, float m11, float m12, - // float m20, float m21, float m22, - // float m30, float m31, float m32) - // { - // m_flMatVal[0][0] = m00; m_flMatVal[0][1] = m01; m_flMatVal[0][2] = m02; - // m_flMatVal[1][0] = m10; m_flMatVal[1][1] = m11; m_flMatVal[1][2] = m12; - // m_flMatVal[2][0] = m20; m_flMatVal[2][1] = m21; m_flMatVal[2][2] = m22; - // m_flMatVal[3][0] = m30; m_flMatVal[3][1] = m31; m_flMatVal[3][2] = m32; - - // } - - float *operator[]( int i ) { Assert(( i >= 0 ) && ( i < 4 )); return m_flMatVal[i]; } - const float *operator[]( int i ) const { Assert(( i >= 0 ) && ( i < 4 )); return m_flMatVal[i]; } - float *Base() { return &m_flMatVal[0][0]; } - const float *Base() const { return &m_flMatVal[0][0]; } - - float m_flMatVal[4][4]; -}; - -void M_MatrixAngles( const M_matrix4x4_t& matrix, RadianEuler &angles, Vector &position) -{ - float cX, sX, cY, sY, cZ, sZ; - - sY = -matrix[0][2]; - cY = sqrtf(1.0-(sY*sY)); - - if (cY != 0.0) - { - sX = matrix[1][2]; - cX = matrix[2][2]; - sZ = matrix[0][1]; - cZ = matrix[0][0]; - } - else - { - sX = -matrix[2][1]; - cX = matrix[1][1]; - sZ = 0.0; - cZ = 1.0; - } - - angles[0] = atan2f( sX, cX ); - angles[2] = atan2f( sZ, cZ ); - - sX = sinf(angles[0]); - cX = cosf(angles[0]); - - if (sX > cX) - cY = matrix[1][2] / sX; - else - cY = matrix[2][2] / cX; - - angles[1] = atan2f( sY, cY ); - - - position.x = matrix[3][0]; - position.y = matrix[3][1]; - position.z = matrix[3][2]; - -} - -// void M_MatrixAngles( const M_matrix4x4_t& matrix, RadianEuler &angles, Vector &position) -// { - - // float cX, sX, cY, sY, cZ, sZ; - - // sY = matrix[2][0]; - // cY = sqrtf(1.0-(sY*sY)); - - // if (cY != 0.0) - // { - // sX = -matrix[2][1]; - // cX = matrix[2][2]; - // sZ = -matrix[1][0]; - // cZ = matrix[0][0]; - // } - // else - // { - // sX = matrix[0][1]; - // cX = matrix[1][1]; - // sZ = 0.0; - // cZ = 1.0; - // } - - // angles[0] = atan2f( sX, cX ); - // angles[2] = atan2f( sZ, cZ ); - - // sX = sinf(angles[0]); - // cX = cosf(angles[0]); - - // if (sX > cX) - // cY = -matrix[2][1] / sX; - // else - // cY = matrix[2][2] / cX; - - // angles[1] = atan2f( sY, cY ); - - // angles[0] = angles[0]; - // angles[1] = angles[1]; - // angles[2] = angles[2]; - - // position.x = matrix[3][0]; - // position.y = matrix[3][1]; - // position.z = matrix[3][2]; -// } - -void M_MatrixCopy( const M_matrix4x4_t& in, M_matrix4x4_t& out ) -{ - // Assert( s_bMathlibInitialized ); - memcpy( out.Base(), in.Base(), sizeof( float ) * 4 * 4 ); -} -void M_RotateZMatrix(float radian, M_matrix4x4_t &resultMatrix) -{ - - resultMatrix[0][0] = cosf(radian); - resultMatrix[0][1] = sin(radian); - resultMatrix[0][2] = 0.0; - resultMatrix[1][0] =-sin(radian); - resultMatrix[1][1] = cos(radian); - resultMatrix[1][2] = 0.0; - resultMatrix[2][0] = 0.0; - resultMatrix[2][1] = 0.0; - resultMatrix[2][2] = 1.0; -} - -// !!! THIS SHIT DOESN'T WORK!! WHY? HAS I EVER? -void M_AngleAboutAxis(Vector &axis, float radianAngle, M_matrix4x4_t &result) -{ - float c = cosf(radianAngle); - float s = sinf(radianAngle); - float t = 1.0 - c; - // axis.normalize(); - - result[0][0] = t * axis[0] * axis[0] + c; - result[0][1] = t * axis[0] * axis[1] - s * axis[2]; - result[0][2] = t * axis[0] * axis[2] + s * axis[1]; - result[1][0] = t * axis[0] * axis[1] + s * axis[2]; - result[1][1] = t * axis[1] * axis[1] + c; - result[1][2] = t * axis[1] * axis[2] - s * axis[0]; - result[2][0] = t * axis[1] * axis[2] - s; - result[2][1] = t * axis[1] * axis[2] + s * axis[1]; - result[2][2] = t * axis[2] * axis[2] + c * axis[0]; - -} - - -void M_MatrixInvert( const M_matrix4x4_t& in, M_matrix4x4_t& out ) -{ - // Assert( s_bMathlibInitialized ); - if ( &in == &out ) - { - M_matrix4x4_t in2; - M_MatrixCopy( in, in2 ); - M_MatrixInvert( in2, out ); - return; - } - float tmp[3]; - - // I'm guessing this only works on a 3x4 orthonormal matrix - out[0][0] = in[0][0]; - out[1][0] = in[0][1]; - out[2][0] = in[0][2]; - - out[0][1] = in[1][0]; - out[1][1] = in[1][1]; - out[2][1] = in[1][2]; - - out[0][2] = in[2][0]; - out[1][2] = in[2][1]; - out[2][2] = in[2][2]; - - tmp[0] = in[3][0]; - tmp[1] = in[3][1]; - tmp[2] = in[3][2]; - - float v1[3], v2[3], v3[3]; - v1[0] = out[0][0]; - v1[1] = out[1][0]; - v1[2] = out[2][0]; - v2[0] = out[0][1]; - v2[1] = out[1][1]; - v2[2] = out[2][1]; - v3[0] = out[0][2]; - v3[1] = out[1][2]; - v3[2] = out[2][2]; - - out[3][0] = -DotProduct( tmp, v1 ); - out[3][1] = -DotProduct( tmp, v2 ); - out[3][2] = -DotProduct( tmp, v3 ); - - // Trivial case - // if (IS_IDENTITY(matrix)) - // return SbMatrix::identity(); - - // // Affine case... - // // SbMatrix affineAnswer; - // // if ( affine_inverse( SbMatrix(matrix), affineAnswer ) ) - // // return affineAnswer; - - // int index[4]; - // float d, invmat[4][4], temp; - // SbMatrix inverse = *this; - - // if(inverse.LUDecomposition(index, d)) { - - // invmat[0][0] = 1.0; - // invmat[0][1] = 0.0; - // invmat[0][2] = 0.0; - // invmat[0][3] = 0.0; - // inverse.LUBackSubstitution(index, invmat[0]); - // invmat[1][0] = 0.0; - // invmat[1][1] = 1.0; - // invmat[1][2] = 0.0; - // invmat[1][3] = 0.0; - // inverse.LUBackSubstitution(index, invmat[1]); - // invmat[2][0] = 0.0; - // invmat[2][1] = 0.0; - // invmat[2][2] = 1.0; - // invmat[2][3] = 0.0; - // inverse.LUBackSubstitution(index, invmat[2]); - // invmat[3][0] = 0.0; - // invmat[3][1] = 0.0; - // invmat[3][2] = 0.0; - // invmat[3][3] = 1.0; - // inverse.LUBackSubstitution(index, invmat[3]); - -// #define SWAP(i,j) \ - // temp = invmat[i][j]; \ - // invmat[i][j] = invmat[j][i]; \ - // invmat[j][i] = temp; - - // SWAP(1,0); - - // SWAP(2,0); - // SWAP(2,1); - - // SWAP(3,0); - // SWAP(3,1); - // SWAP(3,2); -// #undef SWAP - // } -} - -/* -================ -M_ConcatTransforms -================ -*/ -void M_ConcatTransforms (const M_matrix4x4_t &in1, const M_matrix4x4_t &in2, M_matrix4x4_t &out) -{ - - // Assert( s_bMathlibInitialized ); - // if ( &in1 == &out ) - // { - // matrix3x4_t in1b; - // MatrixCopy( in1, in1b ); - // ConcatTransforms( in1b, in2, out ); - // return; - // } - // if ( &in2 == &out ) - // { - // matrix3x4_t in2b; - // MatrixCopy( in2, in2b ); - // ConcatTransforms( in1, in2b, out ); - // return; - // } - -#define MULT(i,j) (in1[i][0]*in2[0][j] + \ - in1[i][1]*in2[1][j] + \ - in1[i][2]*in2[2][j] + \ - in1[i][3]*in2[3][j]) - - out[0][0] = MULT(0,0); - out[0][1] = MULT(0,1); - out[0][2] = MULT(0,2); - out[0][3] = MULT(0,3); - out[1][0] = MULT(1,0); - out[1][1] = MULT(1,1); - out[1][2] = MULT(1,2); - out[1][3] = MULT(1,3); - out[2][0] = MULT(2,0); - out[2][1] = MULT(2,1); - out[2][2] = MULT(2,2); - out[2][3] = MULT(2,3); - out[3][0] = MULT(3,0); - out[3][1] = MULT(3,1); - out[3][2] = MULT(3,2); - out[3][3] = MULT(3,3); - -#undef MULT - -} - -void M_AngleMatrix( RadianEuler const &angles, const Vector &position, M_matrix4x4_t& matrix ) -{ - // Assert( s_bMathlibInitialized ); - float sx, sy, sz, cx, cy, cz; - - - sx = sinf(angles[0]); - cx = cosf(angles[0]); - sy = sinf(angles[1]); - cy = cosf(angles[1]); - sz = sinf(angles[2]); - cz = cosf(angles[2]); - - // SinCos( angles[0], &sx, &cx ); // 2 - // SinCos( angles[1], &sy, &cy ); // 1 - // SinCos( angles[2], &sz, &cz ); // 0 - - M_matrix4x4_t mx, my, mz, temp1; - - // rotation about x - mx[1][1] = cx; - mx[1][2] = sx; - mx[2][1] = -sx; - mx[2][2] = cx; - - // rotation about y - my[0][0] = cy; - my[0][2] = -sy; - my[2][0] = sy; - my[2][2] = cy; - - // rotation about z - mz[0][0] = cz; - mz[0][1] = sz; - mz[1][0] = -sz; - mz[1][1] = cz; - - // z * y * x - M_ConcatTransforms(mx, my, temp1); - M_ConcatTransforms(temp1, mz, matrix); - - // put position in - matrix[3][0] = position.x; - matrix[3][1] = position.y; - matrix[3][2] = position.z; - -} - - -//----------------------------------------------------------------------------- -// Motion mapper functions -//----------------------------------------------------------------------------- -#define BONEAXIS 0 -#define BONEDIR 0 -#define BONESIDE 1 -#define BONEUP 2 -#define WORLDUP 2 -#define PRINTMAT(m) \ - printf("\n%f %f %f %f\n", m[0][0], m[0][1], m[0][2], m[0][3]); \ - printf("%f %f %f %f\n", m[1][0], m[1][1], m[1][2], m[1][3]); \ - printf("%f %f %f %f\n", m[2][0], m[2][1], m[2][2], m[2][3]); \ - printf("%f %f %f %f\n", m[3][0], m[3][1], m[3][2], m[3][3]); - -struct s_planeConstraint_t -{ - char jointNameString[1024]; - float floor; - int axis; - -}; - -struct s_iksolve_t -{ - char jointNameString[1024]; - int reverseSolve; - float extremityScale; - Vector limbRootOffsetScale; - int doRelativeLock; - char relativeLockNameString[1024]; - float relativeLockScale; - -}; - -struct s_jointScale_t -{ - char jointNameString[1024]; - float scale; -}; - -struct s_template_t -{ - char rootScaleJoint[1024]; - float rootScaleAmount; - int numIKSolves; - s_iksolve_t *ikSolves[128]; - int numJointScales; - s_jointScale_t *jointScales[128]; - int numPlaneConstraints; - s_planeConstraint_t *planeConstraints[128]; - float toeFloorZ; - int doSkeletonScale; - float skeletonScale; - -}; - - -//----------------------------------------------------------------------------- -// Load a template file into structure -//----------------------------------------------------------------------------- -s_template_t *New_Template() -{ - s_template_t *pTemplate = (s_template_t *)kalloc(1, sizeof(s_template_t)); - pTemplate->rootScaleAmount = 1.0; - pTemplate->numIKSolves = 0; - pTemplate->numJointScales = 0; - pTemplate->toeFloorZ = 2.802277; - pTemplate->numPlaneConstraints = 0; - pTemplate->doSkeletonScale = 0; - pTemplate->skeletonScale = 1.0; - return pTemplate; -} -s_iksolve_t *New_IKSolve() -{ - s_iksolve_t *pIKSolve = (s_iksolve_t *)kalloc(1, sizeof(s_iksolve_t)); - pIKSolve->reverseSolve = 0; - pIKSolve->extremityScale = 1.0; - pIKSolve->limbRootOffsetScale[0] = pIKSolve->limbRootOffsetScale[1] = pIKSolve->limbRootOffsetScale[2] = 0.0; - pIKSolve->doRelativeLock = 0; - pIKSolve->relativeLockScale = 1.0; - return pIKSolve; -} - -s_planeConstraint_t *New_planeConstraint(float floor) -{ - s_planeConstraint_t *pConstraint = (s_planeConstraint_t *)kalloc(1, sizeof(s_planeConstraint_t)); - pConstraint->floor = floor; - pConstraint->axis = 2; - - return pConstraint; -} - -void Set_DefaultTemplate(s_template_t *pTemplate) -{ - pTemplate->numJointScales = 0; - - strcpy(pTemplate->rootScaleJoint, "ValveBiped.Bip01_L_Foot"); - pTemplate->rootScaleAmount = 1.0; - - pTemplate->numIKSolves = 4; - pTemplate->ikSolves[0] = New_IKSolve(); - pTemplate->ikSolves[1] = New_IKSolve(); - pTemplate->ikSolves[2] = New_IKSolve(); - pTemplate->ikSolves[3] = New_IKSolve(); - - - pTemplate->numPlaneConstraints = 2; - pTemplate->planeConstraints[0] = New_planeConstraint(pTemplate->toeFloorZ); - strcpy(pTemplate->planeConstraints[0]->jointNameString, "ValveBiped.Bip01_L_Toe0"); - pTemplate->planeConstraints[1] = New_planeConstraint(pTemplate->toeFloorZ); - strcpy(pTemplate->planeConstraints[1]->jointNameString, "ValveBiped.Bip01_R_Toe0"); - - strcpy(pTemplate->ikSolves[0]->jointNameString, "ValveBiped.Bip01_L_Foot"); - pTemplate->ikSolves[0]->reverseSolve = 0; - pTemplate->ikSolves[0]->extremityScale = 1.0; - pTemplate->ikSolves[0]->limbRootOffsetScale[0] = 1.0; - pTemplate->ikSolves[0]->limbRootOffsetScale[1] = 1.0; - pTemplate->ikSolves[0]->limbRootOffsetScale[2] = 0.0; - - strcpy(pTemplate->ikSolves[1]->jointNameString, "ValveBiped.Bip01_R_Foot"); - pTemplate->ikSolves[1]->reverseSolve = 0; - pTemplate->ikSolves[1]->extremityScale = 1.0; - pTemplate->ikSolves[1]->limbRootOffsetScale[0] = 1.0; - pTemplate->ikSolves[1]->limbRootOffsetScale[1] = 1.0; - pTemplate->ikSolves[1]->limbRootOffsetScale[2] = 0.0; - - strcpy(pTemplate->ikSolves[2]->jointNameString, "ValveBiped.Bip01_R_Hand"); - pTemplate->ikSolves[2]->reverseSolve = 1; - pTemplate->ikSolves[2]->extremityScale = 1.0; - pTemplate->ikSolves[2]->limbRootOffsetScale[0] = 0.0; - pTemplate->ikSolves[2]->limbRootOffsetScale[1] = 0.0; - pTemplate->ikSolves[2]->limbRootOffsetScale[2] = 1.0; - - strcpy(pTemplate->ikSolves[3]->jointNameString, "ValveBiped.Bip01_L_Hand"); - pTemplate->ikSolves[3]->reverseSolve = 1; - pTemplate->ikSolves[3]->extremityScale = 1.0; - pTemplate->ikSolves[3]->limbRootOffsetScale[0] = 0.0; - pTemplate->ikSolves[3]->limbRootOffsetScale[1] = 0.0; - pTemplate->ikSolves[3]->limbRootOffsetScale[2] = 1.0; - // pTemplate->ikSolves[3]->doRelativeLock = 1; - // strcpy(pTemplate->ikSolves[3]->relativeLockNameString, "ValveBiped.Bip01_R_Hand"); - // pTemplate->ikSolves[3]->relativeLockScale = 1.0; - -} - -void split(char *str, char *sep, char **sp) -{ - char *r = strtok(str, sep); - while(r != NULL) - { - *sp = r; - sp++; - r = strtok(NULL, sep); - } - *sp = NULL; -} - - -int checkCommand(char *str, char *cmd, int numOptions, int numSplit) -{ - if(strcmp(str, cmd) == 0) - { - if(numOptions <= numSplit) - return 1; - else - { - printf("Error: Number or argument mismatch in template file cmd %s, requires %i, found %i\n", cmd, numOptions, numSplit); - return 0; - } - } - return 0; -} - -s_template_t *Load_Template(char *name ) -{ - - // Sanity check file and init - Assert(name); - - s_template_t *pTemplate = New_Template(); - - - // Open file - if (!OpenGlobalFile( name )) - return 0; - - - //March through lines - g_iLinecount = 0; - while(fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) - { - g_iLinecount++; - if(g_szLine[0] == '#') - continue; - - char *endP = strrchr(g_szLine, '\n'); - if(endP != NULL) - *endP = '\0'; - - - char *sp[128]; - char **spp = sp; - - char sep[] = " "; - split(g_szLine, sep, sp); - int numSplit = 0; - - while(*spp != NULL) - { - spp++; - numSplit++; - - } - if(numSplit < 1 || - *sp[0] == '\n') - continue; - - - // int numRead = sscanf( g_szLine, "%s %s %s", cmd, &option, &option2 ); - - // // Blank line - // if ((numRead == EOF) || (numRead == 0)) - // continue; - - // commands - char *cmd; - int numOptions = numSplit - 1; - - cmd = sp[0]; - if(checkCommand(cmd, "twoJointIKSolve", 1, numOptions)) - { - printf("\nCreating two joint IK solve %s\n", sp[1]); - pTemplate->ikSolves[pTemplate->numIKSolves] = New_IKSolve(); - strcpy(pTemplate->ikSolves[pTemplate->numIKSolves]->jointNameString, sp[1]); - pTemplate->numIKSolves++; - - } - else if(checkCommand(cmd, "oneJointPlaneConstraint", 1, numOptions)) - { - printf("\nCreating one joint plane constraint %s\n", sp[1]); - pTemplate->planeConstraints[pTemplate->numPlaneConstraints] = New_planeConstraint(pTemplate->toeFloorZ); - strcpy(pTemplate->planeConstraints[pTemplate->numPlaneConstraints]->jointNameString, sp[1]); - pTemplate->numPlaneConstraints++; - - } - else if(checkCommand(cmd, "reverseSolve", 1, numOptions)) - { - printf("reverseSolve: %s\n", sp[1]); - pTemplate->ikSolves[pTemplate->numIKSolves - 1]->reverseSolve = atoi(sp[1]); - } - else if(checkCommand(cmd, "extremityScale", 1, numOptions)) - { - printf("extremityScale: %s\n", sp[1]); - pTemplate->ikSolves[pTemplate->numIKSolves - 1]->extremityScale = atof(sp[1]); - } - else if(checkCommand(cmd, "limbRootOffsetScale", 3, numOptions)) - { - printf("limbRootOffsetScale: %s %s %s\n", sp[1], sp[2], sp[3]); - pTemplate->ikSolves[pTemplate->numIKSolves - 1]->limbRootOffsetScale[0] = atof(sp[1]); - pTemplate->ikSolves[pTemplate->numIKSolves - 1]->limbRootOffsetScale[1] = atof(sp[2]); - pTemplate->ikSolves[pTemplate->numIKSolves - 1]->limbRootOffsetScale[2] = atof(sp[3]); - } - else if(checkCommand(cmd, "toeFloorZ", 1, numOptions)) - { - printf("toeFloorZ: %s\n", sp[1]); - pTemplate->toeFloorZ = atof(sp[1]); - } - else if(checkCommand(cmd, "relativeLock", 2, numOptions)) - { - printf("relativeLock: %s\n", sp[1]); - pTemplate->ikSolves[pTemplate->numIKSolves - 1]->doRelativeLock = 1; - strcpy(pTemplate->ikSolves[pTemplate->numIKSolves - 1]->relativeLockNameString, sp[1]); - pTemplate->ikSolves[pTemplate->numIKSolves - 1]->relativeLockScale = atof(sp[2]); - - } - else if(checkCommand(cmd, "rootScaleJoint", 1, numOptions)) - { - printf("\nrootScaleJoint: %s\n", sp[1]); - strcpy(pTemplate->rootScaleJoint, sp[1]); - } - else if(checkCommand(cmd, "rootScaleAmount", 1, numOptions)) - { - printf("rootScaleAmount: %s\n", sp[1]); - pTemplate->rootScaleAmount = atof(sp[1]); - } - else if(checkCommand(cmd, "jointScale", 2, numOptions)) - { - printf("\nCreating joint scale %s of %s\n", sp[1], sp[2]); - pTemplate->jointScales[pTemplate->numJointScales] = (s_jointScale_t *)kalloc(1, sizeof(s_jointScale_t)); - strcpy(pTemplate->jointScales[pTemplate->numJointScales]->jointNameString, sp[1]); - pTemplate->jointScales[pTemplate->numJointScales]->scale = atof(sp[2]); - pTemplate->numJointScales++; - } - else if(checkCommand(cmd, "skeletonScale", 2, numOptions)) - { - printf("\nCreating skeleton scale of %s\n", sp[1]); - pTemplate->doSkeletonScale = 1; - pTemplate->skeletonScale = atof(sp[1]); - } - else - { - MdlWarning("unknown studio command\n" ); - } - } - fclose( g_fpInput ); - return pTemplate; -} - -//----------------------------------------------------------------------------- -// get node index from node string name -//----------------------------------------------------------------------------- -int GetNodeIndex(s_source_t *psource, char *nodeName) -{ - for(int i = 0; i < psource->numbones; i++) - { - if(strcmp(nodeName, psource->localBone[i].name) == 0) - { - return i; - } - } - return -1; -} - -//----------------------------------------------------------------------------- -// get node index from node string name -//----------------------------------------------------------------------------- -void GetNodePath(s_source_t *psource, int startIndex, int endIndex, int *path) -{ - *path = endIndex; - - s_node_t *nodes; - nodes = psource->localBone; - while(*path != startIndex) - { - int parent = nodes[*path].parent; - path++; - *path = parent; - } - path++; - *path = -1; -} - -void SumBonePathTranslations(int *indexPath, s_bone_t *boneArray, Vector &resultVector, int rootOffset = 0) -{ - - // walk the path - int *pathPtr = indexPath; - // M_matrix4x4_t matrixCum; - - // find length of path - int length = 0; - while(*pathPtr != -1) - { - length++; - pathPtr++; - } - - int l = length - (1 + rootOffset); - - resultVector[0] = 0.0; - resultVector[1] = 0.0; - resultVector[2] = 0.0; - - for(int i = l; i > -1; i--) - { - s_bone_t *thisBone = boneArray + indexPath[i]; - resultVector += thisBone->pos; - } -} - -void CatBonePath(int *indexPath, s_bone_t *boneArray, M_matrix4x4_t &resultMatrix, int rootOffset = 0) -{ - - // walk the path - int *pathPtr = indexPath; - // M_matrix4x4_t matrixCum; - - // find length of path - int length = 0; - while(*pathPtr != -1) - { - length++; - pathPtr++; - } - - int l = length - (1 + rootOffset); - - for(int i = l; i > -1; i--) - { - s_bone_t *thisBone = boneArray + indexPath[i]; - // printf("bone index: %i %i\n", i, indexPath[i]); - // printf("pos: %f %f %f, rot: %f %f %f\n", thisBone->pos.x, thisBone->pos.y, thisBone->pos.z, thisBone->rot.x, thisBone->rot.y, thisBone->rot.z); - M_matrix4x4_t thisMatrix; - M_AngleMatrix(thisBone->rot, thisBone->pos, thisMatrix); - // PRINTMAT(thisMatrix) - M_matrix4x4_t tempCum; - M_MatrixCopy(resultMatrix, tempCum); - M_ConcatTransforms(thisMatrix, tempCum, resultMatrix); - } - // PRINTMAT(matrixCum); - // M_MatrixAngles(matrixCum, resultBone.rot, resultBone.pos); - - // printf("pos: %f %f %f, rot: %f %f %f\n", resultBone.pos.x,resultBone.pos.y, resultBone.pos.z, RAD2DEG(resultBone.rot.x),RAD2DEG(resultBone.rot.y),RAD2DEG(resultBone.rot.z)); - -} -// int ConformSources(s_source_t *pSource, s_source_t *pTarget) -// { - // if(pSource->numbones != *pTarget->numbones) - // { - // printf("ERROR: The number of bones in the target file must match the source file."); - // return 1; - // } - // if(pSource->numframes != pTarget->numframes) - // { - // printf("Note: Source and target frame lengths do not match"); - // for(int t = 0; t < pTarget->numframes; t++) - // { - // free(pTarget->rawanim[t]); - // } - // pTarget->numframes = pSource->numframes; - // int size = pTarget->numbones * sizeof( s_bone_t ); - // for(t = 0; t < pTarget->numframes; t++) - // { - // pTarget->rawanim[t] = (s_bone_t *) kalloc(1, size); - // memcpy((void *) pSource->rawanim[t], (void *) pTarget->rawanim[t], size - // } - // } - // pTarget->startframe = pSource->startframe; - // pTarget->endframe = pSource->endframe; - - - - -void ScaleJointsFrame(s_source_t *pSkeleton, s_jointScale_t *jointScale, int t) -{ - int numBones = pSkeleton->numbones; - - for(int i = 0; i < numBones; i++) - { - s_node_t pNode = pSkeleton->localBone[i]; - s_bone_t *pSkelBone = &pSkeleton->rawanim[t][i]; - if(strcmp(jointScale->jointNameString, pNode.name) == 0) - { - // printf("Scaling joint %s\n", pNode.name); - pSkelBone->pos = pSkelBone->pos * jointScale->scale; - } - - } -} -void ScaleJoints(s_source_t *pSkeleton, s_jointScale_t *jointScale) -{ - int numFrames = pSkeleton->numframes; - for(int t = 0; t < numFrames; t++) - { - ScaleJointsFrame(pSkeleton, jointScale, t); - } -} - -void ScaleSkeletonFrame(s_source_t *pSkeleton, float scale, int t) -{ - int numBones = pSkeleton->numbones; - - for(int i = 0; i < numBones; i++) - { - s_bone_t *pSkelBone = &pSkeleton->rawanim[t][i]; - pSkelBone->pos = pSkelBone->pos * scale; - - } -} -void ScaleSkeleton(s_source_t *pSkeleton, float scale) -{ - int numFrames = pSkeleton->numframes; - for(int t = 0; t < numFrames; t++) - { - ScaleSkeletonFrame(pSkeleton, scale, t); - } -} - -void CombineSkeletonAnimationFrame(s_source_t *pSkeleton, s_source_t *pAnimation, s_bone_t **ppAnim, int t) -{ - int numBones = pAnimation->numbones; - int size = numBones * sizeof( s_bone_t ); - ppAnim[t] = (s_bone_t *) kalloc(1, size); - for(int i = 0; i < numBones; i++) - { - s_node_t pNode = pAnimation->localBone[i]; - s_bone_t pAnimBone = pAnimation->rawanim[t][i]; - - if(pNode.parent > -1) - { - if ( i < pSkeleton->numbones ) - { - s_bone_t pSkelBone = pSkeleton->rawanim[0][i]; - ppAnim[t][i].pos = pSkelBone.pos; - } - else - { - if ( !g_bGaveMissingBoneWarning ) - { - g_bGaveMissingBoneWarning = true; - Warning( "Warning: Target skeleton has less bones than source animation. Reverting to source data for extra bones.\n" ); - } - - ppAnim[t][i].pos = pAnimBone.pos; - } - } - else - { - ppAnim[t][i].pos = pAnimBone.pos; - } - - ppAnim[t][i].rot = pAnimBone.rot; - } -} -void CombineSkeletonAnimation(s_source_t *pSkeleton, s_source_t *pAnimation, s_bone_t **ppAnim) -{ - int numFrames = pAnimation->numframes; - for(int t = 0; t < numFrames; t++) - { - CombineSkeletonAnimationFrame(pSkeleton, pAnimation, ppAnim, t); - } -} - - -//-------------------------------------------------------------------- -// MotionMap -//-------------------------------------------------------------------- -s_source_t *MotionMap( s_source_t *pSource, s_source_t *pTarget, s_template_t *pTemplate ) -{ - - // scale skeleton - if(pTemplate->doSkeletonScale) - { - ScaleSkeleton(pTarget, pTemplate->skeletonScale); - } - - // scale joints - for(int j = 0; j < pTemplate->numJointScales; j++) - { - s_jointScale_t *pJointScale = pTemplate->jointScales[j]; - ScaleJoints(pTarget, pJointScale); - } - - - // root stuff - char rootString[128] = "ValveBiped.Bip01"; - - // !!! PARAMETER - int rootIndex = GetNodeIndex(pSource, rootString); - int rootScaleIndex = GetNodeIndex(pSource, pTemplate->rootScaleJoint); - int rootScalePath[512]; - if(rootScaleIndex > -1) - { - GetNodePath(pSource, rootIndex, rootScaleIndex, rootScalePath); - } - else - { - printf("Error: Can't find node\n"); - exit(0); - } - float rootScaleLengthSrc = pSource->rawanim[0][rootScaleIndex].pos[BONEDIR]; - float rootScaleParentLengthSrc = pSource->rawanim[0][rootScalePath[1]].pos[BONEDIR]; - float rootScaleSrc = rootScaleLengthSrc + rootScaleParentLengthSrc; - float rootScaleLengthTgt = pTarget->rawanim[0][rootScaleIndex].pos[BONEDIR]; - float rootScaleParentLengthTgt = pTarget->rawanim[0][rootScalePath[1]].pos[BONEDIR]; - float rootScaleTgt = rootScaleLengthTgt + rootScaleParentLengthTgt; - float rootScaleFactor = rootScaleTgt / rootScaleSrc; - - if(g_verbose) - printf("Root Scale Factor: %f\n", rootScaleFactor); - - - // root scale origin - float toeFloorZ = pTemplate->toeFloorZ; - Vector rootScaleOrigin = pSource->rawanim[0][rootIndex].pos; - rootScaleOrigin[2] = toeFloorZ; - - - // setup workspace - s_bone_t *combinedRefAnimation[MAXSTUDIOANIMFRAMES]; - s_bone_t *combinedAnimation[MAXSTUDIOANIMFRAMES]; - s_bone_t *sourceAnimation[MAXSTUDIOANIMFRAMES]; - CombineSkeletonAnimation(pTarget, pSource, combinedAnimation); - CombineSkeletonAnimation(pTarget, pSource, combinedRefAnimation); - - - // do source and target sanity checking - int sourceNumFrames = pSource->numframes; - - - // iterate through limb solves - for(int t = 0; t < sourceNumFrames; t++) - { - // setup pTarget for skeleton comparison - pTarget->rawanim[t] = combinedRefAnimation[t]; - - printf("Note: Processing frame: %i\n", t); - for(int ii = 0; ii < pTemplate->numIKSolves; ii++) - { - s_iksolve_t *thisSolve = pTemplate->ikSolves[ii]; - - char *thisJointNameString = thisSolve->jointNameString; - int thisJointIndex = GetNodeIndex(pSource, thisJointNameString); - - // init paths to feet - int thisJointPathInRoot[512]; - - // get paths to feet - if(thisJointIndex > -1) - { - GetNodePath(pSource, rootIndex, thisJointIndex, thisJointPathInRoot); - } - else - { - printf("Error: Can't find node: %s\n" , thisJointNameString); - exit(0); - } - - // leg "root" or thigh pointers - //int gParentIndex = thisJointPathInRoot[2]; - int *gParentPath = thisJointPathInRoot + 2; - - //---------------------------------------------------------------- - // get limb lengths - //---------------------------------------------------------------- - float thisJointLengthSrc = pSource->rawanim[0][thisJointIndex].pos[BONEDIR]; - float parentJointLengthSrc = pSource->rawanim[0][thisJointPathInRoot[1]].pos[BONEDIR]; - - float thisLimbLengthSrc = thisJointLengthSrc + parentJointLengthSrc; - - float thisJointLengthTgt = pTarget->rawanim[0][thisJointIndex].pos[BONEDIR]; - float parentJointLengthTgt = pTarget->rawanim[0][thisJointPathInRoot[1]].pos[BONEDIR]; - - float thisLimbLengthTgt = thisJointLengthTgt + parentJointLengthTgt; - - // Factor leg length delta - float thisLimbLength = thisLimbLengthSrc - thisLimbLengthTgt; - float thisLimbLengthFactor = thisLimbLengthTgt / thisLimbLengthSrc; - - if(g_verbose) - printf("limb length %s: %i: %f, factor %f\n", thisJointNameString, thisJointIndex, thisLimbLength, thisLimbLengthFactor); - - // calculate joint grandparent offset - // Note: because there's no reference pose this doesn't take rotation into account. - // This only works because of the assumption that joint translations aren't animated. - M_matrix4x4_t gParentGlobalMatSrc, gParentGlobalMatTgt; - Vector gParentGlobalSrc, gParentGlobalTgt; - - // SumBonePathTranslations(gParentPath, pSource->rawanim[t], gParentGlobalSrc, 1); - // SumBonePathTranslations(gParentPath, pTarget->rawanim[t], gParentGlobalTgt, 1); - - // get root path to source parent - CatBonePath(gParentPath, pSource->rawanim[t], gParentGlobalMatSrc, 1); - // check against reference animation - CatBonePath(gParentPath, pTarget->rawanim[t], gParentGlobalMatTgt, 1); - - gParentGlobalSrc[0] = gParentGlobalMatSrc[3][0]; - gParentGlobalSrc[1] = gParentGlobalMatSrc[3][1]; - gParentGlobalSrc[2] = gParentGlobalMatSrc[3][2]; - - gParentGlobalTgt[0] = gParentGlobalMatTgt[3][0]; - gParentGlobalTgt[1] = gParentGlobalMatTgt[3][1]; - gParentGlobalTgt[2] = gParentGlobalMatTgt[3][2]; - - - Vector gParentDelta(gParentGlobalTgt - gParentGlobalSrc); - - if(g_verbose) - printf("Grand parent delta: %f %f %f\n", gParentDelta[0], gParentDelta[1], gParentDelta[2]); - - gParentDelta *= thisSolve->limbRootOffsetScale; - - - //---------------------------------------------------------------- - // time takes effect here - // above waste is unavoidable? - //---------------------------------------------------------------- - M_matrix4x4_t rootMat; - M_AngleMatrix(pSource->rawanim[t][rootIndex].rot, pSource->rawanim[t][rootIndex].pos, rootMat); - - - // OK, time to get it together - // 1) scale foot by legLengthFactor in the non-translated thigh space - // 2) translate foot by legRootDelta in the space of the root - // do we leave everything in the space of the root then? PROBABLY!! - - M_matrix4x4_t thisJointMat, parentJointMat, thisJointInGParentMat; - M_AngleMatrix(pSource->rawanim[t][thisJointPathInRoot[0]].rot, pSource->rawanim[t][thisJointPathInRoot[0]].pos, thisJointMat); - M_AngleMatrix(pSource->rawanim[t][thisJointPathInRoot[1]].rot, pSource->rawanim[t][thisJointPathInRoot[1]].pos, parentJointMat); - M_ConcatTransforms(thisJointMat, parentJointMat, thisJointInGParentMat); - - if(!thisSolve->doRelativeLock) - { - // scale around grand parent - float effectiveScaleFactor = ((thisLimbLengthFactor - 1.0) * thisSolve->extremityScale ) + 1.0; - thisJointInGParentMat[3][0] *= effectiveScaleFactor; - thisJointInGParentMat[3][1] *= effectiveScaleFactor; - thisJointInGParentMat[3][2] *= effectiveScaleFactor; - } - - // adjust into source root space - M_matrix4x4_t gParentInRootMat, thisJointInRootMat; - CatBonePath(gParentPath, pSource->rawanim[t], gParentInRootMat, 1); - M_ConcatTransforms(thisJointInGParentMat, gParentInRootMat, thisJointInRootMat); - - if(!thisSolve->doRelativeLock) - { - // adjust by difference of local root - thisJointInRootMat[3][0] += gParentDelta[0]; - thisJointInRootMat[3][1] += gParentDelta[1]; - thisJointInRootMat[3][2] += gParentDelta[2]; - } - else - { - char *relativeJointNameString = thisSolve->relativeLockNameString; - int relativeJointIndex = GetNodeIndex(pSource, relativeJointNameString); - - // init paths to feet - int relativeJointPathInRoot[512]; - - // get paths to feet - if(relativeJointIndex > -1) - { - GetNodePath(pSource, rootIndex, relativeJointIndex, relativeJointPathInRoot); - } - else - { - printf("Error: Can't find node: %s\n" , relativeJointNameString); - exit(0); - } - // get the source relative joint - M_matrix4x4_t relativeJointInRootMatSrc, relativeJointInRootMatSrcInverse, thisJointInRelativeSrcMat; - CatBonePath(relativeJointPathInRoot, pSource->rawanim[t], relativeJointInRootMatSrc, 1); - M_MatrixInvert(relativeJointInRootMatSrc, relativeJointInRootMatSrcInverse); - M_ConcatTransforms(thisJointInRootMat, relativeJointInRootMatSrcInverse, thisJointInRelativeSrcMat); - if(thisSolve->relativeLockScale != 1.0) - { - thisJointInRelativeSrcMat[3][0] *= thisSolve->relativeLockScale; - thisJointInRelativeSrcMat[3][1] *= thisSolve->relativeLockScale; - thisJointInRelativeSrcMat[3][2] *= thisSolve->relativeLockScale; - } - - // swap momentarily to get new destination - // NOTE: the relative lock must have already been solved - sourceAnimation[t] = pSource->rawanim[t]; - pSource->rawanim[t] = combinedAnimation[t]; - - // get new relative location - M_matrix4x4_t relativeJointInRootMatTgt; - CatBonePath(relativeJointPathInRoot, pSource->rawanim[t], relativeJointInRootMatTgt, 1); - M_ConcatTransforms(thisJointInRelativeSrcMat, relativeJointInRootMatTgt, thisJointInRootMat); - - // swap back just for cleanliness - // a little overkill as it's just swapped - // just leaving it here for clarity - combinedAnimation[t] = pSource->rawanim[t]; - pSource->rawanim[t] = sourceAnimation[t]; - - } - - //---------------------------------------------------------------- - // swap animation - //---------------------------------------------------------------- - sourceAnimation[t] = pSource->rawanim[t]; - pSource->rawanim[t] = combinedAnimation[t]; - - //---------------------------------------------------------------- - // make thigh data global based on new skeleton - //---------------------------------------------------------------- - // get thigh in global space - M_matrix4x4_t gParentInTgtRootMat, ggParentInTgtRootMat; - // int *gParentPath = thisJointPathInRoot + 2; - CatBonePath(gParentPath, pSource->rawanim[t], gParentInTgtRootMat, 1); - CatBonePath(gParentPath+1, pSource->rawanim[t], ggParentInTgtRootMat, 1); - - - //---------------------------------------------------------------- - // Calculate IK for legs - //---------------------------------------------------------------- - float parentJointLength = pSource->rawanim[t][*(thisJointPathInRoot + 1)].pos[BONEDIR]; - float thisJointLength = pSource->rawanim[t][thisJointIndex].pos[BONEDIR]; - - Vector thisLimbHypot; - thisLimbHypot[0] = thisJointInRootMat[3][0] - gParentInTgtRootMat[3][0]; - thisLimbHypot[1] = thisJointInRootMat[3][1] - gParentInTgtRootMat[3][1]; - thisLimbHypot[2] = thisJointInRootMat[3][2] - gParentInTgtRootMat[3][2]; - - float thisLimbHypotLength = thisLimbHypot.Length(); - - // law of cosines! - float gParentCos = (thisLimbHypotLength*thisLimbHypotLength + parentJointLength*parentJointLength - thisJointLength*thisJointLength) / (2*parentJointLength*thisLimbHypotLength); - float parentCos = (parentJointLength*parentJointLength + thisJointLength*thisJointLength - thisLimbHypotLength*thisLimbHypotLength) / (2*parentJointLength*thisJointLength); - - VectorNormalize(thisLimbHypot); - - Vector thisLimbHypotUnit = thisLimbHypot; - - M_matrix4x4_t gParentJointIKMat; - Vector gParentJointIKRot, gParentJointIKOrth; - - gParentJointIKRot[0] = gParentInTgtRootMat[BONEUP][0]; - gParentJointIKRot[1] = gParentInTgtRootMat[BONEUP][1]; - gParentJointIKRot[2] = gParentInTgtRootMat[BONEUP][2]; - - VectorNormalize(gParentJointIKRot); - gParentJointIKOrth = gParentJointIKRot.Cross(thisLimbHypotUnit); - VectorNormalize(gParentJointIKOrth); - gParentJointIKRot = thisLimbHypotUnit.Cross(gParentJointIKOrth); - VectorNormalize(gParentJointIKRot); - - M_MatrixCopy(gParentInTgtRootMat, gParentJointIKMat); - - gParentJointIKMat[0][0] = thisLimbHypotUnit[0]; - gParentJointIKMat[0][1] = thisLimbHypotUnit[1]; - gParentJointIKMat[0][2] = thisLimbHypotUnit[2]; - - gParentJointIKMat[1][0] = gParentJointIKOrth[0]; - gParentJointIKMat[1][1] = gParentJointIKOrth[1]; - gParentJointIKMat[1][2] = gParentJointIKOrth[2]; - - gParentJointIKMat[2][0] = gParentJointIKRot[0]; - gParentJointIKMat[2][1] = gParentJointIKRot[1]; - gParentJointIKMat[2][2] = gParentJointIKRot[2]; - - - M_matrix4x4_t gParentJointIKRotMat, gParentJointResultMat; - float gParentDeg; - if(thisSolve->reverseSolve) - { - gParentDeg = acos(gParentCos); - } - else - { - gParentDeg = -acos(gParentCos); - } - - // sanity check limb length - if(thisLimbHypotLength < thisLimbLengthTgt) - { - M_RotateZMatrix(gParentDeg, gParentJointIKRotMat); - } - - M_ConcatTransforms(gParentJointIKRotMat, gParentJointIKMat, gParentJointResultMat); - - M_matrix4x4_t parentJointIKRotMat; - //!!! shouldn't need the 180 degree addition, something in the law of cosines!!! - float parentDeg; - if(thisSolve->reverseSolve) - { - parentDeg = acos(parentCos)+M_PI; - } - else - { - parentDeg = -acos(parentCos)+M_PI; - } - - // sanity check limb length - if(thisLimbHypotLength < thisLimbLengthTgt) - { - M_RotateZMatrix(parentDeg, parentJointIKRotMat); - } - - - // Thighs - M_matrix4x4_t ggParentInTgtRootMatInverse, gParentJointLocalMat; - M_MatrixInvert(ggParentInTgtRootMat, ggParentInTgtRootMatInverse); - M_ConcatTransforms(gParentJointResultMat, ggParentInTgtRootMatInverse, gParentJointLocalMat); - - s_bone_t resultBone; - - // temp test stuff - // M_MatrixAngles(thisJointInRootMat, resultBone.rot, resultBone.pos); - // pSource->rawanim[t][thisJointIndex].rot = resultBone.rot; - // pSource->rawanim[t][thisJointIndex].pos = resultBone.pos; - - // M_MatrixAngles(gParentInTgtRootMat, resultBone.rot, resultBone.pos); - // pSource->rawanim[t][gParentIndex].rot = resultBone.rot; - // pSource->rawanim[t][gParentIndex].pos = resultBone.pos; - - - M_MatrixAngles(gParentJointLocalMat, resultBone.rot, resultBone.pos); - pSource->rawanim[t][*gParentPath].pos = resultBone.pos; - pSource->rawanim[t][*gParentPath].rot = resultBone.rot; - - M_MatrixAngles(parentJointIKRotMat, resultBone.rot, resultBone.pos); - pSource->rawanim[t][*(thisJointPathInRoot+1)].rot = resultBone.rot; - - M_matrix4x4_t parentJointGlobalMat, parentJointGlobalMatInverse, thisJointLocalMat; - CatBonePath(thisJointPathInRoot+1, pSource->rawanim[t], parentJointGlobalMat, 1); - - - M_MatrixInvert(parentJointGlobalMat, parentJointGlobalMatInverse); - M_ConcatTransforms(thisJointInRootMat, parentJointGlobalMatInverse, thisJointLocalMat); - - M_MatrixAngles(thisJointLocalMat, resultBone.rot, resultBone.pos); - pSource->rawanim[t][thisJointIndex].rot = resultBone.rot; - - - // swap animation back for next solve - combinedAnimation[t] = pSource->rawanim[t]; - pSource->rawanim[t] = sourceAnimation[t]; - - } - // swap animation - sourceAnimation[t] = pSource->rawanim[t]; - pSource->rawanim[t] = combinedAnimation[t]; - - //---------------------------------------------------------------- - // adjust root - //---------------------------------------------------------------- - Vector originBonePos = pSource->rawanim[t][rootIndex].pos; - Vector rootInScaleOrigin = originBonePos - rootScaleOrigin; - float effectiveRootScale = ((rootScaleFactor - 1.0) * pTemplate->rootScaleAmount) + 1.0; - Vector scaledRoot = rootInScaleOrigin * effectiveRootScale; - pSource->rawanim[t][rootIndex].pos = rootScaleOrigin + scaledRoot; - - //------------------------------------------------------------ - // plane constraints - //------------------------------------------------------------ - for(int ii = 0; ii < pTemplate->numPlaneConstraints; ii++) - { - s_planeConstraint_t *thisSolve = pTemplate->planeConstraints[ii]; - - char *thisJointNameString = thisSolve->jointNameString; - if(g_verbose) - printf("Executing plane constraint: %s\n", thisJointNameString); - - int thisJointIndex = GetNodeIndex(pSource, thisJointNameString); - - // init paths to feet - int thisJointPath[512]; - - // get paths to feet - if(thisJointIndex > -1) - { - GetNodePath(pSource, -1, thisJointIndex, thisJointPath); - } - else - { - printf("Error: Can't find node: %s\n" , thisJointNameString); - exit(0); - } - int parentIndex = thisJointPath[1]; - int *parentPath = thisJointPath + 1; - - M_matrix4x4_t thisJointGlobalMat, parentJointGlobalMat, gParentJointGlobalMat, gParentJointGlobalMatInverse; - CatBonePath(thisJointPath, pSource->rawanim[t], thisJointGlobalMat, 0); - CatBonePath(parentPath, pSource->rawanim[t], parentJointGlobalMat, 0); - CatBonePath(parentPath+1, pSource->rawanim[t], gParentJointGlobalMat, 0); - M_MatrixInvert(gParentJointGlobalMat, gParentJointGlobalMatInverse); - - if(thisJointGlobalMat[3][thisSolve->axis] < thisSolve->floor) - { - // printf("-- broken plane: %f\n", thisJointGlobalMat[3][thisSolve->axis]); - if(parentJointGlobalMat[3][thisSolve->axis] < thisSolve->floor) - { - printf("Error: Constraint parent has broken the plane, this frame's plane constraint unsolvable!\n"); - } - else - { - Vector parentJointAtPlane(parentJointGlobalMat[3][0], parentJointGlobalMat[3][1], parentJointGlobalMat[3][2]); - Vector parentPos(parentJointGlobalMat[3][0], parentJointGlobalMat[3][1], parentJointGlobalMat[3][2]); - Vector thisJointAtPlane(thisJointGlobalMat[3][0], thisJointGlobalMat[3][1], thisJointGlobalMat[3][2]); - Vector thisJointPos(thisJointGlobalMat[3][0], thisJointGlobalMat[3][1], thisJointGlobalMat[3][2]); - - thisJointAtPlane[thisSolve->axis] = thisSolve->floor; - parentJointAtPlane[thisSolve->axis] = thisSolve->floor; - - float thisJointLength = pSource->rawanim[t][thisJointIndex].pos[BONEAXIS]; - float parentLengthToPlane = parentPos[thisSolve->axis] - thisSolve->floor; - float adjacent = sqrtf((thisJointLength * thisJointLength) - (parentLengthToPlane * parentLengthToPlane)); - Vector parentDirection = thisJointAtPlane - parentJointAtPlane; - VectorNormalize(parentDirection); - - Vector newJointPos = parentJointAtPlane + (parentDirection * adjacent); - - Vector newParentDir = newJointPos - parentPos; - Vector parentUp(parentJointGlobalMat[BONEUP][0], parentJointGlobalMat[BONEUP][1], parentJointGlobalMat[BONEUP][2]); - - VectorNormalize(newParentDir); - VectorNormalize(parentUp); - // Vector parentSide = newParentDir.Cross(parentUp); - Vector parentSide = parentUp.Cross(newParentDir); - VectorNormalize(parentSide); - parentUp = newParentDir.Cross(parentSide); - // parentUp = parentSide.Cross(newParentDir); - VectorNormalize(parentUp); - parentJointGlobalMat[BONEDIR][0] = newParentDir[0]; - parentJointGlobalMat[BONEDIR][1] = newParentDir[1]; - parentJointGlobalMat[BONEDIR][2] = newParentDir[2]; - parentJointGlobalMat[BONEUP][0] = parentUp[0]; - parentJointGlobalMat[BONEUP][1] = parentUp[1]; - parentJointGlobalMat[BONEUP][2] = parentUp[2]; - parentJointGlobalMat[BONESIDE][0] = parentSide[0]; - parentJointGlobalMat[BONESIDE][1] = parentSide[1]; - parentJointGlobalMat[BONESIDE][2] = parentSide[2]; - - - M_matrix4x4_t newParentJointMat; - - M_ConcatTransforms(parentJointGlobalMat, gParentJointGlobalMatInverse, newParentJointMat); - - s_bone_t resultBone; - M_MatrixAngles(newParentJointMat, resultBone.rot, resultBone.pos); - pSource->rawanim[t][parentIndex].rot = resultBone.rot; - } - } - } - - // swap animation back for next solve - combinedAnimation[t] = pSource->rawanim[t]; - pSource->rawanim[t] = sourceAnimation[t]; - } - for(int t = 0; t < sourceNumFrames; t++) - { - pTarget->rawanim[t] = combinedAnimation[t]; - } - pTarget->numframes = sourceNumFrames; - - - - - -#if 0 - // Process motion mapping into out and return that - s_source_t *out = new s_source_t; - - return out; -#else - // Just returns the start animation, to test the Save_SMD API. - return pTarget; -#endif -} - -char templates[] = -"\n\ -#\n\ -# default template file is analogus to not specifying a template file at all\n\ -#\n\ -\n\ -rootScaleJoint ValveBiped.Bip01_L_Foot\n\ -rootScaleAmount 1.0\n\ -toeFloorZ 2.7777\n\ -\n\ -twoJointIKSolve ValveBiped.Bip01_L_Foot\n\ -reverseSolve 0\n\ -extremityScale 1.0\n\ -limbRootOffsetScale 1.0 1.0 0.0\n\ -\n\ -twoJointIKSolve ValveBiped.Bip01_R_Foot\n\ -reverseSolve 0\n\ -extremityScale 1.0\n\ -limbRootOffsetScale 1.0 1.0 0.0\n\ -\n\ -oneJointPlaneConstraint ValveBiped.Bip01_L_Toe0\n\ -\n\ -oneJointPlaneConstraint ValveBiped.Bip01_R_Toe0\n\ -\n\ -twoJointIKSolve ValveBiped.Bip01_R_Hand\n\ -reverseSolve 1\n\ -extremityScale 1.0\n\ -limbRootOffsetScale 0.0 0.0 1.0\n\ -\n\ -twoJointIKSolve ValveBiped.Bip01_L_Hand\n\ -reverseSolve 1\n\ -extremityScale 1.0\n\ -limbRootOffsetScale 0.0 0.0 1.0\n\ -\n\ -"; - - -void UsageAndExit() -{ - MdlError( "usage: motionmapper [-quiet] [-verbose] [-templateFile filename] [-printTemplates] sourceanim.smd targetskeleton.smd output.smd\n\ -\tsourceanim: should contain ref pose and animation data\n\ -\ttargetsekeleton: should contain new ref pose, animation data ignored/can be absent\n\ -\toutput: animation from source mapped onto target skeleton (contains new ref pose)\n\ -\t-templateFile filename : specifies a template file for guiding the mapping of motion\n\ -\t-printTemplate: Causes motionmapper to output the contents of an example template file, which can be used in conjunction with the -templateFile argument to create various motion effects.\n\ -\n"); -} - -void PrintHeader() -{ - vprint( 0, "Valve Software - motionmapper.exe ((c) Valve Coroporation %s)\n", __DATE__ ); - vprint( 0, "--- Maps motion from one animation/skeleton onto another skeleton ---\n" ); -} - - - -/* -============== -main -============== -*/ -int main (int argc, char **argv) -{ - int i; - - int useTemplate = 0; - char templateFileName[1024]; - - // Header - PrintHeader(); - - // Init command line stuff - CommandLine()->CreateCmdLine( argc, argv ); - InstallSpewFunction(); - - // init math stuff - MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false ); - g_currentscale = g_defaultscale = 1.0; - g_defaultrotation = RadianEuler( 0, 0, M_PI / 2 ); - - // No args? - if (argc == 1) - { - UsageAndExit(); - } - - // Init variable - g_quiet = false; - - // list template hooey - CUtlVector< CUtlSymbol > filenames; - - // Get args - for (i = 1; i < argc; i++) - { - // Switches - if (argv[i][0] == '-') - { - if (!stricmp(argv[i], "-allowdebug")) - { - // Ignore, used by interface system to catch debug builds checked into release tree - continue; - } - - if (!stricmp(argv[i], "-quiet")) - { - g_quiet = true; - g_verbose = false; - continue; - } - - if (!stricmp(argv[i], "-verbose")) - { - g_quiet = false; - g_verbose = true; - continue; - } - if (!stricmp(argv[i], "-printTemplate")) - { - printf("%s\n", templates); - exit(0); - - } - if (!stricmp(argv[i], "-templateFile")) - { - if(i + 1 < argc) - { - strcpy( templateFileName, argv[i+1]); - useTemplate = 1; - printf("Note: %s passed as template file", templateFileName); - } - else - { - printf("Error: -templateFile requires an argument, none found!"); - UsageAndExit(); - - } - i++; - continue; - } - } - else - { - // more template stuff - CUtlSymbol sym = argv[ i ]; - filenames.AddToTail( sym ); - } - } - - // Enough file args? - if ( filenames.Count() != 3 ) - { - // misformed arguments - // otherwise generating unintended results - printf("Error: 3 file arguments required, %i found!", filenames.Count()); - UsageAndExit(); - } - - // Filename arg indexes - int sourceanim = 0; - int targetskel = 1; - int outputanim = 2; - - // Copy arg string to global variable - strcpy( g_outfile, filenames[ outputanim ].String() ); - - // Init filesystem hooey - CmdLib_InitFileSystem( g_outfile ); - // ?? - Q_FileBase( g_outfile, g_outfile, sizeof( g_outfile ) ); - - // Verbose stuff - if (!g_quiet) - { - vprint( 0, "%s, %s, %s, path %s\n", qdir, gamedir, g_outfile ); - } - // ?? - Q_DefaultExtension(g_outfile, ".smd", sizeof( g_outfile ) ); - - // Verbose stuff - if (!g_quiet) - { - vprint( 0, "Source animation: %s\n", filenames[ sourceanim ].String() ); - vprint( 0, "Target skeleton: %s\n", filenames[ targetskel ].String() ); - - vprint( 0, "Creating on \"%s\"\n", g_outfile); - } - // fullpath = EXTERNAL GLOBAL!!!??? - strcpy( fullpath, g_outfile ); - strcpy( fullpath, ExpandPath( fullpath ) ); - strcpy( fullpath, ExpandArg( fullpath ) ); - - // Load source and target data - s_source_t *pSource = Load_Source( filenames[sourceanim].String(), "smd", false, false ); - s_source_t *pTarget = Load_Source( filenames[targetskel].String(), "smd", false, false ); - - - // - s_template_t *pTemplate = NULL; - if(useTemplate) - { - pTemplate = Load_Template(templateFileName); - } - else - { - printf("Note: No template file specified, using defaults settings.\n"); - - pTemplate = New_Template(); - Set_DefaultTemplate(pTemplate); - } - - - // Process skeleton - s_source_t *pMappedAnimation = MotionMap( pSource, pTarget, pTemplate ); - - - // Save output (ref skeleton & animation data); - Save_SMD( fullpath, pMappedAnimation ); - - Q_StripExtension( filenames[outputanim].String(), outname, sizeof( outname ) ); - - // Verbose stuff - if (!g_quiet) - { - vprint( 0, "\nCompleted \"%s\"\n", g_outfile); - } - - return 0; -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +#include +#include +#include +#include +#include "filesystem_tools.h" +#include "cmdlib.h" +#include "scriplib.h" +#include "mathlib/mathlib.h" +#define EXTERN +#include "studio.h" +#include "motionmapper.h" +#include "tier1/strtools.h" +#include "tier0/icommandline.h" +#include "utldict.h" +#include +#include "UtlBuffer.h" +#include "utlsymbol.h" + +bool g_quiet = false; +bool g_verbose = false; +char g_outfile[1024]; +bool uselogfile = false; + +char g_szFilename[1024]; +FILE *g_fpInput; +char g_szLine[4096]; +int g_iLinecount; + +bool g_bZBrush = false; +bool g_bGaveMissingBoneWarning = false; + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : depth - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void vprint( int depth, const char *fmt, ... ) +{ + char string[ 8192 ]; + va_list va; + va_start( va, fmt ); + V_vsprintf_safe( string, fmt, va ); + va_end( va ); + + FILE *fp = NULL; + + if ( uselogfile ) + { + fp = fopen( "log.txt", "ab" ); + } + + while ( depth-- > 0 ) + { + vprint( 0, " " ); + OutputDebugString( " " ); + if ( fp ) + { + fprintf( fp, " " ); + } + } + + ::printf( "%s", string ); + OutputDebugString( string ); + + if ( fp ) + { + char *p = string; + while ( *p ) + { + if ( *p == '\n' ) + { + fputc( '\r', fp ); + } + fputc( *p, fp ); + p++; + } + fclose( fp ); + } +} + + +int k_memtotal; +void *kalloc( int num, int size ) +{ + // vprint( 0, "calloc( %d, %d )\n", num, size ); + // vprint( 0, "%d ", num * size ); + k_memtotal += num * size; + return calloc( num, size ); +} + +void kmemset( void *ptr, int value, int size ) +{ + // vprint( 0, "kmemset( %x, %d, %d )\n", ptr, value, size ); + memset( ptr, value, size ); + return; +} + +static bool g_bFirstWarning = true; + +void MdlWarning( const char *fmt, ... ) +{ + va_list args; + static char output[1024]; + + if (g_quiet) + { + if (g_bFirstWarning) + { + vprint( 0, "%s :\n", fullpath ); + g_bFirstWarning = false; + } + vprint( 0, "\t"); + } + + vprint( 0, "WARNING: "); + va_start( args, fmt ); + vprint( 0, fmt, args ); +} + + +void MdlError( char const *fmt, ... ) +{ + va_list args; + + if (g_quiet) + { + if (g_bFirstWarning) + { + vprint( 0, "%s :\n", fullpath ); + g_bFirstWarning = false; + } + vprint( 0, "\t"); + } + + vprint( 0, "ERROR: "); + va_start( args, fmt ); + vprint( 0, fmt, args ); + + exit( -1 ); +} + +int OpenGlobalFile( char *src ) +{ + int time1; + char filename[1024]; + + // local copy of string + strcpy( filename, ExpandPath( src ) ); + + // Ummm, path sanity checking + int pathLength; + int numBasePaths = CmdLib_GetNumBasePaths(); + // This is kinda gross. . . doing the same work in cmdlib on SafeOpenRead. + if( CmdLib_HasBasePath( filename, pathLength ) ) + { + char tmp[1024]; + int i; + for( i = 0; i < numBasePaths; i++ ) + { + strcpy( tmp, CmdLib_GetBasePath( i ) ); + strcat( tmp, filename + pathLength ); + + time1 = FileTime( tmp ); + if( time1 != -1 ) + { + if ((g_fpInput = fopen(tmp, "r")) == 0) + { + MdlWarning( "reader: could not open file '%s'\n", src ); + return 0; + } + else + { + return 1; + } + } + } + return 0; + } + else + { + time1 = FileTime (filename); + if (time1 == -1) + return 0; + + // Whoohooo, FOPEN! + if ((g_fpInput = fopen(filename, "r")) == 0) + { + MdlWarning( "reader: could not open file '%s'\n", src ); + return 0; + } + + return 1; + } +} + +bool IsEnd( char const* pLine ) +{ + if (strncmp( "end", pLine, 3 ) != 0) + return false; + return (pLine[3] == '\0') || (pLine[3] == '\n'); +} + + +//Wrong name for the use of it. +void scale_vertex( Vector &org ) +{ + org[0] = org[0] * g_currentscale; + org[1] = org[1] * g_currentscale; + org[2] = org[2] * g_currentscale; +} + + +void clip_rotations( RadianEuler& rot ) +{ + int j; + // clip everything to : -M_PI <= x < M_PI + + for (j = 0; j < 3; j++) { + while (rot[j] >= M_PI) + rot[j] -= M_PI*2; + while (rot[j] < -M_PI) + rot[j] += M_PI*2; + } +} + + +void clip_rotations( Vector& rot ) +{ + int j; + // clip everything to : -180 <= x < 180 + + for (j = 0; j < 3; j++) { + while (rot[j] >= 180) + rot[j] -= 180*2; + while (rot[j] < -180) + rot[j] += 180*2; + } +} + + +void Build_Reference( s_source_t *psource) +{ + int i, parent; + Vector angle; + + for (i = 0; i < psource->numbones; i++) + { + matrix3x4_t m; + AngleMatrix( psource->rawanim[0][i].rot, m ); + m[0][3] = psource->rawanim[0][i].pos[0]; + m[1][3] = psource->rawanim[0][i].pos[1]; + m[2][3] = psource->rawanim[0][i].pos[2]; + + parent = psource->localBone[i].parent; + if (parent == -1) + { + // scale the done pos. + // calc rotational matrices + MatrixCopy( m, psource->boneToPose[i] ); + } + else + { + // calc compound rotational matrices + // FIXME : Hey, it's orthogical so inv(A) == transpose(A) + ConcatTransforms( psource->boneToPose[parent], m, psource->boneToPose[i] ); + } + // vprint( 0, "%3d %f %f %f\n", i, psource->bonefixup[i].worldorg[0], psource->bonefixup[i].worldorg[1], psource->bonefixup[i].worldorg[2] ); + /* + AngleMatrix( angle, m ); + vprint( 0, "%8.4f %8.4f %8.4f\n", m[0][0], m[1][0], m[2][0] ); + vprint( 0, "%8.4f %8.4f %8.4f\n", m[0][1], m[1][1], m[2][1] ); + vprint( 0, "%8.4f %8.4f %8.4f\n", m[0][2], m[1][2], m[2][2] ); + */ + } +} + +int Grab_Nodes( s_node_t *pnodes ) +{ + // + // s_node_t structure: index is index!! + // + int index; + char name[1024]; + int parent; + int numbones = 0; + + // Init parent to none + for (index = 0; index < MAXSTUDIOSRCBONES; index++) + { + pnodes[index].parent = -1; + } + + // March through nodes lines + while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) + { + g_iLinecount++; + // get tokens + if (sscanf( g_szLine, "%d \"%[^\"]\" %d", &index, name, &parent ) == 3) + { + // check for duplicated bones + /* + if (strlen(pnodes[index].name) != 0) + { + MdlError( "bone \"%s\" exists more than once\n", name ); + } + */ + // copy name to struct array + V_strcpy_safe( pnodes[index].name, name ); + // set parent into struct array + pnodes[index].parent = parent; + // increment numbones + if (index > numbones) + { + numbones = index; + } + } + else + { + return numbones + 1; + } + } + MdlError( "Unexpected EOF at line %d\n", g_iLinecount ); + return 0; +} + +void Grab_Vertexanimation( s_source_t *psource ) +{ + char cmd[1024]; + int index; + Vector pos; + Vector normal; + int t = -1; + int count = 0; + static s_vertanim_t tmpvanim[MAXSTUDIOVERTS*4]; + + while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) + { + g_iLinecount++; + if (sscanf( g_szLine, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &normal[0], &normal[1], &normal[2] ) == 7) + { + if (psource->startframe < 0) + { + MdlError( "Missing frame start(%d) : %s", g_iLinecount, g_szLine ); + } + + if (t < 0) + { + MdlError( "VTA Frame Sync (%d) : %s", g_iLinecount, g_szLine ); + } + + tmpvanim[count].vertex = index; + VectorCopy( pos, tmpvanim[count].pos ); + VectorCopy( normal, tmpvanim[count].normal ); + count++; + + if (index >= psource->numvertices) + psource->numvertices = index + 1; + } + else + { + // flush data + + if (count) + { + psource->numvanims[t] = count; + + psource->vanim[t] = (s_vertanim_t *)kalloc( count, sizeof( s_vertanim_t ) ); + + memcpy( psource->vanim[t], tmpvanim, count * sizeof( s_vertanim_t ) ); + } + else if (t > 0) + { + psource->numvanims[t] = 0; + } + + // next command + if (sscanf( g_szLine, "%1023s %d", cmd, &index )) + { + if (strcmp( cmd, "time" ) == 0) + { + t = index; + count = 0; + + if (t < psource->startframe) + { + MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine ); + } + if (t > psource->endframe) + { + MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine ); + } + + t -= psource->startframe; + } + else if (strcmp( cmd, "end") == 0) + { + psource->numframes = psource->endframe - psource->startframe + 1; + return; + } + else + { + MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine ); + } + + } + else + { + MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine ); + } + } + } + MdlError( "unexpected EOF: %s\n", psource->filename ); +} + +void Grab_Animation( s_source_t *psource ) +{ + Vector pos; + RadianEuler rot; + char cmd[1024]; + int index; + int t = -99999999; + int size; + + // Init startframe + psource->startframe = -1; + + // size per frame + size = psource->numbones * sizeof( s_bone_t ); + + // march through animation + while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) + { + // linecount + g_iLinecount++; + // split if big enoough + if (sscanf( g_szLine, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &rot[0], &rot[1], &rot[2] ) == 7) + { + // startframe is sanity check for having determined time + if (psource->startframe < 0) + { + MdlError( "Missing frame start(%d) : %s", g_iLinecount, g_szLine ); + } + + // scale if pertinent + scale_vertex( pos ); + VectorCopy( pos, psource->rawanim[t][index].pos ); + VectorCopy( rot, psource->rawanim[t][index].rot ); + + clip_rotations( rot ); // !!! + } + else if (sscanf( g_szLine, "%1023s %d", cmd, &index )) + { + // get time + if (strcmp( cmd, "time" ) == 0) + { + // again time IS an index + t = index; + if (psource->startframe == -1) + { + psource->startframe = t; + } + // sanity check time (little funny logic here, see previous IF) + if (t < psource->startframe) + { + MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine ); + } + // bump up endframe? + if (t > psource->endframe) + { + psource->endframe = t; + } + // make t into pure index + t -= psource->startframe; + + // check for memory allocation + if (psource->rawanim[t] == NULL) + { + // Allocate 1 frame of full bonecount + psource->rawanim[t] = (s_bone_t *)kalloc( 1, size ); + + // duplicate previous frames keys?? preventative sanity? + if (t > 0 && psource->rawanim[t-1]) + { + for (int j = 0; j < psource->numbones; j++) + { + VectorCopy( psource->rawanim[t-1][j].pos, psource->rawanim[t][j].pos ); + VectorCopy( psource->rawanim[t-1][j].rot, psource->rawanim[t][j].rot ); + } + } + } + else + { + // MdlError( "%s has duplicated frame %d\n", psource->filename, t ); + } + } + else if (strcmp( cmd, "end") == 0) + { + psource->numframes = psource->endframe - psource->startframe + 1; + + for (t = 0; t < psource->numframes; t++) + { + if (psource->rawanim[t] == NULL) + { + MdlError( "%s is missing frame %d\n", psource->filename, t + psource->startframe ); + } + } + + Build_Reference( psource ); + return; + } + else + { + MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine ); + } + } + else + { + MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine ); + } + } + + MdlError( "unexpected EOF: %s\n", psource->filename ); +} + +int lookup_index( s_source_t *psource, int material, Vector& vertex, Vector& normal, Vector2D texcoord ) +{ + int i; + + for (i = 0; i < numvlist; i++) + { + if (v_listdata[i].m == material + && DotProduct( g_normal[i], normal ) > normal_blend + && VectorCompare( g_vertex[i], vertex ) + && g_texcoord[i][0] == texcoord[0] + && g_texcoord[i][1] == texcoord[1]) + { + v_listdata[i].lastref = numvlist; + return i; + } + } + if (i >= MAXSTUDIOVERTS) { + MdlError( "too many indices in source: \"%s\"\n", psource->filename); + } + + VectorCopy( vertex, g_vertex[i] ); + VectorCopy( normal, g_normal[i] ); + Vector2Copy( texcoord, g_texcoord[i] ); + + v_listdata[i].v = i; + v_listdata[i].m = material; + v_listdata[i].n = i; + v_listdata[i].t = i; + + v_listdata[i].firstref = numvlist; + v_listdata[i].lastref = numvlist; + + numvlist = i + 1; + return i; +} + + +void ParseFaceData( s_source_t *psource, int material, s_face_t *pFace ) +{ + int index[3]; + int i, j; + Vector p; + Vector normal; + Vector2D t; + int iCount, bones[MAXSTUDIOSRCBONES]; + float weights[MAXSTUDIOSRCBONES]; + int bone; + + for (j = 0; j < 3; j++) + { + memset( g_szLine, 0, sizeof( g_szLine ) ); + + if (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) == NULL) + { + MdlError("%s: error on g_szLine %d: %s", g_szFilename, g_iLinecount, g_szLine ); + } + + iCount = 0; + + g_iLinecount++; + i = sscanf( g_szLine, "%d %f %f %f %f %f %f %f %f %d %d %f %d %f %d %f %d %f", + &bone, + &p[0], &p[1], &p[2], + &normal[0], &normal[1], &normal[2], + &t[0], &t[1], + &iCount, + &bones[0], &weights[0], &bones[1], &weights[1], &bones[2], &weights[2], &bones[3], &weights[3] ); + + if (i < 9) + continue; + + if (bone < 0 || bone >= psource->numbones) + { + MdlError("bogus bone index\n%d %s :\n%s", g_iLinecount, g_szFilename, g_szLine ); + } + + //Scale face pos + scale_vertex( p ); + + // continue parsing more bones. + // FIXME: don't we have a built in parser that'll do this? + if (iCount > 4) + { + int k; + int ctr = 0; + char *token; + for (k = 0; k < 18; k++) + { + while (g_szLine[ctr] == ' ') + { + ctr++; + } + token = strtok( &g_szLine[ctr], " " ); + ctr += strlen( token ) + 1; + } + for (k = 4; k < iCount && k < MAXSTUDIOSRCBONES; k++) + { + while (g_szLine[ctr] == ' ') + { + ctr++; + } + token = strtok( &g_szLine[ctr], " " ); + ctr += strlen( token ) + 1; + + bones[k] = atoi(token); + + token = strtok( &g_szLine[ctr], " " ); + ctr += strlen( token ) + 1; + + weights[k] = atof(token); + } + // vprint( 0, "%d ", iCount ); + + //vprint( 0, "\n"); + //exit(1); + } + + // adjust_vertex( p ); + // scale_vertex( p ); + + // move vertex position to object space. + // VectorSubtract( p, psource->bonefixup[bone].worldorg, tmp ); + // VectorTransform(tmp, psource->bonefixup[bone].im, p ); + + // move normal to object space. + // VectorCopy( normal, tmp ); + // VectorTransform(tmp, psource->bonefixup[bone].im, normal ); + // VectorNormalize( normal ); + + // invert v + t[1] = 1.0 - t[1]; + + index[j] = lookup_index( psource, material, p, normal, t ); + + if (i == 9 || iCount == 0) + { + g_bone[index[j]].numbones = 1; + g_bone[index[j]].bone[0] = bone; + g_bone[index[j]].weight[0] = 1.0; + } + else + { + iCount = SortAndBalanceBones( iCount, MAXSTUDIOBONEWEIGHTS, bones, weights ); + + g_bone[index[j]].numbones = iCount; + for (i = 0; i < iCount; i++) + { + g_bone[index[j]].bone[i] = bones[i]; + g_bone[index[j]].weight[i] = weights[i]; + } + } + } + + // pFace->material = material; // BUG + pFace->a = index[0]; + pFace->b = index[1]; + pFace->c = index[2]; + Assert( ((pFace->a & 0xF0000000) == 0) && ((pFace->b & 0xF0000000) == 0) && + ((pFace->c & 0xF0000000) == 0) ); + + if (flip_triangles) + { + j = pFace->b; pFace->b = pFace->c; pFace->c = j; + } +} + +int use_texture_as_material( int textureindex ) +{ + if (g_texture[textureindex].material == -1) + { + // vprint( 0, "%d %d %s\n", textureindex, g_nummaterials, g_texture[textureindex].name ); + g_material[g_nummaterials] = textureindex; + g_texture[textureindex].material = g_nummaterials++; + } + + return g_texture[textureindex].material; +} + +int material_to_texture( int material ) +{ + int i; + for (i = 0; i < g_numtextures; i++) + { + if (g_texture[i].material == material) + { + return i; + } + } + return -1; +} + +int lookup_texture( char *texturename, int maxlen ) +{ + int i; + + Q_StripExtension( texturename, texturename, maxlen ); + + for (i = 0; i < g_numtextures; i++) + { + if (stricmp( g_texture[i].name, texturename ) == 0) + { + return i; + } + } + + if (i >= MAXSTUDIOSKINS) + MdlError("Too many materials used, max %d\n", ( int )MAXSTUDIOSKINS ); + +// vprint( 0, "texture %d = %s\n", i, texturename ); + V_strcpy_safe( g_texture[i].name, texturename ); + + g_texture[i].material = -1; + /* + if (stristr( texturename, "chrome" ) != NULL) { + texture[i].flags = STUDIO_NF_FLATSHADE | STUDIO_NF_CHROME; + } + else { + texture[i].flags = 0; + } + */ + g_numtextures++; + return i; +} + +int SortAndBalanceBones( int iCount, int iMaxCount, int bones[], float weights[] ) +{ + int i; + + // collapse duplicate bone weights + for (i = 0; i < iCount-1; i++) + { + int j; + for (j = i + 1; j < iCount; j++) + { + if (bones[i] == bones[j]) + { + weights[i] += weights[j]; + weights[j] = 0.0; + } + } + } + + // do sleazy bubble sort + int bShouldSort; + do { + bShouldSort = false; + for (i = 0; i < iCount-1; i++) + { + if (weights[i+1] > weights[i]) + { + int j = bones[i+1]; bones[i+1] = bones[i]; bones[i] = j; + float w = weights[i+1]; weights[i+1] = weights[i]; weights[i] = w; + bShouldSort = true; + } + } + } while (bShouldSort); + + // throw away all weights less than 1/20th + while (iCount > 1 && weights[iCount-1] < 0.05) + { + iCount--; + } + + // clip to the top iMaxCount bones + if (iCount > iMaxCount) + { + iCount = iMaxCount; + } + + float t = 0; + for (i = 0; i < iCount; i++) + { + t += weights[i]; + } + + if (t <= 0.0) + { + // missing weights?, go ahead and evenly share? + // FIXME: shouldn't this error out? + t = 1.0 / iCount; + + for (i = 0; i < iCount; i++) + { + weights[i] = t; + } + } + else + { + // scale to sum to 1.0 + t = 1.0 / t; + + for (i = 0; i < iCount; i++) + { + weights[i] = weights[i] * t; + } + } + + return iCount; +} + +int vlistCompare( const void *elem1, const void *elem2 ) +{ + v_unify_t *u1 = &v_listdata[*(int *)elem1]; + v_unify_t *u2 = &v_listdata[*(int *)elem2]; + + // sort by material + if (u1->m < u2->m) + return -1; + if (u1->m > u2->m) + return 1; + + // sort by last used + if (u1->lastref < u2->lastref) + return -1; + if (u1->lastref > u2->lastref) + return 1; + + return 0; +} + +int faceCompare( const void *elem1, const void *elem2 ) +{ + int i1 = *(int *)elem1; + int i2 = *(int *)elem2; + + // sort by material + if (g_face[i1].material < g_face[i2].material) + return -1; + if (g_face[i1].material > g_face[i2].material) + return 1; + + // sort by original usage + if (i1 < i2) + return -1; + if (i1 > i2) + return 1; + + return 0; +} + +#define SMALL_FLOAT 1e-12 + +// NOTE: This routine was taken (and modified) from NVidia's BlinnReflection demo +// Creates basis vectors, based on a vertex and index list. +// See the NVidia white paper 'GDC2K PerPixel Lighting' for a description +// of how this computation works +static void CalcTriangleTangentSpace( s_source_t *pSrc, int v1, int v2, int v3, + Vector &sVect, Vector &tVect ) +{ +/* + static bool firstTime = true; + static FILE *fp = NULL; + if( firstTime ) + { + firstTime = false; + fp = fopen( "crap.out", "w" ); + } +*/ + + /* Compute the partial derivatives of X, Y, and Z with respect to S and T. */ + Vector2D t0( pSrc->texcoord[v1][0], pSrc->texcoord[v1][1] ); + Vector2D t1( pSrc->texcoord[v2][0], pSrc->texcoord[v2][1] ); + Vector2D t2( pSrc->texcoord[v3][0], pSrc->texcoord[v3][1] ); + Vector p0( pSrc->vertex[v1][0], pSrc->vertex[v1][1], pSrc->vertex[v1][2] ); + Vector p1( pSrc->vertex[v2][0], pSrc->vertex[v2][1], pSrc->vertex[v2][2] ); + Vector p2( pSrc->vertex[v3][0], pSrc->vertex[v3][1], pSrc->vertex[v3][2] ); + + sVect.Init( 0.0f, 0.0f, 0.0f ); + tVect.Init( 0.0f, 0.0f, 0.0f ); + + // x, s, t + Vector edge01 = Vector( p1.x - p0.x, t1.x - t0.x, t1.y - t0.y ); + Vector edge02 = Vector( p2.x - p0.x, t2.x - t0.x, t2.y - t0.y ); + + Vector cross; + CrossProduct( edge01, edge02, cross ); + if( fabs( cross.x ) > SMALL_FLOAT ) + { + sVect.x += -cross.y / cross.x; + tVect.x += -cross.z / cross.x; + } + + // y, s, t + edge01 = Vector( p1.y - p0.y, t1.x - t0.x, t1.y - t0.y ); + edge02 = Vector( p2.y - p0.y, t2.x - t0.x, t2.y - t0.y ); + + CrossProduct( edge01, edge02, cross ); + if( fabs( cross.x ) > SMALL_FLOAT ) + { + sVect.y += -cross.y / cross.x; + tVect.y += -cross.z / cross.x; + } + + // z, s, t + edge01 = Vector( p1.z - p0.z, t1.x - t0.x, t1.y - t0.y ); + edge02 = Vector( p2.z - p0.z, t2.x - t0.x, t2.y - t0.y ); + + CrossProduct( edge01, edge02, cross ); + if( fabs( cross.x ) > SMALL_FLOAT ) + { + sVect.z += -cross.y / cross.x; + tVect.z += -cross.z / cross.x; + } + + // Normalize sVect and tVect + VectorNormalize( sVect ); + VectorNormalize( tVect ); + +/* + // Calculate flat normal + Vector flatNormal; + edge01 = p1 - p0; + edge02 = p2 - p0; + CrossProduct( edge02, edge01, flatNormal ); + VectorNormalize( flatNormal ); + + // Get the average position + Vector avgPos = ( p0 + p1 + p2 ) / 3.0f; + + // Draw the svect + Vector endS = avgPos + sVect * .2f; + fvprint( 0, fp, "2\n" ); + fvprint( 0, fp, "%f %f %f 1.0 0.0 0.0\n", endS[0], endS[1], endS[2] ); + fvprint( 0, fp, "%f %f %f 1.0 0.0 0.0\n", avgPos[0], avgPos[1], avgPos[2] ); + + // Draw the tvect + Vector endT = avgPos + tVect * .2f; + fvprint( 0, fp, "2\n" ); + fvprint( 0, fp, "%f %f %f 0.0 1.0 0.0\n", endT[0], endT[1], endT[2] ); + fvprint( 0, fp, "%f %f %f 0.0 1.0 0.0\n", avgPos[0], avgPos[1], avgPos[2] ); + + // Draw the normal + Vector endN = avgPos + flatNormal * .2f; + fvprint( 0, fp, "2\n" ); + fvprint( 0, fp, "%f %f %f 0.0 0.0 1.0\n", endN[0], endN[1], endN[2] ); + fvprint( 0, fp, "%f %f %f 0.0 0.0 1.0\n", avgPos[0], avgPos[1], avgPos[2] ); + + // Draw the wireframe of the triangle in white. + fvprint( 0, fp, "2\n" ); + fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p0[0], p0[1], p0[2] ); + fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p1[0], p1[1], p1[2] ); + fvprint( 0, fp, "2\n" ); + fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p1[0], p1[1], p1[2] ); + fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p2[0], p2[1], p2[2] ); + fvprint( 0, fp, "2\n" ); + fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p2[0], p2[1], p2[2] ); + fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p0[0], p0[1], p0[2] ); + + // Draw a slightly shrunken version of the geometry to hide surfaces + Vector tmp0 = p0 - flatNormal * .1f; + Vector tmp1 = p1 - flatNormal * .1f; + Vector tmp2 = p2 - flatNormal * .1f; + fvprint( 0, fp, "3\n" ); + fvprint( 0, fp, "%f %f %f 0.1 0.1 0.1\n", tmp0[0], tmp0[1], tmp0[2] ); + fvprint( 0, fp, "%f %f %f 0.1 0.1 0.1\n", tmp1[0], tmp1[1], tmp1[2] ); + fvprint( 0, fp, "%f %f %f 0.1 0.1 0.1\n", tmp2[0], tmp2[1], tmp2[2] ); + + fflush( fp ); +*/ +} + +typedef CUtlVector CIntVector; + +void CalcModelTangentSpaces( s_source_t *pSrc ) +{ + // Build a map from vertex to a list of triangles that share the vert. + int meshID; + for( meshID = 0; meshID < pSrc->nummeshes; meshID++ ) + { + s_mesh_t *pMesh = &pSrc->mesh[pSrc->meshindex[meshID]]; + CUtlVector vertToTriMap; + vertToTriMap.AddMultipleToTail( pMesh->numvertices ); + int triID; + for( triID = 0; triID < pMesh->numfaces; triID++ ) + { + s_face_t *pFace = &pSrc->face[triID + pMesh->faceoffset]; + vertToTriMap[pFace->a].AddToTail( triID ); + vertToTriMap[pFace->b].AddToTail( triID ); + vertToTriMap[pFace->c].AddToTail( triID ); + } + + // Calculate the tangent space for each triangle. + CUtlVector triSVect; + CUtlVector triTVect; + triSVect.AddMultipleToTail( pMesh->numfaces ); + triTVect.AddMultipleToTail( pMesh->numfaces ); + for( triID = 0; triID < pMesh->numfaces; triID++ ) + { + s_face_t *pFace = &pSrc->face[triID + pMesh->faceoffset]; + CalcTriangleTangentSpace( pSrc, + pMesh->vertexoffset + pFace->a, + pMesh->vertexoffset + pFace->b, + pMesh->vertexoffset + pFace->c, + triSVect[triID], triTVect[triID] ); + } + + // calculate an average tangent space for each vertex. + int vertID; + for( vertID = 0; vertID < pMesh->numvertices; vertID++ ) + { + const Vector &normal = pSrc->normal[vertID+pMesh->vertexoffset]; + Vector4D &finalSVect = pSrc->tangentS[vertID+pMesh->vertexoffset]; + Vector sVect, tVect; + + sVect.Init( 0.0f, 0.0f, 0.0f ); + tVect.Init( 0.0f, 0.0f, 0.0f ); + for( triID = 0; triID < vertToTriMap[vertID].Size(); triID++ ) + { + sVect += triSVect[vertToTriMap[vertID][triID]]; + tVect += triTVect[vertToTriMap[vertID][triID]]; + } + + // In the case of zbrush, everything needs to be treated as smooth. + if( g_bZBrush ) + { + int vertID2; + Vector vertPos1( pSrc->vertex[vertID][0], pSrc->vertex[vertID][1], pSrc->vertex[vertID][2] ); + for( vertID2 = 0; vertID2 < pMesh->numvertices; vertID2++ ) + { + if( vertID2 == vertID ) + { + continue; + } + Vector vertPos2( pSrc->vertex[vertID2][0], pSrc->vertex[vertID2][1], pSrc->vertex[vertID2][2] ); + if( vertPos1 == vertPos2 ) + { + int triID2; + for( triID2 = 0; triID2 < vertToTriMap[vertID2].Size(); triID2++ ) + { + sVect += triSVect[vertToTriMap[vertID2][triID2]]; + tVect += triTVect[vertToTriMap[vertID2][triID2]]; + } + } + } + } + + // make an orthonormal system. + // need to check if we are left or right handed. + Vector tmpVect; + CrossProduct( sVect, tVect, tmpVect ); + bool leftHanded = DotProduct( tmpVect, normal ) < 0.0f; + if( !leftHanded ) + { + CrossProduct( normal, sVect, tVect ); + CrossProduct( tVect, normal, sVect ); + VectorNormalize( sVect ); + VectorNormalize( tVect ); + finalSVect[0] = sVect[0]; + finalSVect[1] = sVect[1]; + finalSVect[2] = sVect[2]; + finalSVect[3] = 1.0f; + } + else + { + CrossProduct( sVect, normal, tVect ); + CrossProduct( normal, tVect, sVect ); + VectorNormalize( sVect ); + VectorNormalize( tVect ); + finalSVect[0] = sVect[0]; + finalSVect[1] = sVect[1]; + finalSVect[2] = sVect[2]; + finalSVect[3] = -1.0f; + } + } + } +} + +void BuildIndividualMeshes( s_source_t *psource ) +{ + int i, j, k; + + // sort new vertices by materials, last used + static int v_listsort[MAXSTUDIOVERTS]; // map desired order to vlist entry + static int v_ilistsort[MAXSTUDIOVERTS]; // map vlist entry to desired order + + for (i = 0; i < numvlist; i++) + { + v_listsort[i] = i; + } + qsort( v_listsort, numvlist, sizeof( int ), vlistCompare ); + for (i = 0; i < numvlist; i++) + { + v_ilistsort[v_listsort[i]] = i; + } + + + // allocate memory + psource->numvertices = numvlist; + psource->localBoneweight = (s_boneweight_t *)kalloc( psource->numvertices, sizeof( s_boneweight_t ) ); + psource->globalBoneweight = NULL; + psource->vertexInfo = (s_vertexinfo_t *)kalloc( psource->numvertices, sizeof( s_vertexinfo_t ) ); + psource->vertex = new Vector[psource->numvertices]; + psource->normal = new Vector[psource->numvertices]; + psource->tangentS = new Vector4D[psource->numvertices]; + psource->texcoord = (Vector2D *)kalloc( psource->numvertices, sizeof( Vector2D ) ); + + // create arrays of unique vertexes, normals, texcoords. + for (i = 0; i < psource->numvertices; i++) + { + j = v_listsort[i]; + + VectorCopy( g_vertex[v_listdata[j].v], psource->vertex[i] ); + VectorCopy( g_normal[v_listdata[j].n], psource->normal[i] ); + Vector2Copy( g_texcoord[v_listdata[j].t], psource->texcoord[i] ); + + psource->localBoneweight[i].numbones = g_bone[v_listdata[j].v].numbones; + int k; + for( k = 0; k < MAXSTUDIOBONEWEIGHTS; k++ ) + { + psource->localBoneweight[i].bone[k] = g_bone[v_listdata[j].v].bone[k]; + psource->localBoneweight[i].weight[k] = g_bone[v_listdata[j].v].weight[k]; + } + + // store a bunch of other info + psource->vertexInfo[i].material = v_listdata[j].m; + + psource->vertexInfo[i].firstref = v_listdata[j].firstref; + psource->vertexInfo[i].lastref = v_listdata[j].lastref; + // vprint( 0, "%4d : %2d : %6.2f %6.2f %6.2f\n", i, psource->boneweight[i].bone[0], psource->vertex[i][0], psource->vertex[i][1], psource->vertex[i][2] ); + } + + // sort faces by materials, last used. + static int facesort[MAXSTUDIOTRIANGLES]; // map desired order to src_face entry + static int ifacesort[MAXSTUDIOTRIANGLES]; // map src_face entry to desired order + + for (i = 0; i < g_numfaces; i++) + { + facesort[i] = i; + } + qsort( facesort, g_numfaces, sizeof( int ), faceCompare ); + for (i = 0; i < g_numfaces; i++) + { + ifacesort[facesort[i]] = i; + } + + psource->numfaces = g_numfaces; + // find first occurance for each material + for (k = 0; k < MAXSTUDIOSKINS; k++) + { + psource->mesh[k].numvertices = 0; + psource->mesh[k].vertexoffset = psource->numvertices; + + psource->mesh[k].numfaces = 0; + psource->mesh[k].faceoffset = g_numfaces; + } + + // find first and count of indices per material + for (i = 0; i < psource->numvertices; i++) + { + k = psource->vertexInfo[i].material; + psource->mesh[k].numvertices++; + if (psource->mesh[k].vertexoffset > i) + psource->mesh[k].vertexoffset = i; + } + + // find first and count of faces per material + for (i = 0; i < psource->numfaces; i++) + { + k = g_face[facesort[i]].material; + + psource->mesh[k].numfaces++; + if (psource->mesh[k].faceoffset > i) + psource->mesh[k].faceoffset = i; + } + + /* + for (k = 0; k < MAXSTUDIOSKINS; k++) + { + vprint( 0, "%d : %d:%d %d:%d\n", k, psource->mesh[k].numvertices, psource->mesh[k].vertexoffset, psource->mesh[k].numfaces, psource->mesh[k].faceoffset ); + } + */ + + // create remapped faces + psource->face = (s_face_t *)kalloc( psource->numfaces, sizeof( s_face_t )); + for (k = 0; k < MAXSTUDIOSKINS; k++) + { + if (psource->mesh[k].numfaces) + { + psource->meshindex[psource->nummeshes] = k; + + for (i = psource->mesh[k].faceoffset; i < psource->mesh[k].numfaces + psource->mesh[k].faceoffset; i++) + { + j = facesort[i]; + + psource->face[i].a = v_ilistsort[g_src_uface[j].a] - psource->mesh[k].vertexoffset; + psource->face[i].b = v_ilistsort[g_src_uface[j].b] - psource->mesh[k].vertexoffset; + psource->face[i].c = v_ilistsort[g_src_uface[j].c] - psource->mesh[k].vertexoffset; + Assert( ((psource->face[i].a & 0xF0000000) == 0) && ((psource->face[i].b & 0xF0000000) == 0) && + ((psource->face[i].c & 0xF0000000) == 0) ); + // vprint( 0, "%3d : %4d %4d %4d\n", i, psource->face[i].a, psource->face[i].b, psource->face[i].c ); + } + + psource->nummeshes++; + } + } + + CalcModelTangentSpaces( psource ); +} + +void Grab_Triangles( s_source_t *psource ) +{ + int i; + Vector vmin, vmax; + + vmin[0] = vmin[1] = vmin[2] = 99999; + vmax[0] = vmax[1] = vmax[2] = -99999; + + g_numfaces = 0; + numvlist = 0; + + // + // load the base triangles + // + int texture; + int material; + char texturename[64]; + + while (1) + { + if (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) == NULL) + break; + + g_iLinecount++; + + // check for end + if (IsEnd( g_szLine )) + break; + + // Look for extra junk that we may want to avoid... + int nLineLength = strlen( g_szLine ); + if (nLineLength >= 64) + { + MdlWarning("Unexpected data at line %d, (need a texture name) ignoring...\n", g_iLinecount ); + continue; + } + + // strip off trailing smag + V_strcpy_safe( texturename, g_szLine ); + for (i = strlen( texturename ) - 1; i >= 0 && ! isgraph( texturename[i] ); i--) + { + } + texturename[i + 1] = '\0'; + + // funky texture overrides + for (i = 0; i < numrep; i++) + { + if (sourcetexture[i][0] == '\0') + { + strcpy( texturename, defaulttexture[i] ); + break; + } + if (stricmp( texturename, sourcetexture[i]) == 0) + { + strcpy( texturename, defaulttexture[i] ); + break; + } + } + + if (texturename[0] == '\0') + { + // weird source problem, skip them + fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); + fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); + fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); + g_iLinecount += 3; + continue; + } + + if (stricmp( texturename, "null.bmp") == 0 || stricmp( texturename, "null.tga") == 0) + { + // skip all faces with the null texture on them. + fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); + fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); + fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); + g_iLinecount += 3; + continue; + } + + texture = lookup_texture( texturename, sizeof( texturename ) ); + psource->texmap[texture] = texture; // hack, make it 1:1 + material = use_texture_as_material( texture ); + + s_face_t f; + ParseFaceData( psource, material, &f ); + + g_src_uface[g_numfaces] = f; + g_face[g_numfaces].material = material; + g_numfaces++; + } + + BuildIndividualMeshes( psource ); +} + +//-------------------------------------------------------------------- +// Load a SMD file +//-------------------------------------------------------------------- +int Load_SMD ( s_source_t *psource ) +{ + char cmd[1024]; + int option; + + // Open file + if (!OpenGlobalFile( psource->filename )) + return 0; + + // verbose + if( !g_quiet ) + { + printf ("SMD MODEL %s\n", psource->filename); + } + + //March through lines + g_iLinecount = 0; + while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) + { + g_iLinecount++; + int numRead = sscanf( g_szLine, "%s %d", cmd, &option ); + + // Blank line + if ((numRead == EOF) || (numRead == 0)) + continue; + + if (strcmp( cmd, "version" ) == 0) + { + if (option != 1) + { + MdlError("bad version\n"); + } + } + // Get hierarchy? + else if (strcmp( cmd, "nodes" ) == 0) + { + psource->numbones = Grab_Nodes( psource->localBone ); + } + // Get animation?? + else if (strcmp( cmd, "skeleton" ) == 0) + { + Grab_Animation( psource ); + } + // Geo? + else if (strcmp( cmd, "triangles" ) == 0) + { + Grab_Triangles( psource ); + } + // Geo animation + else if (strcmp( cmd, "vertexanimation" ) == 0) + { + Grab_Vertexanimation( psource ); + } + else + { + MdlWarning("unknown studio command\n" ); + } + } + fclose( g_fpInput ); + + is_v1support = true; + + return 1; +} + +//----------------------------------------------------------------------------- +// Checks to see if the model source was already loaded +//----------------------------------------------------------------------------- +static s_source_t *FindCachedSource( char const* name, char const* xext ) +{ + int i; + + if( xext[0] ) + { + // we know what extension is necessary. . look for it. + sprintf (g_szFilename, "%s%s.%s", cddir[numdirs], name, xext ); + for (i = 0; i < g_numsources; i++) + { + if (stricmp( g_szFilename, g_source[i]->filename ) == 0) + return g_source[i]; + } + } + else + { + // we don't know what extension to use, so look for all of 'em. + sprintf (g_szFilename, "%s%s.vrm", cddir[numdirs], name ); + for (i = 0; i < g_numsources; i++) + { + if (stricmp( g_szFilename, g_source[i]->filename ) == 0) + return g_source[i]; + } + sprintf (g_szFilename, "%s%s.smd", cddir[numdirs], name ); + for (i = 0; i < g_numsources; i++) + { + if (stricmp( g_szFilename, g_source[i]->filename ) == 0) + return g_source[i]; + } + /* + sprintf (g_szFilename, "%s%s.vta", cddir[numdirs], name ); + for (i = 0; i < g_numsources; i++) + { + if (stricmp( g_szFilename, g_source[i]->filename ) == 0) + return g_source[i]; + } + */ + } + + // Not found + return 0; +} + +static void FlipFacing( s_source_t *pSrc ) +{ + unsigned short tmp; + + int i, j; + for( i = 0; i < pSrc->nummeshes; i++ ) + { + s_mesh_t *pMesh = &pSrc->mesh[i]; + for( j = 0; j < pMesh->numfaces; j++ ) + { + s_face_t &f = pSrc->face[pMesh->faceoffset + j]; + tmp = f.b; f.b = f.c; f.c = tmp; + } + } +} + +//----------------------------------------------------------------------------- +// Loads an animation source +//----------------------------------------------------------------------------- + +s_source_t *Load_Source( char const *name, const char *ext, bool reverse, bool isActiveModel ) +{ + // Sanity check number of source files + if ( g_numsources >= MAXSTUDIOSEQUENCES ) + MdlError( "Load_Source( %s ) - overflowed g_numsources.", name ); + + // Sanity check file and init + Assert(name); + int namelen = strlen(name) + 1; + char* pTempName = (char*)_alloca( namelen ); + char xext[32]; + int result = false; + + // Local copy of filename + strcpy( pTempName, name ); + + // Sanity check file extension? + Q_ExtractFileExtension( pTempName, xext, sizeof( xext ) ); + if (xext[0] == '\0') + { + V_strcpy_safe( xext, ext ); + } + else + { + Q_StripExtension( pTempName, pTempName, namelen ); + } + + // Cached source, ie: already loaded model, legacy + // s_source_t* pSource = FindCachedSource( pTempName, xext ); + // if (pSource) + // { + // if (isActiveModel) + // pSource->isActiveModel = true; + // return pSource; + // } + + // allocate space and whatnot + g_source[g_numsources] = (s_source_t *)kalloc( 1, sizeof( s_source_t ) ); + V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename ); + + // legacy stuff + if (isActiveModel) + { + g_source[g_numsources]->isActiveModel = true; + } + + // more ext sanity check + if ( ( !result && xext[0] == '\0' ) || stricmp( xext, "smd" ) == 0) + { + Q_snprintf( g_szFilename, sizeof(g_szFilename), "%s%s.smd", cddir[numdirs], pTempName ); + V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename ); + + // Import part, load smd file + result = Load_SMD( g_source[g_numsources] ); + } + + /* + if ( ( !result && xext[0] == '\0' ) || stricmp( xext, "dmx" ) == 0) + { + Q_snprintf( g_szFilename, sizeof(g_szFilename), "%s%s.dmx", cddir[numdirs], pTempName ); + V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename ); + + // Import part, load smd file + result = Load_DMX( g_source[g_numsources] ); + } + */ + + // Oops + if ( !result) + { + MdlError( "could not load file '%s'\n", g_source[g_numsources]->filename ); + } + + // bump up number of sources + g_numsources++; + if( reverse ) + { + FlipFacing( g_source[g_numsources-1] ); + } + return g_source[g_numsources-1]; +} + +void SaveNodes( s_source_t *source, CUtlBuffer& buf ) +{ + if ( source->numbones <= 0 ) + return; + + buf.Printf( "nodes\n" ); + + for ( int i = 0; i < source->numbones; ++i ) + { + s_node_t *bone = &source->localBone[ i ]; + + buf.Printf( "%d \"%s\" %d\n", i, bone->name, bone->parent ); + } + + buf.Printf( "end\n" ); +} + +// FIXME: since we don't us a .qc, we could have problems with scaling, etc.??? +void descale_vertex( Vector &org ) +{ + float invscale = 1.0f / g_currentscale; + + org[0] = org[0] * invscale; + org[1] = org[1] * invscale; + org[2] = org[2] * invscale; +} + +void SaveAnimation( s_source_t *source, CUtlBuffer& buf ) +{ + if ( source->numbones <= 0 ) + return; + + buf.Printf( "skeleton\n" ); + + for ( int frame = 0; frame < source->numframes; ++frame ) + { + buf.Printf( "time %i\n", frame + source->startframe ); + + for ( int i = 0; i < source->numbones; ++i ) + { + s_bone_t *prev = NULL; + if ( frame > 0 ) + { + if ( source->rawanim[ frame - 1 ] ) + { + prev = &source->rawanim[ frame - 1 ][ i ]; + } + } + + Vector pos = source->rawanim[ frame ][ i ].pos; + descale_vertex( pos ); + RadianEuler rot = source->rawanim[ frame ][ i ].rot; + +// If this is enabled, then we delta this pos vs the prev frame and don't write out a sample if it's the same value... +#if 0 + if ( prev ) + { + Vector ppos = source->rawanim[ frame -1 ][ i ].pos; + descale_vertex( pos ); + RadianEuler prot = source->rawanim[ frame -1 ][ i ].rot; + + // Only output it if there's a delta + if ( ( ppos != pos ) || + Q_memcmp( &prot, &rot, sizeof( prot ) ) ) + { + buf.Printf + ( "%d %f %f %f %f %f %f\n", + i, // bone index + pos[ 0 ], + pos[ 1 ], + pos[ 2 ], + rot[ 0 ], + rot[ 1 ], + rot[ 2 ] + ); + } + } + else +#endif + { + buf.Printf + ( "%d %f %f %f %f %f %f\n", + i, // bone index + pos[ 0 ], + pos[ 1 ], + pos[ 2 ], + rot[ 0 ], + rot[ 1 ], + rot[ 2 ] + ); + } + } + } + + buf.Printf( "end\n" ); +} + +void Save_SMD( char const *filename, s_source_t *source ) +{ + // Text buffer + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + buf.Printf( "version 1\n" ); + + SaveNodes( source, buf ); + SaveAnimation( source, buf ); + + FileHandle_t fh = g_pFileSystem->Open( filename, "wb" ); + if ( FILESYSTEM_INVALID_HANDLE != fh ) + { + g_pFileSystem->Write( buf.Base(), buf.TellPut(), fh ); + g_pFileSystem->Close( fh ); + } +} + +//-------------------------------------------------------------------- +// mikes right handed row based linear algebra +//-------------------------------------------------------------------- +struct M_matrix4x4_t +{ + M_matrix4x4_t() { + + m_flMatVal[0][0] = 1.0; m_flMatVal[0][1] = 0.0; m_flMatVal[0][2] = 0.0; m_flMatVal[0][3] = 0.0; + m_flMatVal[1][0] = 0.0; m_flMatVal[1][1] = 1.0; m_flMatVal[1][2] = 0.0; m_flMatVal[1][3] = 0.0; + m_flMatVal[2][0] = 0.0; m_flMatVal[2][1] = 0.0; m_flMatVal[2][2] = 1.0; m_flMatVal[2][3] = 0.0; + m_flMatVal[3][0] = 0.0; m_flMatVal[3][1] = 0.0; m_flMatVal[3][2] = 0.0; m_flMatVal[3][3] = 1.0; + + } + // M_matrix3x4_t( + // float m00, float m01, float m02, + // float m10, float m11, float m12, + // float m20, float m21, float m22, + // float m30, float m31, float m32) + // { + // m_flMatVal[0][0] = m00; m_flMatVal[0][1] = m01; m_flMatVal[0][2] = m02; + // m_flMatVal[1][0] = m10; m_flMatVal[1][1] = m11; m_flMatVal[1][2] = m12; + // m_flMatVal[2][0] = m20; m_flMatVal[2][1] = m21; m_flMatVal[2][2] = m22; + // m_flMatVal[3][0] = m30; m_flMatVal[3][1] = m31; m_flMatVal[3][2] = m32; + + // } + + float *operator[]( int i ) { Assert(( i >= 0 ) && ( i < 4 )); return m_flMatVal[i]; } + const float *operator[]( int i ) const { Assert(( i >= 0 ) && ( i < 4 )); return m_flMatVal[i]; } + float *Base() { return &m_flMatVal[0][0]; } + const float *Base() const { return &m_flMatVal[0][0]; } + + float m_flMatVal[4][4]; +}; + +void M_MatrixAngles( const M_matrix4x4_t& matrix, RadianEuler &angles, Vector &position) +{ + float cX, sX, cY, sY, cZ, sZ; + + sY = -matrix[0][2]; + cY = sqrtf(1.0-(sY*sY)); + + if (cY != 0.0) + { + sX = matrix[1][2]; + cX = matrix[2][2]; + sZ = matrix[0][1]; + cZ = matrix[0][0]; + } + else + { + sX = -matrix[2][1]; + cX = matrix[1][1]; + sZ = 0.0; + cZ = 1.0; + } + + angles[0] = atan2f( sX, cX ); + angles[2] = atan2f( sZ, cZ ); + + sX = sinf(angles[0]); + cX = cosf(angles[0]); + + if (sX > cX) + cY = matrix[1][2] / sX; + else + cY = matrix[2][2] / cX; + + angles[1] = atan2f( sY, cY ); + + + position.x = matrix[3][0]; + position.y = matrix[3][1]; + position.z = matrix[3][2]; + +} + +// void M_MatrixAngles( const M_matrix4x4_t& matrix, RadianEuler &angles, Vector &position) +// { + + // float cX, sX, cY, sY, cZ, sZ; + + // sY = matrix[2][0]; + // cY = sqrtf(1.0-(sY*sY)); + + // if (cY != 0.0) + // { + // sX = -matrix[2][1]; + // cX = matrix[2][2]; + // sZ = -matrix[1][0]; + // cZ = matrix[0][0]; + // } + // else + // { + // sX = matrix[0][1]; + // cX = matrix[1][1]; + // sZ = 0.0; + // cZ = 1.0; + // } + + // angles[0] = atan2f( sX, cX ); + // angles[2] = atan2f( sZ, cZ ); + + // sX = sinf(angles[0]); + // cX = cosf(angles[0]); + + // if (sX > cX) + // cY = -matrix[2][1] / sX; + // else + // cY = matrix[2][2] / cX; + + // angles[1] = atan2f( sY, cY ); + + // angles[0] = angles[0]; + // angles[1] = angles[1]; + // angles[2] = angles[2]; + + // position.x = matrix[3][0]; + // position.y = matrix[3][1]; + // position.z = matrix[3][2]; +// } + +void M_MatrixCopy( const M_matrix4x4_t& in, M_matrix4x4_t& out ) +{ + // Assert( s_bMathlibInitialized ); + memcpy( out.Base(), in.Base(), sizeof( float ) * 4 * 4 ); +} +void M_RotateZMatrix(float radian, M_matrix4x4_t &resultMatrix) +{ + + resultMatrix[0][0] = cosf(radian); + resultMatrix[0][1] = sin(radian); + resultMatrix[0][2] = 0.0; + resultMatrix[1][0] =-sin(radian); + resultMatrix[1][1] = cos(radian); + resultMatrix[1][2] = 0.0; + resultMatrix[2][0] = 0.0; + resultMatrix[2][1] = 0.0; + resultMatrix[2][2] = 1.0; +} + +// !!! THIS SHIT DOESN'T WORK!! WHY? HAS I EVER? +void M_AngleAboutAxis(Vector &axis, float radianAngle, M_matrix4x4_t &result) +{ + float c = cosf(radianAngle); + float s = sinf(radianAngle); + float t = 1.0 - c; + // axis.normalize(); + + result[0][0] = t * axis[0] * axis[0] + c; + result[0][1] = t * axis[0] * axis[1] - s * axis[2]; + result[0][2] = t * axis[0] * axis[2] + s * axis[1]; + result[1][0] = t * axis[0] * axis[1] + s * axis[2]; + result[1][1] = t * axis[1] * axis[1] + c; + result[1][2] = t * axis[1] * axis[2] - s * axis[0]; + result[2][0] = t * axis[1] * axis[2] - s; + result[2][1] = t * axis[1] * axis[2] + s * axis[1]; + result[2][2] = t * axis[2] * axis[2] + c * axis[0]; + +} + + +void M_MatrixInvert( const M_matrix4x4_t& in, M_matrix4x4_t& out ) +{ + // Assert( s_bMathlibInitialized ); + if ( &in == &out ) + { + M_matrix4x4_t in2; + M_MatrixCopy( in, in2 ); + M_MatrixInvert( in2, out ); + return; + } + float tmp[3]; + + // I'm guessing this only works on a 3x4 orthonormal matrix + out[0][0] = in[0][0]; + out[1][0] = in[0][1]; + out[2][0] = in[0][2]; + + out[0][1] = in[1][0]; + out[1][1] = in[1][1]; + out[2][1] = in[1][2]; + + out[0][2] = in[2][0]; + out[1][2] = in[2][1]; + out[2][2] = in[2][2]; + + tmp[0] = in[3][0]; + tmp[1] = in[3][1]; + tmp[2] = in[3][2]; + + float v1[3], v2[3], v3[3]; + v1[0] = out[0][0]; + v1[1] = out[1][0]; + v1[2] = out[2][0]; + v2[0] = out[0][1]; + v2[1] = out[1][1]; + v2[2] = out[2][1]; + v3[0] = out[0][2]; + v3[1] = out[1][2]; + v3[2] = out[2][2]; + + out[3][0] = -DotProduct( tmp, v1 ); + out[3][1] = -DotProduct( tmp, v2 ); + out[3][2] = -DotProduct( tmp, v3 ); + + // Trivial case + // if (IS_IDENTITY(matrix)) + // return SbMatrix::identity(); + + // // Affine case... + // // SbMatrix affineAnswer; + // // if ( affine_inverse( SbMatrix(matrix), affineAnswer ) ) + // // return affineAnswer; + + // int index[4]; + // float d, invmat[4][4], temp; + // SbMatrix inverse = *this; + + // if(inverse.LUDecomposition(index, d)) { + + // invmat[0][0] = 1.0; + // invmat[0][1] = 0.0; + // invmat[0][2] = 0.0; + // invmat[0][3] = 0.0; + // inverse.LUBackSubstitution(index, invmat[0]); + // invmat[1][0] = 0.0; + // invmat[1][1] = 1.0; + // invmat[1][2] = 0.0; + // invmat[1][3] = 0.0; + // inverse.LUBackSubstitution(index, invmat[1]); + // invmat[2][0] = 0.0; + // invmat[2][1] = 0.0; + // invmat[2][2] = 1.0; + // invmat[2][3] = 0.0; + // inverse.LUBackSubstitution(index, invmat[2]); + // invmat[3][0] = 0.0; + // invmat[3][1] = 0.0; + // invmat[3][2] = 0.0; + // invmat[3][3] = 1.0; + // inverse.LUBackSubstitution(index, invmat[3]); + +// #define SWAP(i,j) \ + // temp = invmat[i][j]; \ + // invmat[i][j] = invmat[j][i]; \ + // invmat[j][i] = temp; + + // SWAP(1,0); + + // SWAP(2,0); + // SWAP(2,1); + + // SWAP(3,0); + // SWAP(3,1); + // SWAP(3,2); +// #undef SWAP + // } +} + +/* +================ +M_ConcatTransforms +================ +*/ +void M_ConcatTransforms (const M_matrix4x4_t &in1, const M_matrix4x4_t &in2, M_matrix4x4_t &out) +{ + + // Assert( s_bMathlibInitialized ); + // if ( &in1 == &out ) + // { + // matrix3x4_t in1b; + // MatrixCopy( in1, in1b ); + // ConcatTransforms( in1b, in2, out ); + // return; + // } + // if ( &in2 == &out ) + // { + // matrix3x4_t in2b; + // MatrixCopy( in2, in2b ); + // ConcatTransforms( in1, in2b, out ); + // return; + // } + +#define MULT(i,j) (in1[i][0]*in2[0][j] + \ + in1[i][1]*in2[1][j] + \ + in1[i][2]*in2[2][j] + \ + in1[i][3]*in2[3][j]) + + out[0][0] = MULT(0,0); + out[0][1] = MULT(0,1); + out[0][2] = MULT(0,2); + out[0][3] = MULT(0,3); + out[1][0] = MULT(1,0); + out[1][1] = MULT(1,1); + out[1][2] = MULT(1,2); + out[1][3] = MULT(1,3); + out[2][0] = MULT(2,0); + out[2][1] = MULT(2,1); + out[2][2] = MULT(2,2); + out[2][3] = MULT(2,3); + out[3][0] = MULT(3,0); + out[3][1] = MULT(3,1); + out[3][2] = MULT(3,2); + out[3][3] = MULT(3,3); + +#undef MULT + +} + +void M_AngleMatrix( RadianEuler const &angles, const Vector &position, M_matrix4x4_t& matrix ) +{ + // Assert( s_bMathlibInitialized ); + float sx, sy, sz, cx, cy, cz; + + + sx = sinf(angles[0]); + cx = cosf(angles[0]); + sy = sinf(angles[1]); + cy = cosf(angles[1]); + sz = sinf(angles[2]); + cz = cosf(angles[2]); + + // SinCos( angles[0], &sx, &cx ); // 2 + // SinCos( angles[1], &sy, &cy ); // 1 + // SinCos( angles[2], &sz, &cz ); // 0 + + M_matrix4x4_t mx, my, mz, temp1; + + // rotation about x + mx[1][1] = cx; + mx[1][2] = sx; + mx[2][1] = -sx; + mx[2][2] = cx; + + // rotation about y + my[0][0] = cy; + my[0][2] = -sy; + my[2][0] = sy; + my[2][2] = cy; + + // rotation about z + mz[0][0] = cz; + mz[0][1] = sz; + mz[1][0] = -sz; + mz[1][1] = cz; + + // z * y * x + M_ConcatTransforms(mx, my, temp1); + M_ConcatTransforms(temp1, mz, matrix); + + // put position in + matrix[3][0] = position.x; + matrix[3][1] = position.y; + matrix[3][2] = position.z; + +} + + +//----------------------------------------------------------------------------- +// Motion mapper functions +//----------------------------------------------------------------------------- +#define BONEAXIS 0 +#define BONEDIR 0 +#define BONESIDE 1 +#define BONEUP 2 +#define WORLDUP 2 +#define PRINTMAT(m) \ + printf("\n%f %f %f %f\n", m[0][0], m[0][1], m[0][2], m[0][3]); \ + printf("%f %f %f %f\n", m[1][0], m[1][1], m[1][2], m[1][3]); \ + printf("%f %f %f %f\n", m[2][0], m[2][1], m[2][2], m[2][3]); \ + printf("%f %f %f %f\n", m[3][0], m[3][1], m[3][2], m[3][3]); + +struct s_planeConstraint_t +{ + char jointNameString[1024]; + float floor; + int axis; + +}; + +struct s_iksolve_t +{ + char jointNameString[1024]; + int reverseSolve; + float extremityScale; + Vector limbRootOffsetScale; + int doRelativeLock; + char relativeLockNameString[1024]; + float relativeLockScale; + +}; + +struct s_jointScale_t +{ + char jointNameString[1024]; + float scale; +}; + +struct s_template_t +{ + char rootScaleJoint[1024]; + float rootScaleAmount; + int numIKSolves; + s_iksolve_t *ikSolves[128]; + int numJointScales; + s_jointScale_t *jointScales[128]; + int numPlaneConstraints; + s_planeConstraint_t *planeConstraints[128]; + float toeFloorZ; + int doSkeletonScale; + float skeletonScale; + +}; + + +//----------------------------------------------------------------------------- +// Load a template file into structure +//----------------------------------------------------------------------------- +s_template_t *New_Template() +{ + s_template_t *pTemplate = (s_template_t *)kalloc(1, sizeof(s_template_t)); + pTemplate->rootScaleAmount = 1.0; + pTemplate->numIKSolves = 0; + pTemplate->numJointScales = 0; + pTemplate->toeFloorZ = 2.802277; + pTemplate->numPlaneConstraints = 0; + pTemplate->doSkeletonScale = 0; + pTemplate->skeletonScale = 1.0; + return pTemplate; +} +s_iksolve_t *New_IKSolve() +{ + s_iksolve_t *pIKSolve = (s_iksolve_t *)kalloc(1, sizeof(s_iksolve_t)); + pIKSolve->reverseSolve = 0; + pIKSolve->extremityScale = 1.0; + pIKSolve->limbRootOffsetScale[0] = pIKSolve->limbRootOffsetScale[1] = pIKSolve->limbRootOffsetScale[2] = 0.0; + pIKSolve->doRelativeLock = 0; + pIKSolve->relativeLockScale = 1.0; + return pIKSolve; +} + +s_planeConstraint_t *New_planeConstraint(float floor) +{ + s_planeConstraint_t *pConstraint = (s_planeConstraint_t *)kalloc(1, sizeof(s_planeConstraint_t)); + pConstraint->floor = floor; + pConstraint->axis = 2; + + return pConstraint; +} + +void Set_DefaultTemplate(s_template_t *pTemplate) +{ + pTemplate->numJointScales = 0; + + strcpy(pTemplate->rootScaleJoint, "ValveBiped.Bip01_L_Foot"); + pTemplate->rootScaleAmount = 1.0; + + pTemplate->numIKSolves = 4; + pTemplate->ikSolves[0] = New_IKSolve(); + pTemplate->ikSolves[1] = New_IKSolve(); + pTemplate->ikSolves[2] = New_IKSolve(); + pTemplate->ikSolves[3] = New_IKSolve(); + + + pTemplate->numPlaneConstraints = 2; + pTemplate->planeConstraints[0] = New_planeConstraint(pTemplate->toeFloorZ); + strcpy(pTemplate->planeConstraints[0]->jointNameString, "ValveBiped.Bip01_L_Toe0"); + pTemplate->planeConstraints[1] = New_planeConstraint(pTemplate->toeFloorZ); + strcpy(pTemplate->planeConstraints[1]->jointNameString, "ValveBiped.Bip01_R_Toe0"); + + strcpy(pTemplate->ikSolves[0]->jointNameString, "ValveBiped.Bip01_L_Foot"); + pTemplate->ikSolves[0]->reverseSolve = 0; + pTemplate->ikSolves[0]->extremityScale = 1.0; + pTemplate->ikSolves[0]->limbRootOffsetScale[0] = 1.0; + pTemplate->ikSolves[0]->limbRootOffsetScale[1] = 1.0; + pTemplate->ikSolves[0]->limbRootOffsetScale[2] = 0.0; + + strcpy(pTemplate->ikSolves[1]->jointNameString, "ValveBiped.Bip01_R_Foot"); + pTemplate->ikSolves[1]->reverseSolve = 0; + pTemplate->ikSolves[1]->extremityScale = 1.0; + pTemplate->ikSolves[1]->limbRootOffsetScale[0] = 1.0; + pTemplate->ikSolves[1]->limbRootOffsetScale[1] = 1.0; + pTemplate->ikSolves[1]->limbRootOffsetScale[2] = 0.0; + + strcpy(pTemplate->ikSolves[2]->jointNameString, "ValveBiped.Bip01_R_Hand"); + pTemplate->ikSolves[2]->reverseSolve = 1; + pTemplate->ikSolves[2]->extremityScale = 1.0; + pTemplate->ikSolves[2]->limbRootOffsetScale[0] = 0.0; + pTemplate->ikSolves[2]->limbRootOffsetScale[1] = 0.0; + pTemplate->ikSolves[2]->limbRootOffsetScale[2] = 1.0; + + strcpy(pTemplate->ikSolves[3]->jointNameString, "ValveBiped.Bip01_L_Hand"); + pTemplate->ikSolves[3]->reverseSolve = 1; + pTemplate->ikSolves[3]->extremityScale = 1.0; + pTemplate->ikSolves[3]->limbRootOffsetScale[0] = 0.0; + pTemplate->ikSolves[3]->limbRootOffsetScale[1] = 0.0; + pTemplate->ikSolves[3]->limbRootOffsetScale[2] = 1.0; + // pTemplate->ikSolves[3]->doRelativeLock = 1; + // strcpy(pTemplate->ikSolves[3]->relativeLockNameString, "ValveBiped.Bip01_R_Hand"); + // pTemplate->ikSolves[3]->relativeLockScale = 1.0; + +} + +void split(char *str, char *sep, char **sp) +{ + char *r = strtok(str, sep); + while(r != NULL) + { + *sp = r; + sp++; + r = strtok(NULL, sep); + } + *sp = NULL; +} + + +int checkCommand(char *str, char *cmd, int numOptions, int numSplit) +{ + if(strcmp(str, cmd) == 0) + { + if(numOptions <= numSplit) + return 1; + else + { + printf("Error: Number or argument mismatch in template file cmd %s, requires %i, found %i\n", cmd, numOptions, numSplit); + return 0; + } + } + return 0; +} + +s_template_t *Load_Template(char *name ) +{ + + // Sanity check file and init + Assert(name); + + s_template_t *pTemplate = New_Template(); + + + // Open file + if (!OpenGlobalFile( name )) + return 0; + + + //March through lines + g_iLinecount = 0; + while(fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) + { + g_iLinecount++; + if(g_szLine[0] == '#') + continue; + + char *endP = strrchr(g_szLine, '\n'); + if(endP != NULL) + *endP = '\0'; + + + char *sp[128]; + char **spp = sp; + + char sep[] = " "; + split(g_szLine, sep, sp); + int numSplit = 0; + + while(*spp != NULL) + { + spp++; + numSplit++; + + } + if(numSplit < 1 || + *sp[0] == '\n') + continue; + + + // int numRead = sscanf( g_szLine, "%s %s %s", cmd, &option, &option2 ); + + // // Blank line + // if ((numRead == EOF) || (numRead == 0)) + // continue; + + // commands + char *cmd; + int numOptions = numSplit - 1; + + cmd = sp[0]; + if(checkCommand(cmd, "twoJointIKSolve", 1, numOptions)) + { + printf("\nCreating two joint IK solve %s\n", sp[1]); + pTemplate->ikSolves[pTemplate->numIKSolves] = New_IKSolve(); + strcpy(pTemplate->ikSolves[pTemplate->numIKSolves]->jointNameString, sp[1]); + pTemplate->numIKSolves++; + + } + else if(checkCommand(cmd, "oneJointPlaneConstraint", 1, numOptions)) + { + printf("\nCreating one joint plane constraint %s\n", sp[1]); + pTemplate->planeConstraints[pTemplate->numPlaneConstraints] = New_planeConstraint(pTemplate->toeFloorZ); + strcpy(pTemplate->planeConstraints[pTemplate->numPlaneConstraints]->jointNameString, sp[1]); + pTemplate->numPlaneConstraints++; + + } + else if(checkCommand(cmd, "reverseSolve", 1, numOptions)) + { + printf("reverseSolve: %s\n", sp[1]); + pTemplate->ikSolves[pTemplate->numIKSolves - 1]->reverseSolve = atoi(sp[1]); + } + else if(checkCommand(cmd, "extremityScale", 1, numOptions)) + { + printf("extremityScale: %s\n", sp[1]); + pTemplate->ikSolves[pTemplate->numIKSolves - 1]->extremityScale = atof(sp[1]); + } + else if(checkCommand(cmd, "limbRootOffsetScale", 3, numOptions)) + { + printf("limbRootOffsetScale: %s %s %s\n", sp[1], sp[2], sp[3]); + pTemplate->ikSolves[pTemplate->numIKSolves - 1]->limbRootOffsetScale[0] = atof(sp[1]); + pTemplate->ikSolves[pTemplate->numIKSolves - 1]->limbRootOffsetScale[1] = atof(sp[2]); + pTemplate->ikSolves[pTemplate->numIKSolves - 1]->limbRootOffsetScale[2] = atof(sp[3]); + } + else if(checkCommand(cmd, "toeFloorZ", 1, numOptions)) + { + printf("toeFloorZ: %s\n", sp[1]); + pTemplate->toeFloorZ = atof(sp[1]); + } + else if(checkCommand(cmd, "relativeLock", 2, numOptions)) + { + printf("relativeLock: %s\n", sp[1]); + pTemplate->ikSolves[pTemplate->numIKSolves - 1]->doRelativeLock = 1; + strcpy(pTemplate->ikSolves[pTemplate->numIKSolves - 1]->relativeLockNameString, sp[1]); + pTemplate->ikSolves[pTemplate->numIKSolves - 1]->relativeLockScale = atof(sp[2]); + + } + else if(checkCommand(cmd, "rootScaleJoint", 1, numOptions)) + { + printf("\nrootScaleJoint: %s\n", sp[1]); + strcpy(pTemplate->rootScaleJoint, sp[1]); + } + else if(checkCommand(cmd, "rootScaleAmount", 1, numOptions)) + { + printf("rootScaleAmount: %s\n", sp[1]); + pTemplate->rootScaleAmount = atof(sp[1]); + } + else if(checkCommand(cmd, "jointScale", 2, numOptions)) + { + printf("\nCreating joint scale %s of %s\n", sp[1], sp[2]); + pTemplate->jointScales[pTemplate->numJointScales] = (s_jointScale_t *)kalloc(1, sizeof(s_jointScale_t)); + strcpy(pTemplate->jointScales[pTemplate->numJointScales]->jointNameString, sp[1]); + pTemplate->jointScales[pTemplate->numJointScales]->scale = atof(sp[2]); + pTemplate->numJointScales++; + } + else if(checkCommand(cmd, "skeletonScale", 2, numOptions)) + { + printf("\nCreating skeleton scale of %s\n", sp[1]); + pTemplate->doSkeletonScale = 1; + pTemplate->skeletonScale = atof(sp[1]); + } + else + { + MdlWarning("unknown studio command\n" ); + } + } + fclose( g_fpInput ); + return pTemplate; +} + +//----------------------------------------------------------------------------- +// get node index from node string name +//----------------------------------------------------------------------------- +int GetNodeIndex(s_source_t *psource, char *nodeName) +{ + for(int i = 0; i < psource->numbones; i++) + { + if(strcmp(nodeName, psource->localBone[i].name) == 0) + { + return i; + } + } + return -1; +} + +//----------------------------------------------------------------------------- +// get node index from node string name +//----------------------------------------------------------------------------- +void GetNodePath(s_source_t *psource, int startIndex, int endIndex, int *path) +{ + *path = endIndex; + + s_node_t *nodes; + nodes = psource->localBone; + while(*path != startIndex) + { + int parent = nodes[*path].parent; + path++; + *path = parent; + } + path++; + *path = -1; +} + +void SumBonePathTranslations(int *indexPath, s_bone_t *boneArray, Vector &resultVector, int rootOffset = 0) +{ + + // walk the path + int *pathPtr = indexPath; + // M_matrix4x4_t matrixCum; + + // find length of path + int length = 0; + while(*pathPtr != -1) + { + length++; + pathPtr++; + } + + int l = length - (1 + rootOffset); + + resultVector[0] = 0.0; + resultVector[1] = 0.0; + resultVector[2] = 0.0; + + for(int i = l; i > -1; i--) + { + s_bone_t *thisBone = boneArray + indexPath[i]; + resultVector += thisBone->pos; + } +} + +void CatBonePath(int *indexPath, s_bone_t *boneArray, M_matrix4x4_t &resultMatrix, int rootOffset = 0) +{ + + // walk the path + int *pathPtr = indexPath; + // M_matrix4x4_t matrixCum; + + // find length of path + int length = 0; + while(*pathPtr != -1) + { + length++; + pathPtr++; + } + + int l = length - (1 + rootOffset); + + for(int i = l; i > -1; i--) + { + s_bone_t *thisBone = boneArray + indexPath[i]; + // printf("bone index: %i %i\n", i, indexPath[i]); + // printf("pos: %f %f %f, rot: %f %f %f\n", thisBone->pos.x, thisBone->pos.y, thisBone->pos.z, thisBone->rot.x, thisBone->rot.y, thisBone->rot.z); + M_matrix4x4_t thisMatrix; + M_AngleMatrix(thisBone->rot, thisBone->pos, thisMatrix); + // PRINTMAT(thisMatrix) + M_matrix4x4_t tempCum; + M_MatrixCopy(resultMatrix, tempCum); + M_ConcatTransforms(thisMatrix, tempCum, resultMatrix); + } + // PRINTMAT(matrixCum); + // M_MatrixAngles(matrixCum, resultBone.rot, resultBone.pos); + + // printf("pos: %f %f %f, rot: %f %f %f\n", resultBone.pos.x,resultBone.pos.y, resultBone.pos.z, RAD2DEG(resultBone.rot.x),RAD2DEG(resultBone.rot.y),RAD2DEG(resultBone.rot.z)); + +} +// int ConformSources(s_source_t *pSource, s_source_t *pTarget) +// { + // if(pSource->numbones != *pTarget->numbones) + // { + // printf("ERROR: The number of bones in the target file must match the source file."); + // return 1; + // } + // if(pSource->numframes != pTarget->numframes) + // { + // printf("Note: Source and target frame lengths do not match"); + // for(int t = 0; t < pTarget->numframes; t++) + // { + // free(pTarget->rawanim[t]); + // } + // pTarget->numframes = pSource->numframes; + // int size = pTarget->numbones * sizeof( s_bone_t ); + // for(t = 0; t < pTarget->numframes; t++) + // { + // pTarget->rawanim[t] = (s_bone_t *) kalloc(1, size); + // memcpy((void *) pSource->rawanim[t], (void *) pTarget->rawanim[t], size + // } + // } + // pTarget->startframe = pSource->startframe; + // pTarget->endframe = pSource->endframe; + + + + +void ScaleJointsFrame(s_source_t *pSkeleton, s_jointScale_t *jointScale, int t) +{ + int numBones = pSkeleton->numbones; + + for(int i = 0; i < numBones; i++) + { + s_node_t pNode = pSkeleton->localBone[i]; + s_bone_t *pSkelBone = &pSkeleton->rawanim[t][i]; + if(strcmp(jointScale->jointNameString, pNode.name) == 0) + { + // printf("Scaling joint %s\n", pNode.name); + pSkelBone->pos = pSkelBone->pos * jointScale->scale; + } + + } +} +void ScaleJoints(s_source_t *pSkeleton, s_jointScale_t *jointScale) +{ + int numFrames = pSkeleton->numframes; + for(int t = 0; t < numFrames; t++) + { + ScaleJointsFrame(pSkeleton, jointScale, t); + } +} + +void ScaleSkeletonFrame(s_source_t *pSkeleton, float scale, int t) +{ + int numBones = pSkeleton->numbones; + + for(int i = 0; i < numBones; i++) + { + s_bone_t *pSkelBone = &pSkeleton->rawanim[t][i]; + pSkelBone->pos = pSkelBone->pos * scale; + + } +} +void ScaleSkeleton(s_source_t *pSkeleton, float scale) +{ + int numFrames = pSkeleton->numframes; + for(int t = 0; t < numFrames; t++) + { + ScaleSkeletonFrame(pSkeleton, scale, t); + } +} + +void CombineSkeletonAnimationFrame(s_source_t *pSkeleton, s_source_t *pAnimation, s_bone_t **ppAnim, int t) +{ + int numBones = pAnimation->numbones; + int size = numBones * sizeof( s_bone_t ); + ppAnim[t] = (s_bone_t *) kalloc(1, size); + for(int i = 0; i < numBones; i++) + { + s_node_t pNode = pAnimation->localBone[i]; + s_bone_t pAnimBone = pAnimation->rawanim[t][i]; + + if(pNode.parent > -1) + { + if ( i < pSkeleton->numbones ) + { + s_bone_t pSkelBone = pSkeleton->rawanim[0][i]; + ppAnim[t][i].pos = pSkelBone.pos; + } + else + { + if ( !g_bGaveMissingBoneWarning ) + { + g_bGaveMissingBoneWarning = true; + Warning( "Warning: Target skeleton has less bones than source animation. Reverting to source data for extra bones.\n" ); + } + + ppAnim[t][i].pos = pAnimBone.pos; + } + } + else + { + ppAnim[t][i].pos = pAnimBone.pos; + } + + ppAnim[t][i].rot = pAnimBone.rot; + } +} +void CombineSkeletonAnimation(s_source_t *pSkeleton, s_source_t *pAnimation, s_bone_t **ppAnim) +{ + int numFrames = pAnimation->numframes; + for(int t = 0; t < numFrames; t++) + { + CombineSkeletonAnimationFrame(pSkeleton, pAnimation, ppAnim, t); + } +} + + +//-------------------------------------------------------------------- +// MotionMap +//-------------------------------------------------------------------- +s_source_t *MotionMap( s_source_t *pSource, s_source_t *pTarget, s_template_t *pTemplate ) +{ + + // scale skeleton + if(pTemplate->doSkeletonScale) + { + ScaleSkeleton(pTarget, pTemplate->skeletonScale); + } + + // scale joints + for(int j = 0; j < pTemplate->numJointScales; j++) + { + s_jointScale_t *pJointScale = pTemplate->jointScales[j]; + ScaleJoints(pTarget, pJointScale); + } + + + // root stuff + char rootString[128] = "ValveBiped.Bip01"; + + // !!! PARAMETER + int rootIndex = GetNodeIndex(pSource, rootString); + int rootScaleIndex = GetNodeIndex(pSource, pTemplate->rootScaleJoint); + int rootScalePath[512]; + if(rootScaleIndex > -1) + { + GetNodePath(pSource, rootIndex, rootScaleIndex, rootScalePath); + } + else + { + printf("Error: Can't find node\n"); + exit(0); + } + float rootScaleLengthSrc = pSource->rawanim[0][rootScaleIndex].pos[BONEDIR]; + float rootScaleParentLengthSrc = pSource->rawanim[0][rootScalePath[1]].pos[BONEDIR]; + float rootScaleSrc = rootScaleLengthSrc + rootScaleParentLengthSrc; + float rootScaleLengthTgt = pTarget->rawanim[0][rootScaleIndex].pos[BONEDIR]; + float rootScaleParentLengthTgt = pTarget->rawanim[0][rootScalePath[1]].pos[BONEDIR]; + float rootScaleTgt = rootScaleLengthTgt + rootScaleParentLengthTgt; + float rootScaleFactor = rootScaleTgt / rootScaleSrc; + + if(g_verbose) + printf("Root Scale Factor: %f\n", rootScaleFactor); + + + // root scale origin + float toeFloorZ = pTemplate->toeFloorZ; + Vector rootScaleOrigin = pSource->rawanim[0][rootIndex].pos; + rootScaleOrigin[2] = toeFloorZ; + + + // setup workspace + s_bone_t *combinedRefAnimation[MAXSTUDIOANIMFRAMES]; + s_bone_t *combinedAnimation[MAXSTUDIOANIMFRAMES]; + s_bone_t *sourceAnimation[MAXSTUDIOANIMFRAMES]; + CombineSkeletonAnimation(pTarget, pSource, combinedAnimation); + CombineSkeletonAnimation(pTarget, pSource, combinedRefAnimation); + + + // do source and target sanity checking + int sourceNumFrames = pSource->numframes; + + + // iterate through limb solves + for(int t = 0; t < sourceNumFrames; t++) + { + // setup pTarget for skeleton comparison + pTarget->rawanim[t] = combinedRefAnimation[t]; + + printf("Note: Processing frame: %i\n", t); + for(int ii = 0; ii < pTemplate->numIKSolves; ii++) + { + s_iksolve_t *thisSolve = pTemplate->ikSolves[ii]; + + char *thisJointNameString = thisSolve->jointNameString; + int thisJointIndex = GetNodeIndex(pSource, thisJointNameString); + + // init paths to feet + int thisJointPathInRoot[512]; + + // get paths to feet + if(thisJointIndex > -1) + { + GetNodePath(pSource, rootIndex, thisJointIndex, thisJointPathInRoot); + } + else + { + printf("Error: Can't find node: %s\n" , thisJointNameString); + exit(0); + } + + // leg "root" or thigh pointers + //int gParentIndex = thisJointPathInRoot[2]; + int *gParentPath = thisJointPathInRoot + 2; + + //---------------------------------------------------------------- + // get limb lengths + //---------------------------------------------------------------- + float thisJointLengthSrc = pSource->rawanim[0][thisJointIndex].pos[BONEDIR]; + float parentJointLengthSrc = pSource->rawanim[0][thisJointPathInRoot[1]].pos[BONEDIR]; + + float thisLimbLengthSrc = thisJointLengthSrc + parentJointLengthSrc; + + float thisJointLengthTgt = pTarget->rawanim[0][thisJointIndex].pos[BONEDIR]; + float parentJointLengthTgt = pTarget->rawanim[0][thisJointPathInRoot[1]].pos[BONEDIR]; + + float thisLimbLengthTgt = thisJointLengthTgt + parentJointLengthTgt; + + // Factor leg length delta + float thisLimbLength = thisLimbLengthSrc - thisLimbLengthTgt; + float thisLimbLengthFactor = thisLimbLengthTgt / thisLimbLengthSrc; + + if(g_verbose) + printf("limb length %s: %i: %f, factor %f\n", thisJointNameString, thisJointIndex, thisLimbLength, thisLimbLengthFactor); + + // calculate joint grandparent offset + // Note: because there's no reference pose this doesn't take rotation into account. + // This only works because of the assumption that joint translations aren't animated. + M_matrix4x4_t gParentGlobalMatSrc, gParentGlobalMatTgt; + Vector gParentGlobalSrc, gParentGlobalTgt; + + // SumBonePathTranslations(gParentPath, pSource->rawanim[t], gParentGlobalSrc, 1); + // SumBonePathTranslations(gParentPath, pTarget->rawanim[t], gParentGlobalTgt, 1); + + // get root path to source parent + CatBonePath(gParentPath, pSource->rawanim[t], gParentGlobalMatSrc, 1); + // check against reference animation + CatBonePath(gParentPath, pTarget->rawanim[t], gParentGlobalMatTgt, 1); + + gParentGlobalSrc[0] = gParentGlobalMatSrc[3][0]; + gParentGlobalSrc[1] = gParentGlobalMatSrc[3][1]; + gParentGlobalSrc[2] = gParentGlobalMatSrc[3][2]; + + gParentGlobalTgt[0] = gParentGlobalMatTgt[3][0]; + gParentGlobalTgt[1] = gParentGlobalMatTgt[3][1]; + gParentGlobalTgt[2] = gParentGlobalMatTgt[3][2]; + + + Vector gParentDelta(gParentGlobalTgt - gParentGlobalSrc); + + if(g_verbose) + printf("Grand parent delta: %f %f %f\n", gParentDelta[0], gParentDelta[1], gParentDelta[2]); + + gParentDelta *= thisSolve->limbRootOffsetScale; + + + //---------------------------------------------------------------- + // time takes effect here + // above waste is unavoidable? + //---------------------------------------------------------------- + M_matrix4x4_t rootMat; + M_AngleMatrix(pSource->rawanim[t][rootIndex].rot, pSource->rawanim[t][rootIndex].pos, rootMat); + + + // OK, time to get it together + // 1) scale foot by legLengthFactor in the non-translated thigh space + // 2) translate foot by legRootDelta in the space of the root + // do we leave everything in the space of the root then? PROBABLY!! + + M_matrix4x4_t thisJointMat, parentJointMat, thisJointInGParentMat; + M_AngleMatrix(pSource->rawanim[t][thisJointPathInRoot[0]].rot, pSource->rawanim[t][thisJointPathInRoot[0]].pos, thisJointMat); + M_AngleMatrix(pSource->rawanim[t][thisJointPathInRoot[1]].rot, pSource->rawanim[t][thisJointPathInRoot[1]].pos, parentJointMat); + M_ConcatTransforms(thisJointMat, parentJointMat, thisJointInGParentMat); + + if(!thisSolve->doRelativeLock) + { + // scale around grand parent + float effectiveScaleFactor = ((thisLimbLengthFactor - 1.0) * thisSolve->extremityScale ) + 1.0; + thisJointInGParentMat[3][0] *= effectiveScaleFactor; + thisJointInGParentMat[3][1] *= effectiveScaleFactor; + thisJointInGParentMat[3][2] *= effectiveScaleFactor; + } + + // adjust into source root space + M_matrix4x4_t gParentInRootMat, thisJointInRootMat; + CatBonePath(gParentPath, pSource->rawanim[t], gParentInRootMat, 1); + M_ConcatTransforms(thisJointInGParentMat, gParentInRootMat, thisJointInRootMat); + + if(!thisSolve->doRelativeLock) + { + // adjust by difference of local root + thisJointInRootMat[3][0] += gParentDelta[0]; + thisJointInRootMat[3][1] += gParentDelta[1]; + thisJointInRootMat[3][2] += gParentDelta[2]; + } + else + { + char *relativeJointNameString = thisSolve->relativeLockNameString; + int relativeJointIndex = GetNodeIndex(pSource, relativeJointNameString); + + // init paths to feet + int relativeJointPathInRoot[512]; + + // get paths to feet + if(relativeJointIndex > -1) + { + GetNodePath(pSource, rootIndex, relativeJointIndex, relativeJointPathInRoot); + } + else + { + printf("Error: Can't find node: %s\n" , relativeJointNameString); + exit(0); + } + // get the source relative joint + M_matrix4x4_t relativeJointInRootMatSrc, relativeJointInRootMatSrcInverse, thisJointInRelativeSrcMat; + CatBonePath(relativeJointPathInRoot, pSource->rawanim[t], relativeJointInRootMatSrc, 1); + M_MatrixInvert(relativeJointInRootMatSrc, relativeJointInRootMatSrcInverse); + M_ConcatTransforms(thisJointInRootMat, relativeJointInRootMatSrcInverse, thisJointInRelativeSrcMat); + if(thisSolve->relativeLockScale != 1.0) + { + thisJointInRelativeSrcMat[3][0] *= thisSolve->relativeLockScale; + thisJointInRelativeSrcMat[3][1] *= thisSolve->relativeLockScale; + thisJointInRelativeSrcMat[3][2] *= thisSolve->relativeLockScale; + } + + // swap momentarily to get new destination + // NOTE: the relative lock must have already been solved + sourceAnimation[t] = pSource->rawanim[t]; + pSource->rawanim[t] = combinedAnimation[t]; + + // get new relative location + M_matrix4x4_t relativeJointInRootMatTgt; + CatBonePath(relativeJointPathInRoot, pSource->rawanim[t], relativeJointInRootMatTgt, 1); + M_ConcatTransforms(thisJointInRelativeSrcMat, relativeJointInRootMatTgt, thisJointInRootMat); + + // swap back just for cleanliness + // a little overkill as it's just swapped + // just leaving it here for clarity + combinedAnimation[t] = pSource->rawanim[t]; + pSource->rawanim[t] = sourceAnimation[t]; + + } + + //---------------------------------------------------------------- + // swap animation + //---------------------------------------------------------------- + sourceAnimation[t] = pSource->rawanim[t]; + pSource->rawanim[t] = combinedAnimation[t]; + + //---------------------------------------------------------------- + // make thigh data global based on new skeleton + //---------------------------------------------------------------- + // get thigh in global space + M_matrix4x4_t gParentInTgtRootMat, ggParentInTgtRootMat; + // int *gParentPath = thisJointPathInRoot + 2; + CatBonePath(gParentPath, pSource->rawanim[t], gParentInTgtRootMat, 1); + CatBonePath(gParentPath+1, pSource->rawanim[t], ggParentInTgtRootMat, 1); + + + //---------------------------------------------------------------- + // Calculate IK for legs + //---------------------------------------------------------------- + float parentJointLength = pSource->rawanim[t][*(thisJointPathInRoot + 1)].pos[BONEDIR]; + float thisJointLength = pSource->rawanim[t][thisJointIndex].pos[BONEDIR]; + + Vector thisLimbHypot; + thisLimbHypot[0] = thisJointInRootMat[3][0] - gParentInTgtRootMat[3][0]; + thisLimbHypot[1] = thisJointInRootMat[3][1] - gParentInTgtRootMat[3][1]; + thisLimbHypot[2] = thisJointInRootMat[3][2] - gParentInTgtRootMat[3][2]; + + float thisLimbHypotLength = thisLimbHypot.Length(); + + // law of cosines! + float gParentCos = (thisLimbHypotLength*thisLimbHypotLength + parentJointLength*parentJointLength - thisJointLength*thisJointLength) / (2*parentJointLength*thisLimbHypotLength); + float parentCos = (parentJointLength*parentJointLength + thisJointLength*thisJointLength - thisLimbHypotLength*thisLimbHypotLength) / (2*parentJointLength*thisJointLength); + + VectorNormalize(thisLimbHypot); + + Vector thisLimbHypotUnit = thisLimbHypot; + + M_matrix4x4_t gParentJointIKMat; + Vector gParentJointIKRot, gParentJointIKOrth; + + gParentJointIKRot[0] = gParentInTgtRootMat[BONEUP][0]; + gParentJointIKRot[1] = gParentInTgtRootMat[BONEUP][1]; + gParentJointIKRot[2] = gParentInTgtRootMat[BONEUP][2]; + + VectorNormalize(gParentJointIKRot); + gParentJointIKOrth = gParentJointIKRot.Cross(thisLimbHypotUnit); + VectorNormalize(gParentJointIKOrth); + gParentJointIKRot = thisLimbHypotUnit.Cross(gParentJointIKOrth); + VectorNormalize(gParentJointIKRot); + + M_MatrixCopy(gParentInTgtRootMat, gParentJointIKMat); + + gParentJointIKMat[0][0] = thisLimbHypotUnit[0]; + gParentJointIKMat[0][1] = thisLimbHypotUnit[1]; + gParentJointIKMat[0][2] = thisLimbHypotUnit[2]; + + gParentJointIKMat[1][0] = gParentJointIKOrth[0]; + gParentJointIKMat[1][1] = gParentJointIKOrth[1]; + gParentJointIKMat[1][2] = gParentJointIKOrth[2]; + + gParentJointIKMat[2][0] = gParentJointIKRot[0]; + gParentJointIKMat[2][1] = gParentJointIKRot[1]; + gParentJointIKMat[2][2] = gParentJointIKRot[2]; + + + M_matrix4x4_t gParentJointIKRotMat, gParentJointResultMat; + float gParentDeg; + if(thisSolve->reverseSolve) + { + gParentDeg = acos(gParentCos); + } + else + { + gParentDeg = -acos(gParentCos); + } + + // sanity check limb length + if(thisLimbHypotLength < thisLimbLengthTgt) + { + M_RotateZMatrix(gParentDeg, gParentJointIKRotMat); + } + + M_ConcatTransforms(gParentJointIKRotMat, gParentJointIKMat, gParentJointResultMat); + + M_matrix4x4_t parentJointIKRotMat; + //!!! shouldn't need the 180 degree addition, something in the law of cosines!!! + float parentDeg; + if(thisSolve->reverseSolve) + { + parentDeg = acos(parentCos)+M_PI; + } + else + { + parentDeg = -acos(parentCos)+M_PI; + } + + // sanity check limb length + if(thisLimbHypotLength < thisLimbLengthTgt) + { + M_RotateZMatrix(parentDeg, parentJointIKRotMat); + } + + + // Thighs + M_matrix4x4_t ggParentInTgtRootMatInverse, gParentJointLocalMat; + M_MatrixInvert(ggParentInTgtRootMat, ggParentInTgtRootMatInverse); + M_ConcatTransforms(gParentJointResultMat, ggParentInTgtRootMatInverse, gParentJointLocalMat); + + s_bone_t resultBone; + + // temp test stuff + // M_MatrixAngles(thisJointInRootMat, resultBone.rot, resultBone.pos); + // pSource->rawanim[t][thisJointIndex].rot = resultBone.rot; + // pSource->rawanim[t][thisJointIndex].pos = resultBone.pos; + + // M_MatrixAngles(gParentInTgtRootMat, resultBone.rot, resultBone.pos); + // pSource->rawanim[t][gParentIndex].rot = resultBone.rot; + // pSource->rawanim[t][gParentIndex].pos = resultBone.pos; + + + M_MatrixAngles(gParentJointLocalMat, resultBone.rot, resultBone.pos); + pSource->rawanim[t][*gParentPath].pos = resultBone.pos; + pSource->rawanim[t][*gParentPath].rot = resultBone.rot; + + M_MatrixAngles(parentJointIKRotMat, resultBone.rot, resultBone.pos); + pSource->rawanim[t][*(thisJointPathInRoot+1)].rot = resultBone.rot; + + M_matrix4x4_t parentJointGlobalMat, parentJointGlobalMatInverse, thisJointLocalMat; + CatBonePath(thisJointPathInRoot+1, pSource->rawanim[t], parentJointGlobalMat, 1); + + + M_MatrixInvert(parentJointGlobalMat, parentJointGlobalMatInverse); + M_ConcatTransforms(thisJointInRootMat, parentJointGlobalMatInverse, thisJointLocalMat); + + M_MatrixAngles(thisJointLocalMat, resultBone.rot, resultBone.pos); + pSource->rawanim[t][thisJointIndex].rot = resultBone.rot; + + + // swap animation back for next solve + combinedAnimation[t] = pSource->rawanim[t]; + pSource->rawanim[t] = sourceAnimation[t]; + + } + // swap animation + sourceAnimation[t] = pSource->rawanim[t]; + pSource->rawanim[t] = combinedAnimation[t]; + + //---------------------------------------------------------------- + // adjust root + //---------------------------------------------------------------- + Vector originBonePos = pSource->rawanim[t][rootIndex].pos; + Vector rootInScaleOrigin = originBonePos - rootScaleOrigin; + float effectiveRootScale = ((rootScaleFactor - 1.0) * pTemplate->rootScaleAmount) + 1.0; + Vector scaledRoot = rootInScaleOrigin * effectiveRootScale; + pSource->rawanim[t][rootIndex].pos = rootScaleOrigin + scaledRoot; + + //------------------------------------------------------------ + // plane constraints + //------------------------------------------------------------ + for(int ii = 0; ii < pTemplate->numPlaneConstraints; ii++) + { + s_planeConstraint_t *thisSolve = pTemplate->planeConstraints[ii]; + + char *thisJointNameString = thisSolve->jointNameString; + if(g_verbose) + printf("Executing plane constraint: %s\n", thisJointNameString); + + int thisJointIndex = GetNodeIndex(pSource, thisJointNameString); + + // init paths to feet + int thisJointPath[512]; + + // get paths to feet + if(thisJointIndex > -1) + { + GetNodePath(pSource, -1, thisJointIndex, thisJointPath); + } + else + { + printf("Error: Can't find node: %s\n" , thisJointNameString); + exit(0); + } + int parentIndex = thisJointPath[1]; + int *parentPath = thisJointPath + 1; + + M_matrix4x4_t thisJointGlobalMat, parentJointGlobalMat, gParentJointGlobalMat, gParentJointGlobalMatInverse; + CatBonePath(thisJointPath, pSource->rawanim[t], thisJointGlobalMat, 0); + CatBonePath(parentPath, pSource->rawanim[t], parentJointGlobalMat, 0); + CatBonePath(parentPath+1, pSource->rawanim[t], gParentJointGlobalMat, 0); + M_MatrixInvert(gParentJointGlobalMat, gParentJointGlobalMatInverse); + + if(thisJointGlobalMat[3][thisSolve->axis] < thisSolve->floor) + { + // printf("-- broken plane: %f\n", thisJointGlobalMat[3][thisSolve->axis]); + if(parentJointGlobalMat[3][thisSolve->axis] < thisSolve->floor) + { + printf("Error: Constraint parent has broken the plane, this frame's plane constraint unsolvable!\n"); + } + else + { + Vector parentJointAtPlane(parentJointGlobalMat[3][0], parentJointGlobalMat[3][1], parentJointGlobalMat[3][2]); + Vector parentPos(parentJointGlobalMat[3][0], parentJointGlobalMat[3][1], parentJointGlobalMat[3][2]); + Vector thisJointAtPlane(thisJointGlobalMat[3][0], thisJointGlobalMat[3][1], thisJointGlobalMat[3][2]); + Vector thisJointPos(thisJointGlobalMat[3][0], thisJointGlobalMat[3][1], thisJointGlobalMat[3][2]); + + thisJointAtPlane[thisSolve->axis] = thisSolve->floor; + parentJointAtPlane[thisSolve->axis] = thisSolve->floor; + + float thisJointLength = pSource->rawanim[t][thisJointIndex].pos[BONEAXIS]; + float parentLengthToPlane = parentPos[thisSolve->axis] - thisSolve->floor; + float adjacent = sqrtf((thisJointLength * thisJointLength) - (parentLengthToPlane * parentLengthToPlane)); + Vector parentDirection = thisJointAtPlane - parentJointAtPlane; + VectorNormalize(parentDirection); + + Vector newJointPos = parentJointAtPlane + (parentDirection * adjacent); + + Vector newParentDir = newJointPos - parentPos; + Vector parentUp(parentJointGlobalMat[BONEUP][0], parentJointGlobalMat[BONEUP][1], parentJointGlobalMat[BONEUP][2]); + + VectorNormalize(newParentDir); + VectorNormalize(parentUp); + // Vector parentSide = newParentDir.Cross(parentUp); + Vector parentSide = parentUp.Cross(newParentDir); + VectorNormalize(parentSide); + parentUp = newParentDir.Cross(parentSide); + // parentUp = parentSide.Cross(newParentDir); + VectorNormalize(parentUp); + parentJointGlobalMat[BONEDIR][0] = newParentDir[0]; + parentJointGlobalMat[BONEDIR][1] = newParentDir[1]; + parentJointGlobalMat[BONEDIR][2] = newParentDir[2]; + parentJointGlobalMat[BONEUP][0] = parentUp[0]; + parentJointGlobalMat[BONEUP][1] = parentUp[1]; + parentJointGlobalMat[BONEUP][2] = parentUp[2]; + parentJointGlobalMat[BONESIDE][0] = parentSide[0]; + parentJointGlobalMat[BONESIDE][1] = parentSide[1]; + parentJointGlobalMat[BONESIDE][2] = parentSide[2]; + + + M_matrix4x4_t newParentJointMat; + + M_ConcatTransforms(parentJointGlobalMat, gParentJointGlobalMatInverse, newParentJointMat); + + s_bone_t resultBone; + M_MatrixAngles(newParentJointMat, resultBone.rot, resultBone.pos); + pSource->rawanim[t][parentIndex].rot = resultBone.rot; + } + } + } + + // swap animation back for next solve + combinedAnimation[t] = pSource->rawanim[t]; + pSource->rawanim[t] = sourceAnimation[t]; + } + for(int t = 0; t < sourceNumFrames; t++) + { + pTarget->rawanim[t] = combinedAnimation[t]; + } + pTarget->numframes = sourceNumFrames; + + + + + +#if 0 + // Process motion mapping into out and return that + s_source_t *out = new s_source_t; + + return out; +#else + // Just returns the start animation, to test the Save_SMD API. + return pTarget; +#endif +} + +char templates[] = +"\n\ +#\n\ +# default template file is analogus to not specifying a template file at all\n\ +#\n\ +\n\ +rootScaleJoint ValveBiped.Bip01_L_Foot\n\ +rootScaleAmount 1.0\n\ +toeFloorZ 2.7777\n\ +\n\ +twoJointIKSolve ValveBiped.Bip01_L_Foot\n\ +reverseSolve 0\n\ +extremityScale 1.0\n\ +limbRootOffsetScale 1.0 1.0 0.0\n\ +\n\ +twoJointIKSolve ValveBiped.Bip01_R_Foot\n\ +reverseSolve 0\n\ +extremityScale 1.0\n\ +limbRootOffsetScale 1.0 1.0 0.0\n\ +\n\ +oneJointPlaneConstraint ValveBiped.Bip01_L_Toe0\n\ +\n\ +oneJointPlaneConstraint ValveBiped.Bip01_R_Toe0\n\ +\n\ +twoJointIKSolve ValveBiped.Bip01_R_Hand\n\ +reverseSolve 1\n\ +extremityScale 1.0\n\ +limbRootOffsetScale 0.0 0.0 1.0\n\ +\n\ +twoJointIKSolve ValveBiped.Bip01_L_Hand\n\ +reverseSolve 1\n\ +extremityScale 1.0\n\ +limbRootOffsetScale 0.0 0.0 1.0\n\ +\n\ +"; + + +void UsageAndExit() +{ + MdlError( "usage: motionmapper [-quiet] [-verbose] [-templateFile filename] [-printTemplates] sourceanim.smd targetskeleton.smd output.smd\n\ +\tsourceanim: should contain ref pose and animation data\n\ +\ttargetsekeleton: should contain new ref pose, animation data ignored/can be absent\n\ +\toutput: animation from source mapped onto target skeleton (contains new ref pose)\n\ +\t-templateFile filename : specifies a template file for guiding the mapping of motion\n\ +\t-printTemplate: Causes motionmapper to output the contents of an example template file, which can be used in conjunction with the -templateFile argument to create various motion effects.\n\ +\n"); +} + +void PrintHeader() +{ + vprint( 0, "Valve Software - motionmapper.exe ((c) Valve Coroporation %s)\n", __DATE__ ); + vprint( 0, "--- Maps motion from one animation/skeleton onto another skeleton ---\n" ); +} + + + +/* +============== +main +============== +*/ +int main (int argc, char **argv) +{ + int i; + + int useTemplate = 0; + char templateFileName[1024]; + + // Header + PrintHeader(); + + // Init command line stuff + CommandLine()->CreateCmdLine( argc, argv ); + InstallSpewFunction(); + + // init math stuff + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false ); + g_currentscale = g_defaultscale = 1.0; + g_defaultrotation = RadianEuler( 0, 0, M_PI / 2 ); + + // No args? + if (argc == 1) + { + UsageAndExit(); + } + + // Init variable + g_quiet = false; + + // list template hooey + CUtlVector< CUtlSymbol > filenames; + + // Get args + for (i = 1; i < argc; i++) + { + // Switches + if (argv[i][0] == '-') + { + if (!stricmp(argv[i], "-allowdebug")) + { + // Ignore, used by interface system to catch debug builds checked into release tree + continue; + } + + if (!stricmp(argv[i], "-quiet")) + { + g_quiet = true; + g_verbose = false; + continue; + } + + if (!stricmp(argv[i], "-verbose")) + { + g_quiet = false; + g_verbose = true; + continue; + } + if (!stricmp(argv[i], "-printTemplate")) + { + printf("%s\n", templates); + exit(0); + + } + if (!stricmp(argv[i], "-templateFile")) + { + if(i + 1 < argc) + { + strcpy( templateFileName, argv[i+1]); + useTemplate = 1; + printf("Note: %s passed as template file", templateFileName); + } + else + { + printf("Error: -templateFile requires an argument, none found!"); + UsageAndExit(); + + } + i++; + continue; + } + } + else + { + // more template stuff + CUtlSymbol sym = argv[ i ]; + filenames.AddToTail( sym ); + } + } + + // Enough file args? + if ( filenames.Count() != 3 ) + { + // misformed arguments + // otherwise generating unintended results + printf("Error: 3 file arguments required, %i found!", filenames.Count()); + UsageAndExit(); + } + + // Filename arg indexes + int sourceanim = 0; + int targetskel = 1; + int outputanim = 2; + + // Copy arg string to global variable + strcpy( g_outfile, filenames[ outputanim ].String() ); + + // Init filesystem hooey + CmdLib_InitFileSystem( g_outfile ); + // ?? + Q_FileBase( g_outfile, g_outfile, sizeof( g_outfile ) ); + + // Verbose stuff + if (!g_quiet) + { + vprint( 0, "%s, %s, %s, path %s\n", qdir, gamedir, g_outfile ); + } + // ?? + Q_DefaultExtension(g_outfile, ".smd", sizeof( g_outfile ) ); + + // Verbose stuff + if (!g_quiet) + { + vprint( 0, "Source animation: %s\n", filenames[ sourceanim ].String() ); + vprint( 0, "Target skeleton: %s\n", filenames[ targetskel ].String() ); + + vprint( 0, "Creating on \"%s\"\n", g_outfile); + } + // fullpath = EXTERNAL GLOBAL!!!??? + strcpy( fullpath, g_outfile ); + strcpy( fullpath, ExpandPath( fullpath ) ); + strcpy( fullpath, ExpandArg( fullpath ) ); + + // Load source and target data + s_source_t *pSource = Load_Source( filenames[sourceanim].String(), "smd", false, false ); + s_source_t *pTarget = Load_Source( filenames[targetskel].String(), "smd", false, false ); + + + // + s_template_t *pTemplate = NULL; + if(useTemplate) + { + pTemplate = Load_Template(templateFileName); + } + else + { + printf("Note: No template file specified, using defaults settings.\n"); + + pTemplate = New_Template(); + Set_DefaultTemplate(pTemplate); + } + + + // Process skeleton + s_source_t *pMappedAnimation = MotionMap( pSource, pTarget, pTemplate ); + + + // Save output (ref skeleton & animation data); + Save_SMD( fullpath, pMappedAnimation ); + + Q_StripExtension( filenames[outputanim].String(), outname, sizeof( outname ) ); + + // Verbose stuff + if (!g_quiet) + { + vprint( 0, "\nCompleted \"%s\"\n", g_outfile); + } + + return 0; +} + diff --git a/mp/src/utils/motionmapper/motionmapper.h b/mp/src/utils/motionmapper/motionmapper.h index 0df3f8ee..6b487e3c 100644 --- a/mp/src/utils/motionmapper/motionmapper.h +++ b/mp/src/utils/motionmapper/motionmapper.h @@ -1,274 +1,274 @@ -/*** -* -//========= Copyright Valve Corporation, 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 -#include "basetypes.h" -#include "utlvector.h" -#include "utlsymbol.h" -#include "mathlib/vector.h" -#include "studio.h" - -struct LodScriptData_t; - -#define IDSTUDIOHEADER (('T'<<24)+('S'<<16)+('D'<<8)+'I') - // little-endian "IDST" -#define IDSTUDIOANIMGROUPHEADER (('G'<<24)+('A'<<16)+('D'<<8)+'I') - // little-endian "IDAG" - - -#define STUDIO_QUADRATIC_MOTION 0x00002000 - -#define MAXSTUDIOANIMFRAMES 2000 // max frames per animation -#define MAXSTUDIOSEQUENCES 1524 // total sequences -#define MAXSTUDIOSRCBONES 512 // bones allowed at source movement -#define MAXSTUDIOBONEWEIGHTS 3 -#define MAXSTUDIONAME 128 - -#ifndef EXTERN -#define EXTERN extern -#endif - -EXTERN char outname[1024]; -EXTERN int numdirs; -EXTERN char cddir[32][MAX_PATH]; -EXTERN char fullpath[1024]; - -EXTERN float g_defaultscale; -EXTERN float g_currentscale; -EXTERN RadianEuler g_defaultrotation; - - -EXTERN char defaulttexture[16][MAX_PATH]; -EXTERN char sourcetexture[16][MAX_PATH]; - -EXTERN int numrep; - -EXTERN int flip_triangles; -EXTERN float normal_blend; - - -void *kalloc( int num, int size ); - -struct s_trianglevert_t -{ - int vertindex; - int normindex; // index into normal array - int s,t; - float u,v; -}; - -struct s_boneweight_t -{ - int numbones; - - int bone[MAXSTUDIOBONEWEIGHTS]; - float weight[MAXSTUDIOBONEWEIGHTS]; -}; - - -struct s_vertexinfo_t -{ - // wtf is this doing here? - int material; - - int firstref; - int lastref; - - int flexmask; - int numflex; - int flexoffset; -}; - -struct s_tmpface_t -{ - int material; - unsigned long a, b, c; - unsigned long ta, tb, tc; - unsigned long na, nb, nc; -}; - -struct s_face_t -{ - unsigned long a, b, c; -}; - -struct s_node_t -{ - char name[MAXSTUDIONAME]; - int parent; -}; - - -struct s_bone_t -{ - Vector pos; - RadianEuler rot; -}; - -struct s_texture_t -{ - char name[MAX_PATH]; - int flags; - int parent; - int material; - float width; - float height; - float dPdu; - float dPdv; -}; -EXTERN s_texture_t g_texture[MAXSTUDIOSKINS]; -EXTERN int g_numtextures; -EXTERN int g_material[MAXSTUDIOSKINS]; // link into texture array -EXTERN int g_nummaterials; - -EXTERN float g_gamma; -EXTERN int g_numskinref; -EXTERN int g_numskinfamilies; -EXTERN int g_skinref[256][MAXSTUDIOSKINS]; // [skin][skinref], returns texture index -EXTERN int g_numtexturegroups; -EXTERN int g_numtexturelayers[32]; -EXTERN int g_numtexturereps[32]; -EXTERN int g_texturegroup[32][32][32]; - -struct s_mesh_t -{ - int numvertices; - int vertexoffset; - - int numfaces; - int faceoffset; -}; - - -struct s_vertanim_t -{ - int vertex; - float speed; - float side; - Vector pos; - Vector normal; -}; - -// processed aggregate lod pools -struct s_loddata_t -{ - int numvertices; - s_boneweight_t *globalBoneweight; - s_vertexinfo_t *vertexInfo; - Vector *vertex; - Vector *normal; - Vector4D *tangentS; - Vector2D *texcoord; - - int numfaces; - s_face_t *face; - - s_mesh_t mesh[MAXSTUDIOSKINS]; - - // remaps verts from an lod's source mesh to this all-lod processed aggregate pool - int *pMeshVertIndexMaps[MAX_NUM_LODS]; -}; - -// raw off-disk source files. Raw data should be not processed. -struct s_source_t -{ - char filename[MAX_PATH]; - int time; // time stamp - - bool isActiveModel; - - // local skeleton hierarchy - int numbones; - s_node_t localBone[MAXSTUDIOSRCBONES]; - matrix3x4_t boneToPose[MAXSTUDIOSRCBONES]; // converts bone local data into initial pose data - - // bone remapping - int boneflags[MAXSTUDIOSRCBONES]; // attachment, vertex, etc flags for this bone - int boneref[MAXSTUDIOSRCBONES]; // flags for this and child bones - int boneLocalToGlobal[MAXSTUDIOSRCBONES]; // bonemap : local bone to world bone mapping - int boneGlobalToLocal[MAXSTUDIOSRCBONES]; // boneimap : world bone to local bone mapping - - int texmap[MAXSTUDIOSKINS*4]; // map local MAX materials to unique textures - - // per material mesh - int nummeshes; - int meshindex[MAXSTUDIOSKINS]; // mesh to skin index - s_mesh_t mesh[MAXSTUDIOSKINS]; - - // model global copy of vertices - int numvertices; - s_boneweight_t *localBoneweight; // vertex info about local bone weighting - s_boneweight_t *globalBoneweight; // vertex info about global bone weighting - s_vertexinfo_t *vertexInfo; // generic vertex info - Vector *vertex; - Vector *normal; - Vector4D *tangentS; - Vector2D *texcoord; - - int numfaces; - s_face_t *face; // vertex indexs per face - - // raw skeletal animation - int numframes; - int startframe; - int endframe; - s_bone_t *rawanim[MAXSTUDIOANIMFRAMES]; // [frame][bones]; - - // vertex animation - int *vanim_mapcount; // local verts map to N target verts - int **vanim_map; // local vertices to target vertices mapping list - int *vanim_flag; // local vert does animate - - int numvanims[MAXSTUDIOANIMFRAMES]; - s_vertanim_t *vanim[MAXSTUDIOANIMFRAMES]; // [frame][vertex] - - // processed aggregate lod data - s_loddata_t *pLodData; -}; - - -EXTERN int g_numsources; -EXTERN s_source_t *g_source[MAXSTUDIOSEQUENCES]; - -EXTERN int is_v1support; - -EXTERN int g_numverts; -EXTERN Vector g_vertex[MAXSTUDIOVERTS]; -EXTERN s_boneweight_t g_bone[MAXSTUDIOVERTS]; - -EXTERN int g_numnormals; -EXTERN Vector g_normal[MAXSTUDIOVERTS]; - -EXTERN int g_numtexcoords; -EXTERN Vector2D g_texcoord[MAXSTUDIOVERTS]; - -EXTERN int g_numfaces; -EXTERN s_tmpface_t g_face[MAXSTUDIOTRIANGLES]; -EXTERN s_face_t g_src_uface[MAXSTUDIOTRIANGLES]; // max res unified faces - -struct v_unify_t -{ - int refcount; - int lastref; - int firstref; - int v; - int m; - int n; - int t; - v_unify_t *next; -}; - -EXTERN v_unify_t *v_list[MAXSTUDIOVERTS]; -EXTERN v_unify_t v_listdata[MAXSTUDIOVERTS]; -EXTERN int numvlist; - -int SortAndBalanceBones( int iCount, int iMaxCount, int bones[], float weights[] ); -void Grab_Vertexanimation( s_source_t *psource ); -extern void BuildIndividualMeshes( s_source_t *psource ); +/*** +* +//========= Copyright Valve Corporation, 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 +#include "basetypes.h" +#include "utlvector.h" +#include "utlsymbol.h" +#include "mathlib/vector.h" +#include "studio.h" + +struct LodScriptData_t; + +#define IDSTUDIOHEADER (('T'<<24)+('S'<<16)+('D'<<8)+'I') + // little-endian "IDST" +#define IDSTUDIOANIMGROUPHEADER (('G'<<24)+('A'<<16)+('D'<<8)+'I') + // little-endian "IDAG" + + +#define STUDIO_QUADRATIC_MOTION 0x00002000 + +#define MAXSTUDIOANIMFRAMES 2000 // max frames per animation +#define MAXSTUDIOSEQUENCES 1524 // total sequences +#define MAXSTUDIOSRCBONES 512 // bones allowed at source movement +#define MAXSTUDIOBONEWEIGHTS 3 +#define MAXSTUDIONAME 128 + +#ifndef EXTERN +#define EXTERN extern +#endif + +EXTERN char outname[1024]; +EXTERN int numdirs; +EXTERN char cddir[32][MAX_PATH]; +EXTERN char fullpath[1024]; + +EXTERN float g_defaultscale; +EXTERN float g_currentscale; +EXTERN RadianEuler g_defaultrotation; + + +EXTERN char defaulttexture[16][MAX_PATH]; +EXTERN char sourcetexture[16][MAX_PATH]; + +EXTERN int numrep; + +EXTERN int flip_triangles; +EXTERN float normal_blend; + + +void *kalloc( int num, int size ); + +struct s_trianglevert_t +{ + int vertindex; + int normindex; // index into normal array + int s,t; + float u,v; +}; + +struct s_boneweight_t +{ + int numbones; + + int bone[MAXSTUDIOBONEWEIGHTS]; + float weight[MAXSTUDIOBONEWEIGHTS]; +}; + + +struct s_vertexinfo_t +{ + // wtf is this doing here? + int material; + + int firstref; + int lastref; + + int flexmask; + int numflex; + int flexoffset; +}; + +struct s_tmpface_t +{ + int material; + unsigned long a, b, c; + unsigned long ta, tb, tc; + unsigned long na, nb, nc; +}; + +struct s_face_t +{ + unsigned long a, b, c; +}; + +struct s_node_t +{ + char name[MAXSTUDIONAME]; + int parent; +}; + + +struct s_bone_t +{ + Vector pos; + RadianEuler rot; +}; + +struct s_texture_t +{ + char name[MAX_PATH]; + int flags; + int parent; + int material; + float width; + float height; + float dPdu; + float dPdv; +}; +EXTERN s_texture_t g_texture[MAXSTUDIOSKINS]; +EXTERN int g_numtextures; +EXTERN int g_material[MAXSTUDIOSKINS]; // link into texture array +EXTERN int g_nummaterials; + +EXTERN float g_gamma; +EXTERN int g_numskinref; +EXTERN int g_numskinfamilies; +EXTERN int g_skinref[256][MAXSTUDIOSKINS]; // [skin][skinref], returns texture index +EXTERN int g_numtexturegroups; +EXTERN int g_numtexturelayers[32]; +EXTERN int g_numtexturereps[32]; +EXTERN int g_texturegroup[32][32][32]; + +struct s_mesh_t +{ + int numvertices; + int vertexoffset; + + int numfaces; + int faceoffset; +}; + + +struct s_vertanim_t +{ + int vertex; + float speed; + float side; + Vector pos; + Vector normal; +}; + +// processed aggregate lod pools +struct s_loddata_t +{ + int numvertices; + s_boneweight_t *globalBoneweight; + s_vertexinfo_t *vertexInfo; + Vector *vertex; + Vector *normal; + Vector4D *tangentS; + Vector2D *texcoord; + + int numfaces; + s_face_t *face; + + s_mesh_t mesh[MAXSTUDIOSKINS]; + + // remaps verts from an lod's source mesh to this all-lod processed aggregate pool + int *pMeshVertIndexMaps[MAX_NUM_LODS]; +}; + +// raw off-disk source files. Raw data should be not processed. +struct s_source_t +{ + char filename[MAX_PATH]; + int time; // time stamp + + bool isActiveModel; + + // local skeleton hierarchy + int numbones; + s_node_t localBone[MAXSTUDIOSRCBONES]; + matrix3x4_t boneToPose[MAXSTUDIOSRCBONES]; // converts bone local data into initial pose data + + // bone remapping + int boneflags[MAXSTUDIOSRCBONES]; // attachment, vertex, etc flags for this bone + int boneref[MAXSTUDIOSRCBONES]; // flags for this and child bones + int boneLocalToGlobal[MAXSTUDIOSRCBONES]; // bonemap : local bone to world bone mapping + int boneGlobalToLocal[MAXSTUDIOSRCBONES]; // boneimap : world bone to local bone mapping + + int texmap[MAXSTUDIOSKINS*4]; // map local MAX materials to unique textures + + // per material mesh + int nummeshes; + int meshindex[MAXSTUDIOSKINS]; // mesh to skin index + s_mesh_t mesh[MAXSTUDIOSKINS]; + + // model global copy of vertices + int numvertices; + s_boneweight_t *localBoneweight; // vertex info about local bone weighting + s_boneweight_t *globalBoneweight; // vertex info about global bone weighting + s_vertexinfo_t *vertexInfo; // generic vertex info + Vector *vertex; + Vector *normal; + Vector4D *tangentS; + Vector2D *texcoord; + + int numfaces; + s_face_t *face; // vertex indexs per face + + // raw skeletal animation + int numframes; + int startframe; + int endframe; + s_bone_t *rawanim[MAXSTUDIOANIMFRAMES]; // [frame][bones]; + + // vertex animation + int *vanim_mapcount; // local verts map to N target verts + int **vanim_map; // local vertices to target vertices mapping list + int *vanim_flag; // local vert does animate + + int numvanims[MAXSTUDIOANIMFRAMES]; + s_vertanim_t *vanim[MAXSTUDIOANIMFRAMES]; // [frame][vertex] + + // processed aggregate lod data + s_loddata_t *pLodData; +}; + + +EXTERN int g_numsources; +EXTERN s_source_t *g_source[MAXSTUDIOSEQUENCES]; + +EXTERN int is_v1support; + +EXTERN int g_numverts; +EXTERN Vector g_vertex[MAXSTUDIOVERTS]; +EXTERN s_boneweight_t g_bone[MAXSTUDIOVERTS]; + +EXTERN int g_numnormals; +EXTERN Vector g_normal[MAXSTUDIOVERTS]; + +EXTERN int g_numtexcoords; +EXTERN Vector2D g_texcoord[MAXSTUDIOVERTS]; + +EXTERN int g_numfaces; +EXTERN s_tmpface_t g_face[MAXSTUDIOTRIANGLES]; +EXTERN s_face_t g_src_uface[MAXSTUDIOTRIANGLES]; // max res unified faces + +struct v_unify_t +{ + int refcount; + int lastref; + int firstref; + int v; + int m; + int n; + int t; + v_unify_t *next; +}; + +EXTERN v_unify_t *v_list[MAXSTUDIOVERTS]; +EXTERN v_unify_t v_listdata[MAXSTUDIOVERTS]; +EXTERN int numvlist; + +int SortAndBalanceBones( int iCount, int iMaxCount, int bones[], float weights[] ); +void Grab_Vertexanimation( s_source_t *psource ); +extern void BuildIndividualMeshes( s_source_t *psource ); diff --git a/mp/src/utils/motionmapper/motionmapper.vpc b/mp/src/utils/motionmapper/motionmapper.vpc index 1272c040..95a53f56 100644 --- a/mp/src/utils/motionmapper/motionmapper.vpc +++ b/mp/src/utils/motionmapper/motionmapper.vpc @@ -1,88 +1,88 @@ -//----------------------------------------------------------------------------- -// MOTIONMAPPER.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" - -$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" - -$Configuration -{ - $Compiler - { - $AdditionalIncludeDirectories "$BASE,..\common,..\nvtristriplib,$SRCDIR\Game_Shared" - $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE" - } - - $Linker - { - $AdditionalDependencies "$BASE winmm.lib" - } -} - -$Project "Motionmapper" -{ - $Folder "Source Files" - { - $File "..\common\cmdlib.cpp" - $File "$SRCDIR\public\filesystem_helpers.cpp" - $File "$SRCDIR\public\filesystem_init.cpp" - $File "..\common\filesystem_tools.cpp" - $File "motionmapper.cpp" - $File "..\common\scriplib.cpp" - } - - $Folder "Header Files" - { - $File "$SRCDIR\public\mathlib\amd3dx.h" - $File "$SRCDIR\public\tier0\basetypes.h" - $File "$SRCDIR\public\tier1\characterset.h" - $File "..\common\cmdlib.h" - $File "$SRCDIR\public\Color.h" - $File "$SRCDIR\public\tier0\commonmacros.h" - $File "$SRCDIR\public\mathlib\compressed_vector.h" - $File "$SRCDIR\public\tier0\dbg.h" - $File "$SRCDIR\public\tier0\fasttimer.h" - $File "$SRCDIR\public\filesystem.h" - $File "$SRCDIR\public\filesystem_helpers.h" - $File "..\common\filesystem_tools.h" - $File "$SRCDIR\public\appframework\IAppSystem.h" - $File "$SRCDIR\public\tier0\icommandline.h" - $File "$SRCDIR\public\vstdlib\IKeyValuesSystem.h" - $File "$SRCDIR\public\tier1\interface.h" - $File "$SRCDIR\public\tier1\KeyValues.h" - $File "$SRCDIR\public\mathlib\mathlib.h" - $File "$SRCDIR\public\tier0\mem.h" - $File "$SRCDIR\public\tier0\memalloc.h" - $File "$SRCDIR\public\tier0\memdbgoff.h" - $File "$SRCDIR\public\tier0\memdbgon.h" - $File "motionmapper.h" - $File "$SRCDIR\public\tier0\platform.h" - $File "$SRCDIR\public\tier0\protected_things.h" - $File "..\common\scriplib.h" - $File "$SRCDIR\public\string_t.h" - $File "$SRCDIR\public\tier1\strtools.h" - $File "$SRCDIR\public\studio.h" - $File "$SRCDIR\public\tier1\utlbuffer.h" - $File "$SRCDIR\public\tier1\utldict.h" - $File "$SRCDIR\public\tier1\utllinkedlist.h" - $File "$SRCDIR\public\tier1\utlmemory.h" - $File "$SRCDIR\public\tier1\utlrbtree.h" - $File "$SRCDIR\public\tier1\utlsymbol.h" - $File "$SRCDIR\public\tier1\utlvector.h" - $File "$SRCDIR\public\mathlib\vector.h" - $File "$SRCDIR\public\mathlib\vector2d.h" - $File "$SRCDIR\public\mathlib\vector4d.h" - $File "$SRCDIR\public\vstdlib\vstdlib.h" - } - - $Folder "Link Libraries" - { - $Lib mathlib - $Lib nvtristrip - $Lib tier2 - } -} +//----------------------------------------------------------------------------- +// MOTIONMAPPER.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\common,..\nvtristriplib,$SRCDIR\Game_Shared" + $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE" + } + + $Linker + { + $AdditionalDependencies "$BASE winmm.lib" + } +} + +$Project "Motionmapper" +{ + $Folder "Source Files" + { + $File "..\common\cmdlib.cpp" + $File "$SRCDIR\public\filesystem_helpers.cpp" + $File "$SRCDIR\public\filesystem_init.cpp" + $File "..\common\filesystem_tools.cpp" + $File "motionmapper.cpp" + $File "..\common\scriplib.cpp" + } + + $Folder "Header Files" + { + $File "$SRCDIR\public\mathlib\amd3dx.h" + $File "$SRCDIR\public\tier0\basetypes.h" + $File "$SRCDIR\public\tier1\characterset.h" + $File "..\common\cmdlib.h" + $File "$SRCDIR\public\Color.h" + $File "$SRCDIR\public\tier0\commonmacros.h" + $File "$SRCDIR\public\mathlib\compressed_vector.h" + $File "$SRCDIR\public\tier0\dbg.h" + $File "$SRCDIR\public\tier0\fasttimer.h" + $File "$SRCDIR\public\filesystem.h" + $File "$SRCDIR\public\filesystem_helpers.h" + $File "..\common\filesystem_tools.h" + $File "$SRCDIR\public\appframework\IAppSystem.h" + $File "$SRCDIR\public\tier0\icommandline.h" + $File "$SRCDIR\public\vstdlib\IKeyValuesSystem.h" + $File "$SRCDIR\public\tier1\interface.h" + $File "$SRCDIR\public\tier1\KeyValues.h" + $File "$SRCDIR\public\mathlib\mathlib.h" + $File "$SRCDIR\public\tier0\mem.h" + $File "$SRCDIR\public\tier0\memalloc.h" + $File "$SRCDIR\public\tier0\memdbgoff.h" + $File "$SRCDIR\public\tier0\memdbgon.h" + $File "motionmapper.h" + $File "$SRCDIR\public\tier0\platform.h" + $File "$SRCDIR\public\tier0\protected_things.h" + $File "..\common\scriplib.h" + $File "$SRCDIR\public\string_t.h" + $File "$SRCDIR\public\tier1\strtools.h" + $File "$SRCDIR\public\studio.h" + $File "$SRCDIR\public\tier1\utlbuffer.h" + $File "$SRCDIR\public\tier1\utldict.h" + $File "$SRCDIR\public\tier1\utllinkedlist.h" + $File "$SRCDIR\public\tier1\utlmemory.h" + $File "$SRCDIR\public\tier1\utlrbtree.h" + $File "$SRCDIR\public\tier1\utlsymbol.h" + $File "$SRCDIR\public\tier1\utlvector.h" + $File "$SRCDIR\public\mathlib\vector.h" + $File "$SRCDIR\public\mathlib\vector2d.h" + $File "$SRCDIR\public\mathlib\vector4d.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + } + + $Folder "Link Libraries" + { + $Lib mathlib + $Lib nvtristrip + $Lib tier2 + } +} diff --git a/mp/src/utils/nvtristriplib/nvtristrip.h b/mp/src/utils/nvtristriplib/nvtristrip.h index 65ad5ef8..b97b679d 100644 --- a/mp/src/utils/nvtristriplib/nvtristrip.h +++ b/mp/src/utils/nvtristriplib/nvtristrip.h @@ -1,124 +1,124 @@ -#ifndef NVTRISTRIP_H -#define NVTRISTRIP_H - -#ifndef NULL -#define NULL 0 -#endif - -#pragma comment(lib, "nvtristrip") - -//////////////////////////////////////////////////////////////////////////////////////// -// Public interface for stripifier -//////////////////////////////////////////////////////////////////////////////////////// - -//GeForce1 and 2 cache size -#define CACHESIZE_GEFORCE1_2 16 - -//GeForce3 cache size -#define CACHESIZE_GEFORCE3 24 - -enum PrimType -{ - PT_LIST, - PT_STRIP, - PT_FAN -}; - -struct PrimitiveGroup -{ - PrimType type; - unsigned int numIndices; - unsigned short* indices; - -//////////////////////////////////////////////////////////////////////////////////////// - - PrimitiveGroup() : type(PT_STRIP), numIndices(0), indices(NULL) {} - ~PrimitiveGroup() - { - if(indices) - delete[] indices; - indices = NULL; - } -}; - -//////////////////////////////////////////////////////////////////////////////////////// -// SetCacheSize() -// -// Sets the cache size which the stripfier uses to optimize the data. -// Controls the length of the generated individual strips. -// This is the "actual" cache size, so 24 for GeForce3 and 16 for GeForce1/2 -// You may want to play around with this number to tweak performance. -// -// Default value: 16 -// -void SetCacheSize(const unsigned int cacheSize); - - -//////////////////////////////////////////////////////////////////////////////////////// -// SetStitchStrips() -// -// bool to indicate whether to stitch together strips into one huge strip or not. -// If set to true, you'll get back one huge strip stitched together using degenerate -// triangles. -// If set to false, you'll get back a large number of separate strips. -// -// Default value: true -// -void SetStitchStrips(const bool bStitchStrips); - - -//////////////////////////////////////////////////////////////////////////////////////// -// SetMinStripSize() -// -// Sets the minimum acceptable size for a strip, in triangles. -// All strips generated which are shorter than this will be thrown into one big, separate list. -// -// Default value: 0 -// -void SetMinStripSize(const unsigned int minSize); - - -//////////////////////////////////////////////////////////////////////////////////////// -// SetListsOnly() -// -// If set to true, will return an optimized list, with no strips at all. -// -// Default value: false -// -void SetListsOnly(const bool bListsOnly); - - -//////////////////////////////////////////////////////////////////////////////////////// -// GenerateStrips() -// -// in_indices: input index list, the indices you would use to render -// in_numIndices: number of entries in in_indices -// primGroups: array of optimized/stripified PrimitiveGroups -// numGroups: number of groups returned -// -// Be sure to call delete[] on the returned primGroups to avoid leaking mem -// -void GenerateStrips(const unsigned short* in_indices, const unsigned int in_numIndices, - PrimitiveGroup** primGroups, unsigned short* numGroups); - - -//////////////////////////////////////////////////////////////////////////////////////// -// RemapIndices() -// -// Function to remap your indices to improve spatial locality in your vertex buffer. -// -// in_primGroups: array of PrimitiveGroups you want remapped -// numGroups: number of entries in in_primGroups -// numVerts: number of vertices in your vertex buffer, also can be thought of as the range -// of acceptable values for indices in your primitive groups. -// remappedGroups: array of remapped PrimitiveGroups -// -// Note that, according to the remapping handed back to you, you must reorder your -// vertex buffer. -// -// Credit goes to the MS Xbox crew for the idea for this interface. -// -void RemapIndices(const PrimitiveGroup* in_primGroups, const unsigned short numGroups, - const unsigned short numVerts, PrimitiveGroup** remappedGroups); - +#ifndef NVTRISTRIP_H +#define NVTRISTRIP_H + +#ifndef NULL +#define NULL 0 +#endif + +#pragma comment(lib, "nvtristrip") + +//////////////////////////////////////////////////////////////////////////////////////// +// Public interface for stripifier +//////////////////////////////////////////////////////////////////////////////////////// + +//GeForce1 and 2 cache size +#define CACHESIZE_GEFORCE1_2 16 + +//GeForce3 cache size +#define CACHESIZE_GEFORCE3 24 + +enum PrimType +{ + PT_LIST, + PT_STRIP, + PT_FAN +}; + +struct PrimitiveGroup +{ + PrimType type; + unsigned int numIndices; + unsigned short* indices; + +//////////////////////////////////////////////////////////////////////////////////////// + + PrimitiveGroup() : type(PT_STRIP), numIndices(0), indices(NULL) {} + ~PrimitiveGroup() + { + if(indices) + delete[] indices; + indices = NULL; + } +}; + +//////////////////////////////////////////////////////////////////////////////////////// +// SetCacheSize() +// +// Sets the cache size which the stripfier uses to optimize the data. +// Controls the length of the generated individual strips. +// This is the "actual" cache size, so 24 for GeForce3 and 16 for GeForce1/2 +// You may want to play around with this number to tweak performance. +// +// Default value: 16 +// +void SetCacheSize(const unsigned int cacheSize); + + +//////////////////////////////////////////////////////////////////////////////////////// +// SetStitchStrips() +// +// bool to indicate whether to stitch together strips into one huge strip or not. +// If set to true, you'll get back one huge strip stitched together using degenerate +// triangles. +// If set to false, you'll get back a large number of separate strips. +// +// Default value: true +// +void SetStitchStrips(const bool bStitchStrips); + + +//////////////////////////////////////////////////////////////////////////////////////// +// SetMinStripSize() +// +// Sets the minimum acceptable size for a strip, in triangles. +// All strips generated which are shorter than this will be thrown into one big, separate list. +// +// Default value: 0 +// +void SetMinStripSize(const unsigned int minSize); + + +//////////////////////////////////////////////////////////////////////////////////////// +// SetListsOnly() +// +// If set to true, will return an optimized list, with no strips at all. +// +// Default value: false +// +void SetListsOnly(const bool bListsOnly); + + +//////////////////////////////////////////////////////////////////////////////////////// +// GenerateStrips() +// +// in_indices: input index list, the indices you would use to render +// in_numIndices: number of entries in in_indices +// primGroups: array of optimized/stripified PrimitiveGroups +// numGroups: number of groups returned +// +// Be sure to call delete[] on the returned primGroups to avoid leaking mem +// +void GenerateStrips(const unsigned short* in_indices, const unsigned int in_numIndices, + PrimitiveGroup** primGroups, unsigned short* numGroups); + + +//////////////////////////////////////////////////////////////////////////////////////// +// RemapIndices() +// +// Function to remap your indices to improve spatial locality in your vertex buffer. +// +// in_primGroups: array of PrimitiveGroups you want remapped +// numGroups: number of entries in in_primGroups +// numVerts: number of vertices in your vertex buffer, also can be thought of as the range +// of acceptable values for indices in your primitive groups. +// remappedGroups: array of remapped PrimitiveGroups +// +// Note that, according to the remapping handed back to you, you must reorder your +// vertex buffer. +// +// Credit goes to the MS Xbox crew for the idea for this interface. +// +void RemapIndices(const PrimitiveGroup* in_primGroups, const unsigned short numGroups, + const unsigned short numVerts, PrimitiveGroup** remappedGroups); + #endif \ No newline at end of file diff --git a/mp/src/utils/phonemeextractor/extractor_utils.cpp b/mp/src/utils/phonemeextractor/extractor_utils.cpp index ba927f04..8b8ff98b 100644 --- a/mp/src/utils/phonemeextractor/extractor_utils.cpp +++ b/mp/src/utils/phonemeextractor/extractor_utils.cpp @@ -1,28 +1,28 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// -#include -#include - -//----------------------------------------------------------------------------- -// Purpose: converts an english string to unicode -//----------------------------------------------------------------------------- -int ConvertANSIToUnicode(const char *ansi, wchar_t *unicode, int unicodeBufferSize) -{ - return ::MultiByteToWideChar(CP_ACP, 0, ansi, -1, unicode, unicodeBufferSize); -} - -char *va( const char *fmt, ... ) -{ - va_list args; - static char output[4][1024]; - static int outbuffer = 0; - - outbuffer++; - va_start( args, fmt ); - vprintf( fmt, args ); - vsprintf( output[ outbuffer & 3 ], fmt, args ); - return output[ outbuffer & 3 ]; +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include +#include + +//----------------------------------------------------------------------------- +// Purpose: converts an english string to unicode +//----------------------------------------------------------------------------- +int ConvertANSIToUnicode(const char *ansi, wchar_t *unicode, int unicodeBufferSize) +{ + return ::MultiByteToWideChar(CP_ACP, 0, ansi, -1, unicode, unicodeBufferSize); +} + +char *va( const char *fmt, ... ) +{ + va_list args; + static char output[4][1024]; + static int outbuffer = 0; + + outbuffer++; + va_start( args, fmt ); + vprintf( fmt, args ); + vsprintf( output[ outbuffer & 3 ], fmt, args ); + return output[ outbuffer & 3 ]; } \ No newline at end of file diff --git a/mp/src/utils/phonemeextractor/phonemeextractor.cpp b/mp/src/utils/phonemeextractor/phonemeextractor.cpp index 8dfc8439..271f1850 100644 --- a/mp/src/utils/phonemeextractor/phonemeextractor.cpp +++ b/mp/src/utils/phonemeextractor/phonemeextractor.cpp @@ -1,1425 +1,1425 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// extracephonemes.cpp : Defines the entry point for the console application. -// -#define PROTECTED_THINGS_DISABLE - -#include "tier0/wchartypes.h" -#include -#include -#include -#include "sphelper.h" -#include "spddkhlp.h" -// ATL Header Files -#include -// Face poser and util includes -#include "utlvector.h" -#include "phonemeextractor/PhonemeExtractor.h" -#include "PhonemeConverter.h" -#include "sentence.h" -#include "tier0/dbg.h" -#include "tier0/icommandline.h" -#include "filesystem.h" - -// Extract phoneme grammar id -#define EP_GRAM_ID 101 -// First rule of dynamic sentence rule set -#define DYN_SENTENCERULE 102 -// # of milliseconds to allow for processing before timeout -#define SR_WAVTIMEOUT 4000 -// Weight tag for rule to rule word/rule transitions -#define CONFIDENCE_WEIGHT 0.0f - -//#define LOGGING 1 -#define LOGFILE "c:\\fp.log" - -void LogReset( void ) -{ -#if LOGGING - FILE *fp = fopen( LOGFILE, "w" ); - if ( fp ) - fclose( fp ); -#endif -} - -char *va( const char *fmt, ... ); - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *words - -//----------------------------------------------------------------------------- -void LogWords( CSentence& sentence ) -{ - Log( "Wordcount == %i\n", sentence.m_Words.Size() ); - - for ( int i = 0; i < sentence.m_Words.Size(); i++ ) - { - const CWordTag *w = sentence.m_Words[ i ]; - Log( "Word %s %u to %u\n", w->GetWord(), w->m_uiStartByte, w->m_uiEndByte ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *phonemes - -//----------------------------------------------------------------------------- -void LogPhonemes( CSentence& sentence ) -{ - return; - - Log( "Phonemecount == %i\n", sentence.CountPhonemes() ); - - for ( int i = 0; i < sentence.m_Words.Size(); i++ ) - { - const CWordTag *w = sentence.m_Words[ i ]; - - for ( int j = 0; j < w->m_Phonemes.Size(); j++ ) - { - const CPhonemeTag *p = w->m_Phonemes[ j ]; - Log( "Phoneme %s %u to %u\n", p->GetTag(), p->m_uiStartByte, p->m_uiEndByte ); - } - } -} - -#define NANO_CONVERT 10000000.0f; - -//----------------------------------------------------------------------------- -// Purpose: Walk list of words and phonemes and create phoneme tags in CSentence object -// FIXME: Right now, phonemes are assumed to evenly space out across a word. -// Input : *converter - -// result - -// sentence - -//----------------------------------------------------------------------------- -void EnumeratePhonemes( ISpPhoneConverter *converter, const ISpRecoResult* result, CSentence& sentence ) -{ - USES_CONVERSION; - - // Grab access to element container - ISpPhrase *phrase = ( ISpPhrase * )result; - if ( !phrase ) - return; - - SPPHRASE *pElements; - if ( !SUCCEEDED( phrase->GetPhrase( &pElements ) ) ) - return; - - // Only use it if it's better/same size as what we already had on-hand - if ( pElements->Rule.ulCountOfElements > 0 ) - //(unsigned int)( sentence.m_Words.Size() - sentence.GetWordBase() ) ) - { - sentence.ResetToBase(); - - // Walk list of words - for ( ULONG i = 0; i < pElements->Rule.ulCountOfElements; i++ ) - { - unsigned int wordstart, wordend; - - // Get start/end sample index - wordstart = pElements->pElements[i].ulAudioStreamOffset + (unsigned int)pElements->ullAudioStreamPosition; - wordend = wordstart + pElements->pElements[i].ulAudioSizeBytes; - - // Create word tag - CWordTag *w = new CWordTag( W2T( pElements->pElements[i].pszDisplayText ) ); - Assert( w ); - w->m_uiStartByte = wordstart; - w->m_uiEndByte = wordend; - - sentence.AddWordTag( w ); - - // Count # of phonemes in this word - SPPHONEID pstr[ 2 ]; - pstr[ 1 ] = 0; - WCHAR wszPhoneme[ SP_MAX_PRON_LENGTH ]; - - const SPPHONEID *current; - SPPHONEID phoneme; - current = pElements->pElements[i].pszPronunciation; - float total_weight = 0.0f; - while ( 1 ) - { - phoneme = *current++; - if ( !phoneme ) - break; - - pstr[ 0 ] = phoneme; - wszPhoneme[ 0 ] = L'\0'; - - converter->IdToPhone( pstr, wszPhoneme ); - - total_weight += WeightForPhoneme( W2A( wszPhoneme ) ); - } - - current = pElements->pElements[i].pszPronunciation; - - // Decide # of bytes/phoneme weight - float psize = 0; - if ( total_weight ) - { - psize = ( wordend - wordstart ) / total_weight; - } - - int number = 0; - - // Re-walk the phoneme list and create true phoneme tags - float startWeight = 0.0f; - while ( 1 ) - { - phoneme = *current++; - if ( !phoneme ) - break; - - pstr[ 0 ] = phoneme; - wszPhoneme[ 0 ] = L'\0'; - - converter->IdToPhone( pstr, wszPhoneme ); - - CPhonemeTag *p = new CPhonemeTag( W2A( wszPhoneme ) ); - Assert( p ); - - float weight = WeightForPhoneme( W2A( wszPhoneme ) ); - - p->m_uiStartByte = wordstart + (int)( startWeight * psize ); - p->m_uiEndByte = p->m_uiStartByte + (int)( psize * weight ); - - startWeight += weight; - - // Convert to IPA phoneme code - p->SetPhonemeCode( TextToPhoneme( p->GetTag() ) ); - - sentence.AddPhonemeTag( w, p ); - - number++; - } - } - } - - // Free memory - ::CoTaskMemFree(pElements); -} - -//----------------------------------------------------------------------------- -// Purpose: Create rules for each word in the reference sentence -//----------------------------------------------------------------------------- -typedef struct -{ - int ruleId; - SPSTATEHANDLE hRule; - CSpDynamicString word; - char plaintext[ 256 ]; -} WORDRULETYPE; - -//----------------------------------------------------------------------------- -// Purpose: Creates start for word of sentence -// Input : cpRecoGrammar - -// *root - -// *rules - -// word - -//----------------------------------------------------------------------------- -void AddWordRule( ISpRecoGrammar* cpRecoGrammar, SPSTATEHANDLE *root, CUtlVector< WORDRULETYPE > *rules, CSpDynamicString& word ) -{ - USES_CONVERSION; - HRESULT hr; - WORDRULETYPE *newrule; - - int idx = (*rules).AddToTail(); - - newrule = &(*rules)[ idx ]; - - newrule->ruleId = DYN_SENTENCERULE + idx + 1; - newrule->word = word; - - strcpy( newrule->plaintext, W2T( word ) ); - - // Create empty rule - hr = cpRecoGrammar->CreateNewState( *root, &newrule->hRule ); - Assert( !FAILED( hr ) ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : cpRecoGrammar - -// *from - -// *to - -//----------------------------------------------------------------------------- -void AddWordTransitionRule( ISpRecoGrammar* cpRecoGrammar, WORDRULETYPE *from, WORDRULETYPE *to ) -{ - USES_CONVERSION; - - HRESULT hr; - Assert( from ); - - if ( from && !to ) - { - OutputDebugString( va( "Transition from %s to TERM\r\n", from->plaintext ) ); - } - else - { - OutputDebugString( va( "Transition from %s to %s\r\n", from->plaintext, to->plaintext ) ); - } - - hr = cpRecoGrammar->AddWordTransition( from->hRule, to ? to->hRule : NULL, (WCHAR *)from->word, NULL, SPWT_LEXICAL, CONFIDENCE_WEIGHT, NULL ); - Assert( !FAILED( hr ) ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : cpRecoGrammar - -// *from - -// *to - -//----------------------------------------------------------------------------- -void AddOptionalTransitionRule( ISpRecoGrammar* cpRecoGrammar, WORDRULETYPE *from, WORDRULETYPE *to ) -{ - USES_CONVERSION; - - HRESULT hr; - Assert( from ); - - if ( from && !to ) - { - OutputDebugString( va( "Opt transition from %s to TERM\r\n", from->plaintext ) ); - } - else - { - OutputDebugString( va( "Opt transition from %s to %s\r\n", from->plaintext, to->plaintext ) ); - } - - hr = cpRecoGrammar->AddWordTransition( from->hRule, to ? to->hRule : NULL, NULL, NULL, SPWT_LEXICAL, CONFIDENCE_WEIGHT, NULL ); - Assert( !FAILED( hr ) ); -} - -#define MAX_WORD_SKIP 1 -//----------------------------------------------------------------------------- -// Purpose: Links together all word rule states into a sentence rule CFG -// Input : singleword - -// cpRecoGrammar - -// *root - -// *rules - -//----------------------------------------------------------------------------- -bool BuildRules( ISpRecoGrammar* cpRecoGrammar, SPSTATEHANDLE *root, CUtlVector< WORDRULETYPE > *rules ) -{ - HRESULT hr; - WORDRULETYPE *rule, *next; - - int numrules = (*rules).Size(); - - rule = &(*rules)[ 0 ]; - - // Add transition - hr = cpRecoGrammar->AddWordTransition( *root, rule->hRule, NULL, NULL, SPWT_LEXICAL, CONFIDENCE_WEIGHT, NULL ); - Assert( !FAILED( hr ) ); - - for ( int i = 0; i < numrules; i++ ) - { - rule = &(*rules)[ i ]; - if ( i < numrules - 1 ) - { - next = &(*rules)[ i + 1 ]; - } - else - { - next = NULL; - } - - AddWordTransitionRule( cpRecoGrammar, rule, next ); - } - - if ( numrules > 1 ) - { - for ( int skip = 1; skip <= min( MAX_WORD_SKIP, numrules ); skip++ ) - { - OutputDebugString( va( "Opt transition from Root to %s\r\n", (*rules)[ 0 ].plaintext ) ); - - hr = cpRecoGrammar->AddWordTransition( *root, (*rules)[ 0 ].hRule, NULL, NULL, SPWT_LEXICAL, CONFIDENCE_WEIGHT, NULL ); - - // Now build rules where you can skip 1 to N intervening words - for ( int i = 1; i < numrules; i++ ) - { - // Start at the beginning? - rule = &(*rules)[ i ]; - if ( i < numrules - skip ) - { - next = &(*rules)[ i + skip ]; - } - else - { - continue; - } - - // Add transition - AddOptionalTransitionRule( cpRecoGrammar, rule, next ); - } - - // Go from final rule to end point - AddOptionalTransitionRule( cpRecoGrammar, rule, NULL ); - } - } - - // Store it - hr = cpRecoGrammar->Commit(NULL); - if ( FAILED( hr ) ) - return false; - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Debugging, prints alternate list if one is created -// Input : cpResult - -// (*pfnPrint - -//----------------------------------------------------------------------------- -void PrintAlternates( ISpRecoResult* cpResult, void (*pfnPrint)( const char *fmt, ... ) ) -{ - ISpPhraseAlt *rgPhraseAlt[ 32 ]; - memset( rgPhraseAlt, 0, sizeof( rgPhraseAlt ) ); - - ULONG ulCount; - - ISpPhrase *phrase = ( ISpPhrase * )cpResult; - if ( phrase ) - { - SPPHRASE *pElements; - if ( SUCCEEDED( phrase->GetPhrase( &pElements ) ) ) - { - if ( pElements->Rule.ulCountOfElements > 0 ) - { - HRESULT hr = cpResult->GetAlternates( - pElements->Rule.ulFirstElement, - pElements->Rule.ulCountOfElements, - 32, - rgPhraseAlt, - &ulCount); - - Assert( !FAILED( hr ) ); - - for ( ULONG r = 0 ; r < ulCount; r++ ) - { - CSpDynamicString dstrText; - hr = rgPhraseAlt[ r ]->GetText( (ULONG)SP_GETWHOLEPHRASE, (ULONG)SP_GETWHOLEPHRASE, TRUE, &dstrText, NULL); - Assert( !FAILED( hr ) ); - - pfnPrint( "[ ALT ]" ); - pfnPrint( dstrText.CopyToChar() ); - pfnPrint( "\r\n" ); - } - } - } - - } - - for ( int i = 0; i < 32; i++ ) - { - if ( rgPhraseAlt[ i ] ) - { - rgPhraseAlt[ i ]->Release(); - rgPhraseAlt[ i ] = NULL; - } - } -} - -void PrintWordsAndPhonemes( CSentence& sentence, void (*pfnPrint)( const char *fmt, ... ) ) -{ - char sz[ 256 ]; - int i; - - pfnPrint( "WORDS\r\n\r\n" ); - - for ( i = 0 ; i < sentence.m_Words.Size(); i++ ) - { - CWordTag *word = sentence.m_Words[ i ]; - if ( !word ) - continue; - - sprintf( sz, "<%u - %u> %s\r\n", - word->m_uiStartByte, word->m_uiEndByte, word->GetWord() ); - - pfnPrint( sz ); - - for ( int j = 0 ; j < word->m_Phonemes.Size(); j++ ) - { - CPhonemeTag *phoneme = word->m_Phonemes[ j ]; - if ( !phoneme ) - continue; - - sprintf( sz, " <%u - %u> %s\r\n", - phoneme->m_uiStartByte, phoneme->m_uiEndByte, phoneme->GetTag() ); - - pfnPrint( sz ); - } - } - - pfnPrint( "\r\n" ); -} - -//----------------------------------------------------------------------------- -// Purpose: Given a wave file and a string of words "text", creates a CFG from the -// sentence and stores the resulting words/phonemes in CSentence -// Input : *wavname - -// text - -// sentence - -// (*pfnPrint - -// Output : SR_RESULT -//----------------------------------------------------------------------------- -SR_RESULT ExtractPhonemes( const char *wavname, CSpDynamicString& text, CSentence& sentence, void (*pfnPrint)( const char *fmt, ...) ) -{ - // Assume failure - SR_RESULT result = SR_RESULT_ERROR; - - if ( text.Length() <= 0 ) - { - pfnPrint( "Error: no rule / text specified\n" ); - return result; - } - - USES_CONVERSION; - HRESULT hr; - - CUtlVector < WORDRULETYPE > wordRules; - - CComPtr cpInputStream; - CComPtr cpRecognizer; - CComPtr cpRecoContext; - CComPtr cpRecoGrammar; - CComPtr cpPhoneConv; - - // Create basic SAPI stream object - // NOTE: The helper SpBindToFile can be used to perform the following operations - hr = cpInputStream.CoCreateInstance(CLSID_SpStream); - if ( FAILED( hr ) ) - { - pfnPrint( "Error: SAPI 5.1 Stream object not installed?\n" ); - return result; - } - - CSpStreamFormat sInputFormat; - - // setup stream object with wav file MY_WAVE_AUDIO_FILENAME - // for read-only access, since it will only be access by the SR engine - hr = cpInputStream->BindToFile( - T2W(wavname), - SPFM_OPEN_READONLY, - NULL, - sInputFormat.WaveFormatExPtr(), - SPFEI_ALL_EVENTS ); - - if ( FAILED( hr ) ) - { - pfnPrint( "Error: couldn't open wav file %s\n", wavname ); - return result; - } - - // Create in-process speech recognition engine - hr = cpRecognizer.CoCreateInstance(CLSID_SpInprocRecognizer); - if ( FAILED( hr ) ) - { - pfnPrint( "Error: SAPI 5.1 In process recognizer object not installed?\n" ); - return result; - } - - // Create recognition context to receive events - hr = cpRecognizer->CreateRecoContext(&cpRecoContext); - if ( FAILED( hr ) ) - { - pfnPrint( "Error: SAPI 5.1 Unable to create recognizer context\n" ); - return result; - } - - // Create a grammar - hr = cpRecoContext->CreateGrammar( EP_GRAM_ID, &cpRecoGrammar ); - if ( FAILED( hr ) ) - { - pfnPrint( "Error: SAPI 5.1 Unable to create recognizer grammar\n" ); - return result; - } - - LANGID englishID = 0x409; // 1033 decimal - - bool userSpecified = false; - LANGID langID = SpGetUserDefaultUILanguage(); - - // Allow commandline override - if ( CommandLine()->FindParm( "-languageid" ) != 0 ) - { - userSpecified = true; - langID = CommandLine()->ParmValue( "-languageid", langID ); - } - - // Create a phoneme converter ( so we can convert to IPA codes ) - hr = SpCreatePhoneConverter( langID, NULL, NULL, &cpPhoneConv ); - if ( FAILED( hr ) ) - { - if ( langID != englishID ) - { - if ( userSpecified ) - { - pfnPrint( "Warning: SAPI 5.1 Unable to create phoneme converter for command line override -languageid %i\n", langID ); - } - else - { - pfnPrint( "Warning: SAPI 5.1 Unable to create phoneme converter for default UI language %i\n",langID ); - } - - // Try english!!! - langID = englishID; - hr = SpCreatePhoneConverter( langID, NULL, NULL, &cpPhoneConv ); - } - - if ( FAILED( hr ) ) - { - pfnPrint( "Error: SAPI 5.1 Unable to create phoneme converter for English language id %i\n", langID ); - return result; - } - else - { - pfnPrint( "Note: SAPI 5.1 Falling back to use english -languageid %i\n", langID ); - } - } - else if ( userSpecified ) - { - pfnPrint( "Note: SAPI 5.1 Using user specified -languageid %i\n",langID ); - } - - SPSTATEHANDLE hStateRoot; - // create/re-create Root level rule of grammar - hr = cpRecoGrammar->GetRule(L"Root", 0, SPRAF_TopLevel | SPRAF_Active, TRUE, &hStateRoot); - if ( FAILED( hr ) ) - { - pfnPrint( "Error: SAPI 5.1 Unable to create root rule\n" ); - return result; - } - - // Inactivate it so we can alter it - hr = cpRecoGrammar->SetRuleState( NULL, NULL, SPRS_INACTIVE ); - if ( FAILED( hr ) ) - { - pfnPrint( "Error: SAPI 5.1 Unable to deactivate grammar rules\n" ); - return result; - } - - // Create the rule set from the words in text - { - CSpDynamicString currentWord; - WCHAR *pos = ( WCHAR * )text; - WCHAR str[ 2 ]; - str[1]= 0; - - while ( *pos ) - { - if ( *pos == L' ' /*|| *pos == L'.' || *pos == L'-'*/ ) - { - // Add word to rule set - if ( currentWord.Length() > 0 ) - { - AddWordRule( cpRecoGrammar, &hStateRoot, &wordRules, currentWord ); - currentWord.Clear(); - } - pos++; - continue; - } - - // Skip anything that's inside a [ xxx ] pair. - if ( *pos == L'[' ) - { - while ( *pos && *pos != L']' ) - { - pos++; - } - - if ( *pos ) - { - pos++; - } - continue; - } - - str[ 0 ] = *pos; - - currentWord.Append( str ); - pos++; - } - - if ( currentWord.Length() > 0 ) - { - AddWordRule( cpRecoGrammar, &hStateRoot, &wordRules, currentWord ); - } - - if ( wordRules.Size() <= 0 ) - { - pfnPrint( "Error: Text %s contained no usable words\n", text ); - return result; - } - - // Build all word to word transitions in the grammar - if ( !BuildRules( cpRecoGrammar, &hStateRoot, &wordRules ) ) - { - pfnPrint( "Error: Rule set for %s could not be generated\n", text ); - return result; - } - } - - // check for recognitions and end of stream event - const ULONGLONG ullInterest = - SPFEI(SPEI_RECOGNITION) | SPFEI(SPEI_END_SR_STREAM) | SPFEI(SPEI_FALSE_RECOGNITION) | - SPFEI(SPEI_PHRASE_START ) | SPFEI(SPEI_HYPOTHESIS ) | SPFEI(SPEI_INTERFERENCE) ; - hr = cpRecoContext->SetInterest( ullInterest, ullInterest ); - if ( FAILED( hr ) ) - { - pfnPrint( "Error: SAPI 5.1 Unable to set interest level\n" ); - return result; - } - // use Win32 events for command-line style application - hr = cpRecoContext->SetNotifyWin32Event(); - if ( FAILED( hr ) ) - { - pfnPrint( "Error: SAPI 5.1 Unable to set win32 notify event\n" ); - return result; - } - // connect wav input to recognizer - // SAPI will negotiate mismatched engine/input audio formats using system audio codecs, so second parameter is not important - use default of TRUE - hr = cpRecognizer->SetInput(cpInputStream, TRUE); - if ( FAILED( hr ) ) - { - pfnPrint( "Error: SAPI 5.1 Unable to associate input stream\n" ); - return result; - } - - // Activate the CFG ( rather than using dictation ) - hr = cpRecoGrammar->SetRuleState( NULL, NULL, SPRS_ACTIVE ); - if ( FAILED( hr ) ) - { - switch ( hr ) - { - case E_INVALIDARG: - pfnPrint( "pszName is invalid or bad. Alternatively, pReserved is non-NULL\n" ); - break; - case SP_STREAM_UNINITIALIZED: - pfnPrint( "ISpRecognizer::SetInput has not been called with the InProc recognizer\n" ); - break; - case SPERR_UNINITIALIZED: - pfnPrint( "The object has not been properly initialized.\n"); - break; - case SPERR_UNSUPPORTED_FORMAT: - pfnPrint( "Audio format is bad or is not recognized. Alternatively, the device driver may be busy by another application and cannot be accessed.\n" ); - break; - case SPERR_NOT_TOPLEVEL_RULE: - pfnPrint( "The rule pszName exists, but is not a top-level rule.\n" ); - break; - default: - pfnPrint( "Unknown error\n" ); - break; - } - pfnPrint( "Error: SAPI 5.1 Unable to activate rule set\n" ); - return result; - } - - // while events occur, continue processing - // timeout should be greater than the audio stream length, or a reasonable amount of time expected to pass before no more recognitions are expected in an audio stream - BOOL fEndStreamReached = FALSE; - while (!fEndStreamReached && S_OK == cpRecoContext->WaitForNotifyEvent( SR_WAVTIMEOUT )) - { - CSpEvent spEvent; - // pull all queued events from the reco context's event queue - - while (!fEndStreamReached && S_OK == spEvent.GetFrom(cpRecoContext)) - { - // Check event type - switch (spEvent.eEventId) - { - case SPEI_INTERFERENCE: - { - SPINTERFERENCE interference = spEvent.Interference(); - - switch ( interference ) - { - case SPINTERFERENCE_NONE: - pfnPrint( "[ I None ]\r\n" ); - break; - case SPINTERFERENCE_NOISE: - pfnPrint( "[ I Noise ]\r\n" ); - break; - case SPINTERFERENCE_NOSIGNAL: - pfnPrint( "[ I No Signal ]\r\n" ); - break; - case SPINTERFERENCE_TOOLOUD: - pfnPrint( "[ I Too Loud ]\r\n" ); - break; - case SPINTERFERENCE_TOOQUIET: - pfnPrint( "[ I Too Quiet ]\r\n" ); - break; - case SPINTERFERENCE_TOOFAST: - pfnPrint( "[ I Too Fast ]\r\n" ); - break; - case SPINTERFERENCE_TOOSLOW: - pfnPrint( "[ I Too Slow ]\r\n" ); - break; - default: - break; - } - } - break; - case SPEI_PHRASE_START: - pfnPrint( "Phrase Start\r\n" ); - sentence.MarkNewPhraseBase(); - break; - - case SPEI_HYPOTHESIS: - case SPEI_RECOGNITION: - case SPEI_FALSE_RECOGNITION: - { - CComPtr cpResult; - cpResult = spEvent.RecoResult(); - - CSpDynamicString dstrText; - if (spEvent.eEventId == SPEI_FALSE_RECOGNITION) - { - dstrText = L"(Unrecognized)"; - - result = SR_RESULT_FAILED; - - // It's possible that the failed recog might have more words, so see if that's the case - EnumeratePhonemes( cpPhoneConv, cpResult, sentence ); - } - else - { - // Hypothesis or recognition success - cpResult->GetText( (ULONG)SP_GETWHOLEPHRASE, (ULONG)SP_GETWHOLEPHRASE, TRUE, &dstrText, NULL); - - EnumeratePhonemes( cpPhoneConv, cpResult, sentence ); - - if ( spEvent.eEventId == SPEI_RECOGNITION ) - { - result = SR_RESULT_SUCCESS; - } - - pfnPrint( va( "%s%s\r\n", spEvent.eEventId == SPEI_HYPOTHESIS ? "[ Hypothesis ] " : "", dstrText.CopyToChar() ) ); - } - - cpResult.Release(); - } - break; - // end of the wav file was reached by the speech recognition engine - case SPEI_END_SR_STREAM: - fEndStreamReached = TRUE; - break; - } - - // clear any event data/object references - spEvent.Clear(); - }// END event pulling loop - break on empty event queue OR end stream - }// END event polling loop - break on event timeout OR end stream - - // Deactivate rule - hr = cpRecoGrammar->SetRuleState( NULL, NULL, SPRS_INACTIVE ); - if ( FAILED( hr ) ) - { - pfnPrint( "Error: SAPI 5.1 Unable to deactivate rule set\n" ); - return result; - } - - // close the input stream, since we're done with it - // NOTE: smart pointer will call SpStream's destructor, and consequently ::Close, but code may want to check for errors on ::Close operation - hr = cpInputStream->Close(); - if ( FAILED( hr ) ) - { - pfnPrint( "Error: SAPI 5.1 Unable to close input stream\n" ); - return result; - } - - return result; -} - -//----------------------------------------------------------------------------- -// Purpose: HACK HACK: We have to delete the RecoContext key or sapi starts to train -// itself on each iteration which was causing some problems. -// Input : hKey - -//----------------------------------------------------------------------------- -void RecursiveRegDelKey(HKEY hKey) -{ - char keyname[256]={0}; - DWORD namesize=256; - - //base case: no subkeys when RegEnumKeyEx returns error on index 0 - LONG lResult=RegEnumKeyEx(hKey,0,keyname,&namesize,NULL,NULL,NULL,NULL); - if (lResult!=ERROR_SUCCESS) - { - return; - } - - do - { - HKEY subkey; - LONG lResult2; - LONG lDelResult; - lResult2=RegOpenKeyEx(hKey,keyname,0,KEY_ALL_ACCESS,&subkey); - - if (lResult2==ERROR_SUCCESS) - { - RecursiveRegDelKey(subkey); - - RegCloseKey(subkey); - lDelResult=RegDeleteKey(hKey,keyname); - namesize=256; - //use 0 in the next function call because when you delete one, the rest shift down! - lResult=RegEnumKeyEx(hKey,0,keyname,&namesize,NULL,NULL,NULL,NULL); - } - - else - { - break; - } - - } while (lResult!=ERROR_NO_MORE_ITEMS); -} - -bool IsUseable( CWordTag *word ) -{ - if ( word->m_uiStartByte || word->m_uiEndByte ) - return true; - - return false; -} - -int FindLastUsableWord( CSentence& outwords ) -{ - int numwords = outwords.m_Words.Size(); - if ( numwords < 1 ) - { - Assert( 0 ); - return -1; - } - - for ( int i = numwords-1; i >= 0; i-- ) - { - CWordTag *check = outwords.m_Words[ i ]; - if ( IsUseable( check ) ) - { - return i; - } - } - - return -1; -} - - -int FindFirstUsableWord( CSentence& outwords ) -{ - int numwords = outwords.m_Words.Size(); - if ( numwords < 1 ) - { - Assert( 0 ); - return -1; - } - - for ( int i = 0; i < numwords; i++ ) - { - CWordTag *check = outwords.m_Words[ i ]; - if ( IsUseable( check ) ) - { - return i; - } - } - - return -1; -} - -//----------------------------------------------------------------------------- -// Purpose: Counts words which have either a valid start or end byte -// Input : *outwords - -// Output : int -//----------------------------------------------------------------------------- -int CountUsableWords( CSentence& outwords ) -{ - int count = 0; - int numwords = outwords.m_Words.Size(); - // Nothing to do - if ( numwords <= 0 ) - return count; - - for ( int i = 0; i < numwords; i++ ) - { - CWordTag *word = outwords.m_Words[ i ]; - if ( !IsUseable( word ) ) - continue; - - count++; - } - - return count; -} - - -//----------------------------------------------------------------------------- -// Purpose: Counts words which have either a valid start or end byte -// Input : *outwords - -// Output : int -//----------------------------------------------------------------------------- -int CountUnuseableWords( CSentence& outwords ) -{ - int count = 0; - int numwords = outwords.m_Words.Size(); - // Nothing to do - if ( numwords <= 0 ) - return count; - - for ( int i = 0; i < numwords; i++ ) - { - CWordTag *word = outwords.m_Words[ i ]; - if ( IsUseable( word ) ) - continue; - - count++; - } - - return count; -} - -// Keeps same relative spacing, but rebases list -void RepartitionPhonemes( CWordTag *word, unsigned int oldStart, unsigned int oldEnd ) -{ - // Repartition phonemes based on old range - float oldRange = ( float )( oldEnd - oldStart ); - float newRange = ( float )( word->m_uiEndByte - word->m_uiStartByte ); - - for ( int i = 0; i < word->m_Phonemes.Size(); i++ ) - { - CPhonemeTag *tag = word->m_Phonemes[ i ]; - Assert( tag ); - - float frac1 = 0.0f, frac2 = 0.0f; - float delta1, delta2; - - delta1 = ( float ) ( tag->m_uiStartByte - oldStart ); - delta2 = ( float ) ( tag->m_uiEndByte - oldStart ); - if ( oldRange > 0.0f ) - { - frac1 = delta1 / oldRange; - frac2 = delta2 / oldRange; - } - - tag->m_uiStartByte = word->m_uiStartByte + ( unsigned int ) ( frac1 * newRange ); - tag->m_uiEndByte = word->m_uiStartByte + ( unsigned int ) ( frac2 * newRange ); - } -} - -void PartitionWords( CSentence& outwords, int start, int end, int sampleStart, int sampleEnd ) -{ - int wordCount = end - start + 1; - Assert( wordCount >= 1 ); - int stepSize = ( sampleEnd - sampleStart ) / wordCount; - - int currentStart = sampleStart; - - for ( int i = start; i <= end; i++ ) - { - CWordTag *word = outwords.m_Words[ i ]; - Assert( word ); - - unsigned int oldStart = word->m_uiStartByte; - unsigned int oldEnd = word->m_uiEndByte; - - word->m_uiStartByte = currentStart; - word->m_uiEndByte = currentStart + stepSize; - - RepartitionPhonemes( word, oldStart, oldEnd ); - - currentStart += stepSize; - } -} - -void MergeWords( CWordTag *w1, CWordTag *w2 ) -{ - unsigned int start, end; - - start = min( w1->m_uiStartByte, w2->m_uiStartByte ); - end = max( w1->m_uiEndByte, w2->m_uiEndByte ); - - unsigned int mid = ( start + end ) / 2; - - unsigned int oldw1start, oldw2start, oldw1end, oldw2end; - - oldw1start = w1->m_uiStartByte; - oldw2start = w2->m_uiStartByte; - oldw1end = w1->m_uiEndByte; - oldw2end = w2->m_uiEndByte; - - w1->m_uiStartByte = start; - w1->m_uiEndByte = mid; - w2->m_uiStartByte = mid; - w2->m_uiEndByte = end; - - RepartitionPhonemes( w1, oldw1start, oldw1end ); - RepartitionPhonemes( w2, oldw2start, oldw2end ); -} - -void FixupZeroLengthWords( CSentence& outwords ) -{ - while ( 1 ) - { - int i; - for ( i = 0 ; i < outwords.m_Words.Size() - 1; i++ ) - { - CWordTag *current, *next; - - current = outwords.m_Words[ i ]; - next = outwords.m_Words[ i + 1 ]; - - if ( current->m_uiEndByte - current->m_uiStartByte <= 0 ) - { - MergeWords( current, next ); - break; - } - - if ( next->m_uiEndByte - next->m_uiStartByte <= 0 ) - { - MergeWords( current, next ); - break; - } - } - - if ( i >= outwords.m_Words.Size() - 1 ) - { - break; - } - } -} - -void ComputeMissingByteSpans( int numsamples, CSentence& outwords ) -{ - int numwords = outwords.m_Words.Size(); - // Nothing to do - if ( numwords <= 0 ) - return; - - int interationcount = 1; - - while( 1 ) - { - Log( "\nCompute %i\n", interationcount++ ); - LogWords( outwords ); - - int wordNumber; - - // Done! - if ( !CountUnuseableWords( outwords ) ) - { - FixupZeroLengthWords( outwords ); - break; - } - - if ( !CountUsableWords( outwords ) ) - { - // Evenly space words across full sample time - PartitionWords( outwords, 0, numwords - 1, 0, numsamples ); - break; - } - - wordNumber = FindFirstUsableWord( outwords ); - // Not the first word - if ( wordNumber > 0 ) - { - // Repartition all of the unusables and the first one starting at zero over the range - CWordTag *firstUsable = outwords.m_Words[ wordNumber ]; - Assert( firstUsable ); - - if ( firstUsable->m_uiStartByte != 0 ) - { - PartitionWords( outwords, 0, wordNumber - 1, 0, firstUsable->m_uiStartByte ); - } - else - { - PartitionWords( outwords, 0, wordNumber, 0, firstUsable->m_uiEndByte ); - } - - // Start over - continue; - } - - wordNumber = FindLastUsableWord( outwords ); - // Not the last word - if ( wordNumber >= 0 && wordNumber < numwords - 1 ) - { - // Repartition all of the unusables and the first one starting at zero over the range - CWordTag *lastUsable = outwords.m_Words[ wordNumber ]; - Assert( lastUsable ); - - if ( lastUsable->m_uiEndByte != (unsigned int)numsamples ) - { - PartitionWords( outwords, wordNumber + 1, numwords-1, lastUsable->m_uiEndByte, numsamples ); - } - else - { - PartitionWords( outwords, wordNumber, numwords-1, lastUsable->m_uiStartByte, numsamples ); - } - - // Start over - continue; - } - - // If we get here it means that the start and end of the list are okay and we just have to - // iterate across the list and fix things in the middle - int startByte = 0; - int endByte = 0; - for ( int i = 0; i < numwords ; i++ ) - { - CWordTag *word = outwords.m_Words[ i ]; - if ( IsUseable( word ) ) - { - startByte = word->m_uiEndByte; - continue; - } - - // Found the start of a chain of 1 or more unusable words - // Find the startbyte of the next usable word and count how many words we check - int wordCount = 1; - for ( int j = i + 1; j < numwords; j++ ) - { - CWordTag *next = outwords.m_Words[ j ]; - if ( IsUseable( next ) ) - { - endByte = next->m_uiStartByte; - break; - } - - wordCount++; - } - - // Now partition words across the gap and go to start again - PartitionWords( outwords, i, i + wordCount - 1, startByte, endByte ); - break; - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Given a wavfile and a list of inwords, determines the word/phonene -// sample counts for the sentce -// Input : *wavfile - -// *inwords - -// *outphonemes{ text.Clear( - -// Output : SR_RESULT -//----------------------------------------------------------------------------- -static SR_RESULT SAPI_ExtractPhonemes( - const char *wavfile, - int numsamples, - void (*pfnPrint)( const char *fmt, ... ), - CSentence& inwords, - CSentence& outwords ) -{ - LogReset(); - - USES_CONVERSION; - - CSpDynamicString text; - text.Clear(); - - HKEY hkwipe; - LONG lResult = RegOpenKeyEx( HKEY_CURRENT_USER, "Software\\Microsoft\\Speech\\RecoProfiles", 0, KEY_ALL_ACCESS, &hkwipe ); - if ( lResult == ERROR_SUCCESS ) - { - RecursiveRegDelKey( hkwipe ); - RegCloseKey( hkwipe ); - } - - if ( strlen( inwords.GetText() ) <= 0 ) - { - inwords.SetTextFromWords(); - } - - // Construct a string from the inwords array - text.Append( T2W( inwords.GetText() ) ); - - // Assume failure - SR_RESULT result = SR_RESULT_ERROR; - - if ( text.Length() > 0 ) - { - CSentence sentence; - - pfnPrint( "Processing...\r\n" ); - - // Give it a try - result = ExtractPhonemes( wavfile, text, sentence, pfnPrint ); - - pfnPrint( "Finished.\r\n" ); - // PrintWordsAndPhonemes( sentence, pfnPrint ); - - // Copy results to outputs - outwords.Reset(); - - outwords.SetText( inwords.GetText() ); - - Log( "Starting\n" ); - LogWords( inwords ); - - if ( SR_RESULT_ERROR != result ) - { - int i; - - Log( "Hypothesized\n" ); - LogWords( sentence ); - - for( i = 0 ; i < sentence.m_Words.Size(); i++ ) - { - CWordTag *tag = sentence.m_Words[ i ]; - if ( tag ) - { - // Skip '...' tag - if ( stricmp( tag->GetWord(), "..." ) ) - { - CWordTag *newTag = new CWordTag( *tag ); - - outwords.m_Words.AddToTail( newTag ); - } - } - } - - // Now insert unrecognized/skipped words from original list - // - int frompos = 0, topos = 0; - - while( 1 ) - { - // End of source list - if ( frompos >= inwords.m_Words.Size() ) - break; - - const CWordTag *fromTag = inwords.m_Words[ frompos ]; - - // Reached end of destination list, just copy words over from from source list until - // we run out of source words - if ( topos >= outwords.m_Words.Size() ) - { - // Just copy words over - CWordTag *newWord = new CWordTag( *fromTag ); - - // Remove phonemes - while ( newWord->m_Phonemes.Size() > 0 ) - { - CPhonemeTag *kill = newWord->m_Phonemes[ 0 ]; - newWord->m_Phonemes.Remove( 0 ); - delete kill; - } - - outwords.m_Words.AddToTail( newWord ); - frompos++; - topos++; - continue; - } - - // Destination word - const CWordTag *toTag = outwords.m_Words[ topos ]; - - // Words match, just skip ahead - if ( !stricmp( fromTag->GetWord(), toTag->GetWord() ) ) - { - frompos++; - topos++; - continue; - } - - // The only case we handle is that something in the source wasn't in the destination - - // Find the next source word that appears in the destination - int skipAhead = frompos + 1; - bool found = false; - while ( skipAhead < inwords.m_Words.Size() ) - { - const CWordTag *sourceWord = inwords.m_Words[ skipAhead ]; - if ( !stricmp( sourceWord->GetWord(), toTag->GetWord() ) ) - { - found = true; - break; - } - - skipAhead++; - } - - // Uh oh destination has words that are not in source, just skip to next destination word? - if ( !found ) - { - topos++; - } - else - { - // Copy words from from source list into destination - // - int skipCount = skipAhead - frompos; - - while ( --skipCount>= 0 ) - { - const CWordTag *sourceWord = inwords.m_Words[ frompos++ ]; - CWordTag *newWord = new CWordTag( *sourceWord ); - - // Remove phonemes - while ( newWord->m_Phonemes.Size() > 0 ) - { - CPhonemeTag *kill = newWord->m_Phonemes[ 0 ]; - newWord->m_Phonemes.Remove( 0 ); - delete kill; - } - - outwords.m_Words.InsertBefore( topos, newWord ); - topos++; - } - - frompos++; - topos++; - } - } - - Log( "\nDone simple check\n" ); - - LogWords( outwords ); - LogPhonemes( outwords ); - - ComputeMissingByteSpans( numsamples, outwords ); - - Log( "\nFinal check\n" ); - - LogWords( outwords ); - LogPhonemes( outwords ); - } - } - else - { - pfnPrint( "Input sentence is empty!\n" ); - } - - // Return results - return result; -} - - -//----------------------------------------------------------------------------- -// Purpose: Expose the interface -//----------------------------------------------------------------------------- -class CPhonemeExtractorSAPI : public IPhonemeExtractor -{ -public: - virtual PE_APITYPE GetAPIType() const - { - return SPEECH_API_SAPI; - } - - // Used for menus, etc - virtual char const *GetName() const - { - return "MS SAPI 5.1"; - } - - SR_RESULT Extract( - const char *wavfile, - int numsamples, - void (*pfnPrint)( const char *fmt, ... ), - CSentence& inwords, - CSentence& outwords ) - { - return SAPI_ExtractPhonemes( wavfile, numsamples, pfnPrint, inwords, outwords ); - } -}; - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// extracephonemes.cpp : Defines the entry point for the console application. +// +#define PROTECTED_THINGS_DISABLE + +#include "tier0/wchartypes.h" +#include +#include +#include +#include "sphelper.h" +#include "spddkhlp.h" +// ATL Header Files +#include +// Face poser and util includes +#include "utlvector.h" +#include "phonemeextractor/PhonemeExtractor.h" +#include "PhonemeConverter.h" +#include "sentence.h" +#include "tier0/dbg.h" +#include "tier0/icommandline.h" +#include "filesystem.h" + +// Extract phoneme grammar id +#define EP_GRAM_ID 101 +// First rule of dynamic sentence rule set +#define DYN_SENTENCERULE 102 +// # of milliseconds to allow for processing before timeout +#define SR_WAVTIMEOUT 4000 +// Weight tag for rule to rule word/rule transitions +#define CONFIDENCE_WEIGHT 0.0f + +//#define LOGGING 1 +#define LOGFILE "c:\\fp.log" + +void LogReset( void ) +{ +#if LOGGING + FILE *fp = fopen( LOGFILE, "w" ); + if ( fp ) + fclose( fp ); +#endif +} + +char *va( const char *fmt, ... ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *words - +//----------------------------------------------------------------------------- +void LogWords( CSentence& sentence ) +{ + Log( "Wordcount == %i\n", sentence.m_Words.Size() ); + + for ( int i = 0; i < sentence.m_Words.Size(); i++ ) + { + const CWordTag *w = sentence.m_Words[ i ]; + Log( "Word %s %u to %u\n", w->GetWord(), w->m_uiStartByte, w->m_uiEndByte ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *phonemes - +//----------------------------------------------------------------------------- +void LogPhonemes( CSentence& sentence ) +{ + return; + + Log( "Phonemecount == %i\n", sentence.CountPhonemes() ); + + for ( int i = 0; i < sentence.m_Words.Size(); i++ ) + { + const CWordTag *w = sentence.m_Words[ i ]; + + for ( int j = 0; j < w->m_Phonemes.Size(); j++ ) + { + const CPhonemeTag *p = w->m_Phonemes[ j ]; + Log( "Phoneme %s %u to %u\n", p->GetTag(), p->m_uiStartByte, p->m_uiEndByte ); + } + } +} + +#define NANO_CONVERT 10000000.0f; + +//----------------------------------------------------------------------------- +// Purpose: Walk list of words and phonemes and create phoneme tags in CSentence object +// FIXME: Right now, phonemes are assumed to evenly space out across a word. +// Input : *converter - +// result - +// sentence - +//----------------------------------------------------------------------------- +void EnumeratePhonemes( ISpPhoneConverter *converter, const ISpRecoResult* result, CSentence& sentence ) +{ + USES_CONVERSION; + + // Grab access to element container + ISpPhrase *phrase = ( ISpPhrase * )result; + if ( !phrase ) + return; + + SPPHRASE *pElements; + if ( !SUCCEEDED( phrase->GetPhrase( &pElements ) ) ) + return; + + // Only use it if it's better/same size as what we already had on-hand + if ( pElements->Rule.ulCountOfElements > 0 ) + //(unsigned int)( sentence.m_Words.Size() - sentence.GetWordBase() ) ) + { + sentence.ResetToBase(); + + // Walk list of words + for ( ULONG i = 0; i < pElements->Rule.ulCountOfElements; i++ ) + { + unsigned int wordstart, wordend; + + // Get start/end sample index + wordstart = pElements->pElements[i].ulAudioStreamOffset + (unsigned int)pElements->ullAudioStreamPosition; + wordend = wordstart + pElements->pElements[i].ulAudioSizeBytes; + + // Create word tag + CWordTag *w = new CWordTag( W2T( pElements->pElements[i].pszDisplayText ) ); + Assert( w ); + w->m_uiStartByte = wordstart; + w->m_uiEndByte = wordend; + + sentence.AddWordTag( w ); + + // Count # of phonemes in this word + SPPHONEID pstr[ 2 ]; + pstr[ 1 ] = 0; + WCHAR wszPhoneme[ SP_MAX_PRON_LENGTH ]; + + const SPPHONEID *current; + SPPHONEID phoneme; + current = pElements->pElements[i].pszPronunciation; + float total_weight = 0.0f; + while ( 1 ) + { + phoneme = *current++; + if ( !phoneme ) + break; + + pstr[ 0 ] = phoneme; + wszPhoneme[ 0 ] = L'\0'; + + converter->IdToPhone( pstr, wszPhoneme ); + + total_weight += WeightForPhoneme( W2A( wszPhoneme ) ); + } + + current = pElements->pElements[i].pszPronunciation; + + // Decide # of bytes/phoneme weight + float psize = 0; + if ( total_weight ) + { + psize = ( wordend - wordstart ) / total_weight; + } + + int number = 0; + + // Re-walk the phoneme list and create true phoneme tags + float startWeight = 0.0f; + while ( 1 ) + { + phoneme = *current++; + if ( !phoneme ) + break; + + pstr[ 0 ] = phoneme; + wszPhoneme[ 0 ] = L'\0'; + + converter->IdToPhone( pstr, wszPhoneme ); + + CPhonemeTag *p = new CPhonemeTag( W2A( wszPhoneme ) ); + Assert( p ); + + float weight = WeightForPhoneme( W2A( wszPhoneme ) ); + + p->m_uiStartByte = wordstart + (int)( startWeight * psize ); + p->m_uiEndByte = p->m_uiStartByte + (int)( psize * weight ); + + startWeight += weight; + + // Convert to IPA phoneme code + p->SetPhonemeCode( TextToPhoneme( p->GetTag() ) ); + + sentence.AddPhonemeTag( w, p ); + + number++; + } + } + } + + // Free memory + ::CoTaskMemFree(pElements); +} + +//----------------------------------------------------------------------------- +// Purpose: Create rules for each word in the reference sentence +//----------------------------------------------------------------------------- +typedef struct +{ + int ruleId; + SPSTATEHANDLE hRule; + CSpDynamicString word; + char plaintext[ 256 ]; +} WORDRULETYPE; + +//----------------------------------------------------------------------------- +// Purpose: Creates start for word of sentence +// Input : cpRecoGrammar - +// *root - +// *rules - +// word - +//----------------------------------------------------------------------------- +void AddWordRule( ISpRecoGrammar* cpRecoGrammar, SPSTATEHANDLE *root, CUtlVector< WORDRULETYPE > *rules, CSpDynamicString& word ) +{ + USES_CONVERSION; + HRESULT hr; + WORDRULETYPE *newrule; + + int idx = (*rules).AddToTail(); + + newrule = &(*rules)[ idx ]; + + newrule->ruleId = DYN_SENTENCERULE + idx + 1; + newrule->word = word; + + strcpy( newrule->plaintext, W2T( word ) ); + + // Create empty rule + hr = cpRecoGrammar->CreateNewState( *root, &newrule->hRule ); + Assert( !FAILED( hr ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : cpRecoGrammar - +// *from - +// *to - +//----------------------------------------------------------------------------- +void AddWordTransitionRule( ISpRecoGrammar* cpRecoGrammar, WORDRULETYPE *from, WORDRULETYPE *to ) +{ + USES_CONVERSION; + + HRESULT hr; + Assert( from ); + + if ( from && !to ) + { + OutputDebugString( va( "Transition from %s to TERM\r\n", from->plaintext ) ); + } + else + { + OutputDebugString( va( "Transition from %s to %s\r\n", from->plaintext, to->plaintext ) ); + } + + hr = cpRecoGrammar->AddWordTransition( from->hRule, to ? to->hRule : NULL, (WCHAR *)from->word, NULL, SPWT_LEXICAL, CONFIDENCE_WEIGHT, NULL ); + Assert( !FAILED( hr ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : cpRecoGrammar - +// *from - +// *to - +//----------------------------------------------------------------------------- +void AddOptionalTransitionRule( ISpRecoGrammar* cpRecoGrammar, WORDRULETYPE *from, WORDRULETYPE *to ) +{ + USES_CONVERSION; + + HRESULT hr; + Assert( from ); + + if ( from && !to ) + { + OutputDebugString( va( "Opt transition from %s to TERM\r\n", from->plaintext ) ); + } + else + { + OutputDebugString( va( "Opt transition from %s to %s\r\n", from->plaintext, to->plaintext ) ); + } + + hr = cpRecoGrammar->AddWordTransition( from->hRule, to ? to->hRule : NULL, NULL, NULL, SPWT_LEXICAL, CONFIDENCE_WEIGHT, NULL ); + Assert( !FAILED( hr ) ); +} + +#define MAX_WORD_SKIP 1 +//----------------------------------------------------------------------------- +// Purpose: Links together all word rule states into a sentence rule CFG +// Input : singleword - +// cpRecoGrammar - +// *root - +// *rules - +//----------------------------------------------------------------------------- +bool BuildRules( ISpRecoGrammar* cpRecoGrammar, SPSTATEHANDLE *root, CUtlVector< WORDRULETYPE > *rules ) +{ + HRESULT hr; + WORDRULETYPE *rule, *next; + + int numrules = (*rules).Size(); + + rule = &(*rules)[ 0 ]; + + // Add transition + hr = cpRecoGrammar->AddWordTransition( *root, rule->hRule, NULL, NULL, SPWT_LEXICAL, CONFIDENCE_WEIGHT, NULL ); + Assert( !FAILED( hr ) ); + + for ( int i = 0; i < numrules; i++ ) + { + rule = &(*rules)[ i ]; + if ( i < numrules - 1 ) + { + next = &(*rules)[ i + 1 ]; + } + else + { + next = NULL; + } + + AddWordTransitionRule( cpRecoGrammar, rule, next ); + } + + if ( numrules > 1 ) + { + for ( int skip = 1; skip <= min( MAX_WORD_SKIP, numrules ); skip++ ) + { + OutputDebugString( va( "Opt transition from Root to %s\r\n", (*rules)[ 0 ].plaintext ) ); + + hr = cpRecoGrammar->AddWordTransition( *root, (*rules)[ 0 ].hRule, NULL, NULL, SPWT_LEXICAL, CONFIDENCE_WEIGHT, NULL ); + + // Now build rules where you can skip 1 to N intervening words + for ( int i = 1; i < numrules; i++ ) + { + // Start at the beginning? + rule = &(*rules)[ i ]; + if ( i < numrules - skip ) + { + next = &(*rules)[ i + skip ]; + } + else + { + continue; + } + + // Add transition + AddOptionalTransitionRule( cpRecoGrammar, rule, next ); + } + + // Go from final rule to end point + AddOptionalTransitionRule( cpRecoGrammar, rule, NULL ); + } + } + + // Store it + hr = cpRecoGrammar->Commit(NULL); + if ( FAILED( hr ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Debugging, prints alternate list if one is created +// Input : cpResult - +// (*pfnPrint - +//----------------------------------------------------------------------------- +void PrintAlternates( ISpRecoResult* cpResult, void (*pfnPrint)( const char *fmt, ... ) ) +{ + ISpPhraseAlt *rgPhraseAlt[ 32 ]; + memset( rgPhraseAlt, 0, sizeof( rgPhraseAlt ) ); + + ULONG ulCount; + + ISpPhrase *phrase = ( ISpPhrase * )cpResult; + if ( phrase ) + { + SPPHRASE *pElements; + if ( SUCCEEDED( phrase->GetPhrase( &pElements ) ) ) + { + if ( pElements->Rule.ulCountOfElements > 0 ) + { + HRESULT hr = cpResult->GetAlternates( + pElements->Rule.ulFirstElement, + pElements->Rule.ulCountOfElements, + 32, + rgPhraseAlt, + &ulCount); + + Assert( !FAILED( hr ) ); + + for ( ULONG r = 0 ; r < ulCount; r++ ) + { + CSpDynamicString dstrText; + hr = rgPhraseAlt[ r ]->GetText( (ULONG)SP_GETWHOLEPHRASE, (ULONG)SP_GETWHOLEPHRASE, TRUE, &dstrText, NULL); + Assert( !FAILED( hr ) ); + + pfnPrint( "[ ALT ]" ); + pfnPrint( dstrText.CopyToChar() ); + pfnPrint( "\r\n" ); + } + } + } + + } + + for ( int i = 0; i < 32; i++ ) + { + if ( rgPhraseAlt[ i ] ) + { + rgPhraseAlt[ i ]->Release(); + rgPhraseAlt[ i ] = NULL; + } + } +} + +void PrintWordsAndPhonemes( CSentence& sentence, void (*pfnPrint)( const char *fmt, ... ) ) +{ + char sz[ 256 ]; + int i; + + pfnPrint( "WORDS\r\n\r\n" ); + + for ( i = 0 ; i < sentence.m_Words.Size(); i++ ) + { + CWordTag *word = sentence.m_Words[ i ]; + if ( !word ) + continue; + + sprintf( sz, "<%u - %u> %s\r\n", + word->m_uiStartByte, word->m_uiEndByte, word->GetWord() ); + + pfnPrint( sz ); + + for ( int j = 0 ; j < word->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *phoneme = word->m_Phonemes[ j ]; + if ( !phoneme ) + continue; + + sprintf( sz, " <%u - %u> %s\r\n", + phoneme->m_uiStartByte, phoneme->m_uiEndByte, phoneme->GetTag() ); + + pfnPrint( sz ); + } + } + + pfnPrint( "\r\n" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Given a wave file and a string of words "text", creates a CFG from the +// sentence and stores the resulting words/phonemes in CSentence +// Input : *wavname - +// text - +// sentence - +// (*pfnPrint - +// Output : SR_RESULT +//----------------------------------------------------------------------------- +SR_RESULT ExtractPhonemes( const char *wavname, CSpDynamicString& text, CSentence& sentence, void (*pfnPrint)( const char *fmt, ...) ) +{ + // Assume failure + SR_RESULT result = SR_RESULT_ERROR; + + if ( text.Length() <= 0 ) + { + pfnPrint( "Error: no rule / text specified\n" ); + return result; + } + + USES_CONVERSION; + HRESULT hr; + + CUtlVector < WORDRULETYPE > wordRules; + + CComPtr cpInputStream; + CComPtr cpRecognizer; + CComPtr cpRecoContext; + CComPtr cpRecoGrammar; + CComPtr cpPhoneConv; + + // Create basic SAPI stream object + // NOTE: The helper SpBindToFile can be used to perform the following operations + hr = cpInputStream.CoCreateInstance(CLSID_SpStream); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Stream object not installed?\n" ); + return result; + } + + CSpStreamFormat sInputFormat; + + // setup stream object with wav file MY_WAVE_AUDIO_FILENAME + // for read-only access, since it will only be access by the SR engine + hr = cpInputStream->BindToFile( + T2W(wavname), + SPFM_OPEN_READONLY, + NULL, + sInputFormat.WaveFormatExPtr(), + SPFEI_ALL_EVENTS ); + + if ( FAILED( hr ) ) + { + pfnPrint( "Error: couldn't open wav file %s\n", wavname ); + return result; + } + + // Create in-process speech recognition engine + hr = cpRecognizer.CoCreateInstance(CLSID_SpInprocRecognizer); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 In process recognizer object not installed?\n" ); + return result; + } + + // Create recognition context to receive events + hr = cpRecognizer->CreateRecoContext(&cpRecoContext); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to create recognizer context\n" ); + return result; + } + + // Create a grammar + hr = cpRecoContext->CreateGrammar( EP_GRAM_ID, &cpRecoGrammar ); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to create recognizer grammar\n" ); + return result; + } + + LANGID englishID = 0x409; // 1033 decimal + + bool userSpecified = false; + LANGID langID = SpGetUserDefaultUILanguage(); + + // Allow commandline override + if ( CommandLine()->FindParm( "-languageid" ) != 0 ) + { + userSpecified = true; + langID = CommandLine()->ParmValue( "-languageid", langID ); + } + + // Create a phoneme converter ( so we can convert to IPA codes ) + hr = SpCreatePhoneConverter( langID, NULL, NULL, &cpPhoneConv ); + if ( FAILED( hr ) ) + { + if ( langID != englishID ) + { + if ( userSpecified ) + { + pfnPrint( "Warning: SAPI 5.1 Unable to create phoneme converter for command line override -languageid %i\n", langID ); + } + else + { + pfnPrint( "Warning: SAPI 5.1 Unable to create phoneme converter for default UI language %i\n",langID ); + } + + // Try english!!! + langID = englishID; + hr = SpCreatePhoneConverter( langID, NULL, NULL, &cpPhoneConv ); + } + + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to create phoneme converter for English language id %i\n", langID ); + return result; + } + else + { + pfnPrint( "Note: SAPI 5.1 Falling back to use english -languageid %i\n", langID ); + } + } + else if ( userSpecified ) + { + pfnPrint( "Note: SAPI 5.1 Using user specified -languageid %i\n",langID ); + } + + SPSTATEHANDLE hStateRoot; + // create/re-create Root level rule of grammar + hr = cpRecoGrammar->GetRule(L"Root", 0, SPRAF_TopLevel | SPRAF_Active, TRUE, &hStateRoot); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to create root rule\n" ); + return result; + } + + // Inactivate it so we can alter it + hr = cpRecoGrammar->SetRuleState( NULL, NULL, SPRS_INACTIVE ); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to deactivate grammar rules\n" ); + return result; + } + + // Create the rule set from the words in text + { + CSpDynamicString currentWord; + WCHAR *pos = ( WCHAR * )text; + WCHAR str[ 2 ]; + str[1]= 0; + + while ( *pos ) + { + if ( *pos == L' ' /*|| *pos == L'.' || *pos == L'-'*/ ) + { + // Add word to rule set + if ( currentWord.Length() > 0 ) + { + AddWordRule( cpRecoGrammar, &hStateRoot, &wordRules, currentWord ); + currentWord.Clear(); + } + pos++; + continue; + } + + // Skip anything that's inside a [ xxx ] pair. + if ( *pos == L'[' ) + { + while ( *pos && *pos != L']' ) + { + pos++; + } + + if ( *pos ) + { + pos++; + } + continue; + } + + str[ 0 ] = *pos; + + currentWord.Append( str ); + pos++; + } + + if ( currentWord.Length() > 0 ) + { + AddWordRule( cpRecoGrammar, &hStateRoot, &wordRules, currentWord ); + } + + if ( wordRules.Size() <= 0 ) + { + pfnPrint( "Error: Text %s contained no usable words\n", text ); + return result; + } + + // Build all word to word transitions in the grammar + if ( !BuildRules( cpRecoGrammar, &hStateRoot, &wordRules ) ) + { + pfnPrint( "Error: Rule set for %s could not be generated\n", text ); + return result; + } + } + + // check for recognitions and end of stream event + const ULONGLONG ullInterest = + SPFEI(SPEI_RECOGNITION) | SPFEI(SPEI_END_SR_STREAM) | SPFEI(SPEI_FALSE_RECOGNITION) | + SPFEI(SPEI_PHRASE_START ) | SPFEI(SPEI_HYPOTHESIS ) | SPFEI(SPEI_INTERFERENCE) ; + hr = cpRecoContext->SetInterest( ullInterest, ullInterest ); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to set interest level\n" ); + return result; + } + // use Win32 events for command-line style application + hr = cpRecoContext->SetNotifyWin32Event(); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to set win32 notify event\n" ); + return result; + } + // connect wav input to recognizer + // SAPI will negotiate mismatched engine/input audio formats using system audio codecs, so second parameter is not important - use default of TRUE + hr = cpRecognizer->SetInput(cpInputStream, TRUE); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to associate input stream\n" ); + return result; + } + + // Activate the CFG ( rather than using dictation ) + hr = cpRecoGrammar->SetRuleState( NULL, NULL, SPRS_ACTIVE ); + if ( FAILED( hr ) ) + { + switch ( hr ) + { + case E_INVALIDARG: + pfnPrint( "pszName is invalid or bad. Alternatively, pReserved is non-NULL\n" ); + break; + case SP_STREAM_UNINITIALIZED: + pfnPrint( "ISpRecognizer::SetInput has not been called with the InProc recognizer\n" ); + break; + case SPERR_UNINITIALIZED: + pfnPrint( "The object has not been properly initialized.\n"); + break; + case SPERR_UNSUPPORTED_FORMAT: + pfnPrint( "Audio format is bad or is not recognized. Alternatively, the device driver may be busy by another application and cannot be accessed.\n" ); + break; + case SPERR_NOT_TOPLEVEL_RULE: + pfnPrint( "The rule pszName exists, but is not a top-level rule.\n" ); + break; + default: + pfnPrint( "Unknown error\n" ); + break; + } + pfnPrint( "Error: SAPI 5.1 Unable to activate rule set\n" ); + return result; + } + + // while events occur, continue processing + // timeout should be greater than the audio stream length, or a reasonable amount of time expected to pass before no more recognitions are expected in an audio stream + BOOL fEndStreamReached = FALSE; + while (!fEndStreamReached && S_OK == cpRecoContext->WaitForNotifyEvent( SR_WAVTIMEOUT )) + { + CSpEvent spEvent; + // pull all queued events from the reco context's event queue + + while (!fEndStreamReached && S_OK == spEvent.GetFrom(cpRecoContext)) + { + // Check event type + switch (spEvent.eEventId) + { + case SPEI_INTERFERENCE: + { + SPINTERFERENCE interference = spEvent.Interference(); + + switch ( interference ) + { + case SPINTERFERENCE_NONE: + pfnPrint( "[ I None ]\r\n" ); + break; + case SPINTERFERENCE_NOISE: + pfnPrint( "[ I Noise ]\r\n" ); + break; + case SPINTERFERENCE_NOSIGNAL: + pfnPrint( "[ I No Signal ]\r\n" ); + break; + case SPINTERFERENCE_TOOLOUD: + pfnPrint( "[ I Too Loud ]\r\n" ); + break; + case SPINTERFERENCE_TOOQUIET: + pfnPrint( "[ I Too Quiet ]\r\n" ); + break; + case SPINTERFERENCE_TOOFAST: + pfnPrint( "[ I Too Fast ]\r\n" ); + break; + case SPINTERFERENCE_TOOSLOW: + pfnPrint( "[ I Too Slow ]\r\n" ); + break; + default: + break; + } + } + break; + case SPEI_PHRASE_START: + pfnPrint( "Phrase Start\r\n" ); + sentence.MarkNewPhraseBase(); + break; + + case SPEI_HYPOTHESIS: + case SPEI_RECOGNITION: + case SPEI_FALSE_RECOGNITION: + { + CComPtr cpResult; + cpResult = spEvent.RecoResult(); + + CSpDynamicString dstrText; + if (spEvent.eEventId == SPEI_FALSE_RECOGNITION) + { + dstrText = L"(Unrecognized)"; + + result = SR_RESULT_FAILED; + + // It's possible that the failed recog might have more words, so see if that's the case + EnumeratePhonemes( cpPhoneConv, cpResult, sentence ); + } + else + { + // Hypothesis or recognition success + cpResult->GetText( (ULONG)SP_GETWHOLEPHRASE, (ULONG)SP_GETWHOLEPHRASE, TRUE, &dstrText, NULL); + + EnumeratePhonemes( cpPhoneConv, cpResult, sentence ); + + if ( spEvent.eEventId == SPEI_RECOGNITION ) + { + result = SR_RESULT_SUCCESS; + } + + pfnPrint( va( "%s%s\r\n", spEvent.eEventId == SPEI_HYPOTHESIS ? "[ Hypothesis ] " : "", dstrText.CopyToChar() ) ); + } + + cpResult.Release(); + } + break; + // end of the wav file was reached by the speech recognition engine + case SPEI_END_SR_STREAM: + fEndStreamReached = TRUE; + break; + } + + // clear any event data/object references + spEvent.Clear(); + }// END event pulling loop - break on empty event queue OR end stream + }// END event polling loop - break on event timeout OR end stream + + // Deactivate rule + hr = cpRecoGrammar->SetRuleState( NULL, NULL, SPRS_INACTIVE ); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to deactivate rule set\n" ); + return result; + } + + // close the input stream, since we're done with it + // NOTE: smart pointer will call SpStream's destructor, and consequently ::Close, but code may want to check for errors on ::Close operation + hr = cpInputStream->Close(); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to close input stream\n" ); + return result; + } + + return result; +} + +//----------------------------------------------------------------------------- +// Purpose: HACK HACK: We have to delete the RecoContext key or sapi starts to train +// itself on each iteration which was causing some problems. +// Input : hKey - +//----------------------------------------------------------------------------- +void RecursiveRegDelKey(HKEY hKey) +{ + char keyname[256]={0}; + DWORD namesize=256; + + //base case: no subkeys when RegEnumKeyEx returns error on index 0 + LONG lResult=RegEnumKeyEx(hKey,0,keyname,&namesize,NULL,NULL,NULL,NULL); + if (lResult!=ERROR_SUCCESS) + { + return; + } + + do + { + HKEY subkey; + LONG lResult2; + LONG lDelResult; + lResult2=RegOpenKeyEx(hKey,keyname,0,KEY_ALL_ACCESS,&subkey); + + if (lResult2==ERROR_SUCCESS) + { + RecursiveRegDelKey(subkey); + + RegCloseKey(subkey); + lDelResult=RegDeleteKey(hKey,keyname); + namesize=256; + //use 0 in the next function call because when you delete one, the rest shift down! + lResult=RegEnumKeyEx(hKey,0,keyname,&namesize,NULL,NULL,NULL,NULL); + } + + else + { + break; + } + + } while (lResult!=ERROR_NO_MORE_ITEMS); +} + +bool IsUseable( CWordTag *word ) +{ + if ( word->m_uiStartByte || word->m_uiEndByte ) + return true; + + return false; +} + +int FindLastUsableWord( CSentence& outwords ) +{ + int numwords = outwords.m_Words.Size(); + if ( numwords < 1 ) + { + Assert( 0 ); + return -1; + } + + for ( int i = numwords-1; i >= 0; i-- ) + { + CWordTag *check = outwords.m_Words[ i ]; + if ( IsUseable( check ) ) + { + return i; + } + } + + return -1; +} + + +int FindFirstUsableWord( CSentence& outwords ) +{ + int numwords = outwords.m_Words.Size(); + if ( numwords < 1 ) + { + Assert( 0 ); + return -1; + } + + for ( int i = 0; i < numwords; i++ ) + { + CWordTag *check = outwords.m_Words[ i ]; + if ( IsUseable( check ) ) + { + return i; + } + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Counts words which have either a valid start or end byte +// Input : *outwords - +// Output : int +//----------------------------------------------------------------------------- +int CountUsableWords( CSentence& outwords ) +{ + int count = 0; + int numwords = outwords.m_Words.Size(); + // Nothing to do + if ( numwords <= 0 ) + return count; + + for ( int i = 0; i < numwords; i++ ) + { + CWordTag *word = outwords.m_Words[ i ]; + if ( !IsUseable( word ) ) + continue; + + count++; + } + + return count; +} + + +//----------------------------------------------------------------------------- +// Purpose: Counts words which have either a valid start or end byte +// Input : *outwords - +// Output : int +//----------------------------------------------------------------------------- +int CountUnuseableWords( CSentence& outwords ) +{ + int count = 0; + int numwords = outwords.m_Words.Size(); + // Nothing to do + if ( numwords <= 0 ) + return count; + + for ( int i = 0; i < numwords; i++ ) + { + CWordTag *word = outwords.m_Words[ i ]; + if ( IsUseable( word ) ) + continue; + + count++; + } + + return count; +} + +// Keeps same relative spacing, but rebases list +void RepartitionPhonemes( CWordTag *word, unsigned int oldStart, unsigned int oldEnd ) +{ + // Repartition phonemes based on old range + float oldRange = ( float )( oldEnd - oldStart ); + float newRange = ( float )( word->m_uiEndByte - word->m_uiStartByte ); + + for ( int i = 0; i < word->m_Phonemes.Size(); i++ ) + { + CPhonemeTag *tag = word->m_Phonemes[ i ]; + Assert( tag ); + + float frac1 = 0.0f, frac2 = 0.0f; + float delta1, delta2; + + delta1 = ( float ) ( tag->m_uiStartByte - oldStart ); + delta2 = ( float ) ( tag->m_uiEndByte - oldStart ); + if ( oldRange > 0.0f ) + { + frac1 = delta1 / oldRange; + frac2 = delta2 / oldRange; + } + + tag->m_uiStartByte = word->m_uiStartByte + ( unsigned int ) ( frac1 * newRange ); + tag->m_uiEndByte = word->m_uiStartByte + ( unsigned int ) ( frac2 * newRange ); + } +} + +void PartitionWords( CSentence& outwords, int start, int end, int sampleStart, int sampleEnd ) +{ + int wordCount = end - start + 1; + Assert( wordCount >= 1 ); + int stepSize = ( sampleEnd - sampleStart ) / wordCount; + + int currentStart = sampleStart; + + for ( int i = start; i <= end; i++ ) + { + CWordTag *word = outwords.m_Words[ i ]; + Assert( word ); + + unsigned int oldStart = word->m_uiStartByte; + unsigned int oldEnd = word->m_uiEndByte; + + word->m_uiStartByte = currentStart; + word->m_uiEndByte = currentStart + stepSize; + + RepartitionPhonemes( word, oldStart, oldEnd ); + + currentStart += stepSize; + } +} + +void MergeWords( CWordTag *w1, CWordTag *w2 ) +{ + unsigned int start, end; + + start = min( w1->m_uiStartByte, w2->m_uiStartByte ); + end = max( w1->m_uiEndByte, w2->m_uiEndByte ); + + unsigned int mid = ( start + end ) / 2; + + unsigned int oldw1start, oldw2start, oldw1end, oldw2end; + + oldw1start = w1->m_uiStartByte; + oldw2start = w2->m_uiStartByte; + oldw1end = w1->m_uiEndByte; + oldw2end = w2->m_uiEndByte; + + w1->m_uiStartByte = start; + w1->m_uiEndByte = mid; + w2->m_uiStartByte = mid; + w2->m_uiEndByte = end; + + RepartitionPhonemes( w1, oldw1start, oldw1end ); + RepartitionPhonemes( w2, oldw2start, oldw2end ); +} + +void FixupZeroLengthWords( CSentence& outwords ) +{ + while ( 1 ) + { + int i; + for ( i = 0 ; i < outwords.m_Words.Size() - 1; i++ ) + { + CWordTag *current, *next; + + current = outwords.m_Words[ i ]; + next = outwords.m_Words[ i + 1 ]; + + if ( current->m_uiEndByte - current->m_uiStartByte <= 0 ) + { + MergeWords( current, next ); + break; + } + + if ( next->m_uiEndByte - next->m_uiStartByte <= 0 ) + { + MergeWords( current, next ); + break; + } + } + + if ( i >= outwords.m_Words.Size() - 1 ) + { + break; + } + } +} + +void ComputeMissingByteSpans( int numsamples, CSentence& outwords ) +{ + int numwords = outwords.m_Words.Size(); + // Nothing to do + if ( numwords <= 0 ) + return; + + int interationcount = 1; + + while( 1 ) + { + Log( "\nCompute %i\n", interationcount++ ); + LogWords( outwords ); + + int wordNumber; + + // Done! + if ( !CountUnuseableWords( outwords ) ) + { + FixupZeroLengthWords( outwords ); + break; + } + + if ( !CountUsableWords( outwords ) ) + { + // Evenly space words across full sample time + PartitionWords( outwords, 0, numwords - 1, 0, numsamples ); + break; + } + + wordNumber = FindFirstUsableWord( outwords ); + // Not the first word + if ( wordNumber > 0 ) + { + // Repartition all of the unusables and the first one starting at zero over the range + CWordTag *firstUsable = outwords.m_Words[ wordNumber ]; + Assert( firstUsable ); + + if ( firstUsable->m_uiStartByte != 0 ) + { + PartitionWords( outwords, 0, wordNumber - 1, 0, firstUsable->m_uiStartByte ); + } + else + { + PartitionWords( outwords, 0, wordNumber, 0, firstUsable->m_uiEndByte ); + } + + // Start over + continue; + } + + wordNumber = FindLastUsableWord( outwords ); + // Not the last word + if ( wordNumber >= 0 && wordNumber < numwords - 1 ) + { + // Repartition all of the unusables and the first one starting at zero over the range + CWordTag *lastUsable = outwords.m_Words[ wordNumber ]; + Assert( lastUsable ); + + if ( lastUsable->m_uiEndByte != (unsigned int)numsamples ) + { + PartitionWords( outwords, wordNumber + 1, numwords-1, lastUsable->m_uiEndByte, numsamples ); + } + else + { + PartitionWords( outwords, wordNumber, numwords-1, lastUsable->m_uiStartByte, numsamples ); + } + + // Start over + continue; + } + + // If we get here it means that the start and end of the list are okay and we just have to + // iterate across the list and fix things in the middle + int startByte = 0; + int endByte = 0; + for ( int i = 0; i < numwords ; i++ ) + { + CWordTag *word = outwords.m_Words[ i ]; + if ( IsUseable( word ) ) + { + startByte = word->m_uiEndByte; + continue; + } + + // Found the start of a chain of 1 or more unusable words + // Find the startbyte of the next usable word and count how many words we check + int wordCount = 1; + for ( int j = i + 1; j < numwords; j++ ) + { + CWordTag *next = outwords.m_Words[ j ]; + if ( IsUseable( next ) ) + { + endByte = next->m_uiStartByte; + break; + } + + wordCount++; + } + + // Now partition words across the gap and go to start again + PartitionWords( outwords, i, i + wordCount - 1, startByte, endByte ); + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Given a wavfile and a list of inwords, determines the word/phonene +// sample counts for the sentce +// Input : *wavfile - +// *inwords - +// *outphonemes{ text.Clear( - +// Output : SR_RESULT +//----------------------------------------------------------------------------- +static SR_RESULT SAPI_ExtractPhonemes( + const char *wavfile, + int numsamples, + void (*pfnPrint)( const char *fmt, ... ), + CSentence& inwords, + CSentence& outwords ) +{ + LogReset(); + + USES_CONVERSION; + + CSpDynamicString text; + text.Clear(); + + HKEY hkwipe; + LONG lResult = RegOpenKeyEx( HKEY_CURRENT_USER, "Software\\Microsoft\\Speech\\RecoProfiles", 0, KEY_ALL_ACCESS, &hkwipe ); + if ( lResult == ERROR_SUCCESS ) + { + RecursiveRegDelKey( hkwipe ); + RegCloseKey( hkwipe ); + } + + if ( strlen( inwords.GetText() ) <= 0 ) + { + inwords.SetTextFromWords(); + } + + // Construct a string from the inwords array + text.Append( T2W( inwords.GetText() ) ); + + // Assume failure + SR_RESULT result = SR_RESULT_ERROR; + + if ( text.Length() > 0 ) + { + CSentence sentence; + + pfnPrint( "Processing...\r\n" ); + + // Give it a try + result = ExtractPhonemes( wavfile, text, sentence, pfnPrint ); + + pfnPrint( "Finished.\r\n" ); + // PrintWordsAndPhonemes( sentence, pfnPrint ); + + // Copy results to outputs + outwords.Reset(); + + outwords.SetText( inwords.GetText() ); + + Log( "Starting\n" ); + LogWords( inwords ); + + if ( SR_RESULT_ERROR != result ) + { + int i; + + Log( "Hypothesized\n" ); + LogWords( sentence ); + + for( i = 0 ; i < sentence.m_Words.Size(); i++ ) + { + CWordTag *tag = sentence.m_Words[ i ]; + if ( tag ) + { + // Skip '...' tag + if ( stricmp( tag->GetWord(), "..." ) ) + { + CWordTag *newTag = new CWordTag( *tag ); + + outwords.m_Words.AddToTail( newTag ); + } + } + } + + // Now insert unrecognized/skipped words from original list + // + int frompos = 0, topos = 0; + + while( 1 ) + { + // End of source list + if ( frompos >= inwords.m_Words.Size() ) + break; + + const CWordTag *fromTag = inwords.m_Words[ frompos ]; + + // Reached end of destination list, just copy words over from from source list until + // we run out of source words + if ( topos >= outwords.m_Words.Size() ) + { + // Just copy words over + CWordTag *newWord = new CWordTag( *fromTag ); + + // Remove phonemes + while ( newWord->m_Phonemes.Size() > 0 ) + { + CPhonemeTag *kill = newWord->m_Phonemes[ 0 ]; + newWord->m_Phonemes.Remove( 0 ); + delete kill; + } + + outwords.m_Words.AddToTail( newWord ); + frompos++; + topos++; + continue; + } + + // Destination word + const CWordTag *toTag = outwords.m_Words[ topos ]; + + // Words match, just skip ahead + if ( !stricmp( fromTag->GetWord(), toTag->GetWord() ) ) + { + frompos++; + topos++; + continue; + } + + // The only case we handle is that something in the source wasn't in the destination + + // Find the next source word that appears in the destination + int skipAhead = frompos + 1; + bool found = false; + while ( skipAhead < inwords.m_Words.Size() ) + { + const CWordTag *sourceWord = inwords.m_Words[ skipAhead ]; + if ( !stricmp( sourceWord->GetWord(), toTag->GetWord() ) ) + { + found = true; + break; + } + + skipAhead++; + } + + // Uh oh destination has words that are not in source, just skip to next destination word? + if ( !found ) + { + topos++; + } + else + { + // Copy words from from source list into destination + // + int skipCount = skipAhead - frompos; + + while ( --skipCount>= 0 ) + { + const CWordTag *sourceWord = inwords.m_Words[ frompos++ ]; + CWordTag *newWord = new CWordTag( *sourceWord ); + + // Remove phonemes + while ( newWord->m_Phonemes.Size() > 0 ) + { + CPhonemeTag *kill = newWord->m_Phonemes[ 0 ]; + newWord->m_Phonemes.Remove( 0 ); + delete kill; + } + + outwords.m_Words.InsertBefore( topos, newWord ); + topos++; + } + + frompos++; + topos++; + } + } + + Log( "\nDone simple check\n" ); + + LogWords( outwords ); + LogPhonemes( outwords ); + + ComputeMissingByteSpans( numsamples, outwords ); + + Log( "\nFinal check\n" ); + + LogWords( outwords ); + LogPhonemes( outwords ); + } + } + else + { + pfnPrint( "Input sentence is empty!\n" ); + } + + // Return results + return result; +} + + +//----------------------------------------------------------------------------- +// Purpose: Expose the interface +//----------------------------------------------------------------------------- +class CPhonemeExtractorSAPI : public IPhonemeExtractor +{ +public: + virtual PE_APITYPE GetAPIType() const + { + return SPEECH_API_SAPI; + } + + // Used for menus, etc + virtual char const *GetName() const + { + return "MS SAPI 5.1"; + } + + SR_RESULT Extract( + const char *wavfile, + int numsamples, + void (*pfnPrint)( const char *fmt, ... ), + CSentence& inwords, + CSentence& outwords ) + { + return SAPI_ExtractPhonemes( wavfile, numsamples, pfnPrint, inwords, outwords ); + } +}; + EXPOSE_SINGLE_INTERFACE( CPhonemeExtractorSAPI, IPhonemeExtractor, VPHONEME_EXTRACTOR_INTERFACE ); \ No newline at end of file diff --git a/mp/src/utils/phonemeextractor/phonemeextractor.vpc b/mp/src/utils/phonemeextractor/phonemeextractor.vpc index f6e82e4d..cdcee5c2 100644 --- a/mp/src/utils/phonemeextractor/phonemeextractor.vpc +++ b/mp/src/utils/phonemeextractor/phonemeextractor.vpc @@ -1,83 +1,83 @@ -//----------------------------------------------------------------------------- -// PHONEMEEXTRACTOR.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin\phonemeextractors" - -$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" - -$Configuration -{ - $Compiler - { - $AdditionalIncludeDirectories "$BASE;../common,../hlfaceposer,../sapi51/include" - $PreprocessorDefinitions "$BASE;PHONEMEEXTRACTOR_EXPORTS" - - // The project has some trouble with the deprecated string function warnings, so turn those off. - $AdditionalOptions "$BASE /wd4995" - } - - $Linker - { - $AdditionalDependencies "$BASE odbc32.lib odbccp32.lib" - } -} - -$Project "Phonemeextractor" -{ - $Folder "Source Files" - { - $File "extractor_utils.cpp" - $File "$SRCDIR\public\phonemeconverter.cpp" - $File "$SRCDIR\public\sentence.cpp" - $File "phonemeextractor.cpp" - } - - $Folder "Header Files" - { - $File "talkback.h" - } - - $Folder "SAPI Header Files" - { // These are dynamic because sapi might not be present in SDK branches, but we still want VPC to succeed. - $DynamicFile "..\sapi51\Include\sapi.h" - $DynamicFile "..\sapi51\Include\sapiddk.h" - $DynamicFile "..\sapi51\Include\Spddkhlp.h" - $DynamicFile "..\sapi51\Include\spdebug.h" - $DynamicFile "..\sapi51\Include\sperror.h" - $DynamicFile "..\sapi51\Include\sphelper.h" - } - - $Folder "Public Header Files" - { - $File "$SRCDIR\public\mathlib\amd3dx.h" - $File "$SRCDIR\public\tier0\basetypes.h" - $File "$SRCDIR\public\tier0\commonmacros.h" - $File "$SRCDIR\public\tier0\dbg.h" - $File "$SRCDIR\public\tier0\fasttimer.h" - $File "$SRCDIR\public\appframework\IAppSystem.h" - $File "$SRCDIR\public\mathlib\mathlib.h" - $File "$SRCDIR\public\phonemeconverter.h" - $File "$SRCDIR\public\phonemeextractor\phonemeextractor.h" - $File "$SRCDIR\public\tier0\platform.h" - $File "$SRCDIR\public\tier0\protected_things.h" - $File "$SRCDIR\public\sentence.h" - $File "$SRCDIR\public\string_t.h" - $File "$SRCDIR\public\tier1\strtools.h" - $File "$SRCDIR\public\tier1\utllinkedlist.h" - $File "$SRCDIR\public\tier1\utlmemory.h" - $File "$SRCDIR\public\tier1\utlvector.h" - $File "$SRCDIR\public\mathlib\vector.h" - $File "$SRCDIR\public\mathlib\vector2d.h" - $File "$SRCDIR\public\vstdlib\vstdlib.h" - } - - $Folder "Link Libraries" - { - $Lib mathlib - $DynamicFile "..\sapi51\lib\i386\sapi.lib" - } -} +//----------------------------------------------------------------------------- +// PHONEMEEXTRACTOR.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin\phonemeextractors" + +$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE;../common,../hlfaceposer,../sapi51/include" + $PreprocessorDefinitions "$BASE;PHONEMEEXTRACTOR_EXPORTS" + + // The project has some trouble with the deprecated string function warnings, so turn those off. + $AdditionalOptions "$BASE /wd4995" + } + + $Linker + { + $AdditionalDependencies "$BASE odbc32.lib odbccp32.lib" + } +} + +$Project "Phonemeextractor" +{ + $Folder "Source Files" + { + $File "extractor_utils.cpp" + $File "$SRCDIR\public\phonemeconverter.cpp" + $File "$SRCDIR\public\sentence.cpp" + $File "phonemeextractor.cpp" + } + + $Folder "Header Files" + { + $File "talkback.h" + } + + $Folder "SAPI Header Files" + { // These are dynamic because sapi might not be present in SDK branches, but we still want VPC to succeed. + $DynamicFile "..\sapi51\Include\sapi.h" + $DynamicFile "..\sapi51\Include\sapiddk.h" + $DynamicFile "..\sapi51\Include\Spddkhlp.h" + $DynamicFile "..\sapi51\Include\spdebug.h" + $DynamicFile "..\sapi51\Include\sperror.h" + $DynamicFile "..\sapi51\Include\sphelper.h" + } + + $Folder "Public Header Files" + { + $File "$SRCDIR\public\mathlib\amd3dx.h" + $File "$SRCDIR\public\tier0\basetypes.h" + $File "$SRCDIR\public\tier0\commonmacros.h" + $File "$SRCDIR\public\tier0\dbg.h" + $File "$SRCDIR\public\tier0\fasttimer.h" + $File "$SRCDIR\public\appframework\IAppSystem.h" + $File "$SRCDIR\public\mathlib\mathlib.h" + $File "$SRCDIR\public\phonemeconverter.h" + $File "$SRCDIR\public\phonemeextractor\phonemeextractor.h" + $File "$SRCDIR\public\tier0\platform.h" + $File "$SRCDIR\public\tier0\protected_things.h" + $File "$SRCDIR\public\sentence.h" + $File "$SRCDIR\public\string_t.h" + $File "$SRCDIR\public\tier1\strtools.h" + $File "$SRCDIR\public\tier1\utllinkedlist.h" + $File "$SRCDIR\public\tier1\utlmemory.h" + $File "$SRCDIR\public\tier1\utlvector.h" + $File "$SRCDIR\public\mathlib\vector.h" + $File "$SRCDIR\public\mathlib\vector2d.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + } + + $Folder "Link Libraries" + { + $Lib mathlib + $DynamicFile "..\sapi51\lib\i386\sapi.lib" + } +} diff --git a/mp/src/utils/phonemeextractor/phonemeextractor_ims.cpp b/mp/src/utils/phonemeextractor/phonemeextractor_ims.cpp index 70819f8e..29dabab4 100644 --- a/mp/src/utils/phonemeextractor/phonemeextractor_ims.cpp +++ b/mp/src/utils/phonemeextractor/phonemeextractor_ims.cpp @@ -1,1075 +1,1075 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// -#include -#include -#include -#include -#include -#include -#include -#include - -#include "phonemeextractor/PhonemeExtractor.h" -#include "ims_helper/ims_helper.h" - -#include "tier0/dbg.h" -#include "sentence.h" -#include "PhonemeConverter.h" -#include "tier1/strtools.h" - -#define TEXTLESS_WORDNAME "[Textless]" - -static IImsHelper *talkback = NULL; - -//----------------------------------------------------------------------------- -// Purpose: Expose the interface -//----------------------------------------------------------------------------- -class CPhonemeExtractorLipSinc : public IPhonemeExtractor -{ -public: - virtual PE_APITYPE GetAPIType() const - { - return SPEECH_API_LIPSINC; - } - - // Used for menus, etc - virtual char const *GetName() const - { - return "IMS (LipSinc)"; - } - - SR_RESULT Extract( - const char *wavfile, - int numsamples, - void (*pfnPrint)( const char *fmt, ... ), - CSentence& inwords, - CSentence& outwords ); - - - CPhonemeExtractorLipSinc( void ); - ~CPhonemeExtractorLipSinc( void ); - - enum - { - MAX_WORD_LENGTH = 128, - }; -private: - - - class CAnalyzedWord - { - public: - char buffer[ MAX_WORD_LENGTH ]; - double starttime; - double endtime; - }; - - class CAnalyzedPhoneme - { - public: - char phoneme[ 32 ]; - double starttime; - double endtime; - }; - - bool InitLipSinc( void ); - void ShutdownLipSinc( void ); - - void DescribeError( TALKBACK_ERR err ); - void Printf( char const *fmt, ... ); - - bool CheckSoundFile( char const *filename ); - bool GetInitialized( void ); - void SetInitialized( bool init ); - - void (*m_pfnPrint)( const char *fmt, ... ); - - char const *ConstructInputSentence( CSentence& inwords ); - bool AttemptAnalysis( TALKBACK_ANALYSIS **ppAnalysis, char const *wavfile, CSentence& inwords ); - - char const *ApplyTBWordRules( char const *word ); - - void ProcessWords( TALKBACK_ANALYSIS *analysis, CSentence& inwords, CSentence& outwords ); - void ProcessWordsTextless( TALKBACK_ANALYSIS *analysis, CSentence& outwords ); - - int GetPhonemeIndexAtWord( TALKBACK_ANALYSIS *analysis, double time, bool checkstart ); - - int GetPhonemeIndexAtWordStart( TALKBACK_ANALYSIS *analysis, double starttime ); - int GetPhonemeIndexAtWordEnd( TALKBACK_ANALYSIS *analysis, double endtime ); - - CAnalyzedWord *GetAnalyzedWord( TALKBACK_ANALYSIS *analysis, int index ); - CAnalyzedPhoneme *GetAnalyzedPhoneme( TALKBACK_ANALYSIS *analysis, int index ); - - int ComputeByteFromTime( float time ); - - bool m_bInitialized; - - float m_flSampleCount; - float m_flDuration; - - float m_flSamplesPerSecond; - - int m_nBytesPerSample; - - HMODULE m_hHelper; -}; - -CPhonemeExtractorLipSinc::CPhonemeExtractorLipSinc( void ) -{ - m_hHelper = (HMODULE)0; - m_pfnPrint = NULL; - - m_bInitialized = false; - - m_flSampleCount = 0.0f; - m_flDuration = 0.0f; - - m_flSamplesPerSecond = 0.0f; - - m_nBytesPerSample = 0; -} - -CPhonemeExtractorLipSinc::~CPhonemeExtractorLipSinc( void ) -{ - if ( GetInitialized() ) - { - ShutdownLipSinc(); - } -} - -bool CPhonemeExtractorLipSinc::GetInitialized( void ) -{ - return m_bInitialized; -} - -void CPhonemeExtractorLipSinc::SetInitialized( bool init ) -{ - m_bInitialized = init; -} - -int CPhonemeExtractorLipSinc::ComputeByteFromTime( float time ) -{ - if ( !m_flDuration ) - return 0; - - float frac = time / m_flDuration; - - float sampleNumber = frac * m_flSampleCount; - - int bytenumber = sampleNumber * m_nBytesPerSample; - - return bytenumber; -} - -void CPhonemeExtractorLipSinc::DescribeError( TALKBACK_ERR err ) -{ - Assert( m_pfnPrint ); - - // Get the error description. - char errorDesc[256] = ""; - if ( err != TALKBACK_NOERR ) - { - talkback->TalkBackGetErrorString( err, sizeof(errorDesc), errorDesc ); - } - - // Report or log the error... - (*m_pfnPrint)( "LIPSINC ERROR: %s\n", errorDesc ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *fmt - -// .. - -//----------------------------------------------------------------------------- -void CPhonemeExtractorLipSinc::Printf( char const *fmt, ... ) -{ - Assert( m_pfnPrint ); - - char string[ 4096 ]; - - va_list argptr; - va_start( argptr, fmt ); - vsprintf( string, fmt, argptr ); - va_end( argptr ); - - (*m_pfnPrint)( "%s", string ); -} - -bool CPhonemeExtractorLipSinc::CheckSoundFile( char const *filename ) -{ - TALKBACK_SOUND_FILE_METRICS fm; - memset( &fm, 0, sizeof( fm ) ); - fm.m_size = sizeof( fm ); - - TALKBACK_ERR err = talkback->TalkBackGetSoundFileMetrics( filename, &fm ); - if ( err != TALKBACK_NOERR ) - { - DescribeError( err ); - return false; - } - - if ( fm.m_canBeAnalyzed ) - { - Printf( "%s: %.2f s, rate %i, bits %i, channels %i\n", - filename, - fm.m_duration, - fm.m_sampleRate, - fm.m_bitsPerSample, - fm.m_channelCount ); - } - - m_flDuration = fm.m_duration; - if ( m_flDuration > 0 ) - { - m_flSamplesPerSecond = m_flSampleCount / m_flDuration; - } - else - { - m_flSamplesPerSecond = 0.0f; - } - - m_nBytesPerSample = ( fm.m_bitsPerSample >> 3 ); - - m_flSampleCount /= m_nBytesPerSample; - - m_nBytesPerSample /= fm.m_channelCount; - - return fm.m_canBeAnalyzed ? true : false; -} - -typedef IImsHelper *(*pfnImsHelper)(void); - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CPhonemeExtractorLipSinc::InitLipSinc( void ) -{ - if ( GetInitialized() ) - { - return true; - } - - m_hHelper = LoadLibrary( "ims_helper.dll" ); - if ( !m_hHelper ) - { - return false; - } - - pfnImsHelper factory = (pfnImsHelper)::GetProcAddress( m_hHelper, "GetImsHelper" ); - if ( !factory ) - { - FreeLibrary( m_hHelper ); - return false; - } - - talkback = reinterpret_cast< IImsHelper * >( (*factory)() ); - if ( !talkback ) - { - FreeLibrary( m_hHelper ); - return false; - } - - char szExeName[ MAX_PATH ]; - szExeName[0] = 0; - GetModuleFileName( (HMODULE)0, szExeName, sizeof( szExeName ) ); - - char szBaseDir[ MAX_PATH ]; - Q_strncpy( szBaseDir, szExeName, sizeof( szBaseDir ) ); - - Q_StripLastDir( szBaseDir, sizeof( szBaseDir ) ); - Q_StripTrailingSlash( szBaseDir ); - Q_strlower( szBaseDir ); - - char coreDataDir[ 512 ]; - Q_snprintf( coreDataDir, sizeof( coreDataDir ), "%s\\lipsinc_data\\", - szBaseDir ); - Q_FixSlashes( coreDataDir ); - - char szCheck[ 512 ]; - Q_snprintf( szCheck, sizeof( szCheck ), "%sDtC6dal.dat", coreDataDir ); - struct __stat64 buf; - - if ( _stat64( szCheck, &buf ) != 0 ) - { - Q_snprintf( coreDataDir, sizeof( coreDataDir ), "%s\\bin\\lipsinc_data\\", - szBaseDir ); - Q_FixSlashes( coreDataDir ); - Q_snprintf( szCheck, sizeof( szCheck ), "%sDtC6dal.dat", coreDataDir ); - - if ( _stat64( szCheck, &buf ) != 0 ) - { - Error( "Unable to find talkback data files in %s.", coreDataDir ); - } - } - - TALKBACK_ERR err; - - err = talkback->TalkBackStartupLibrary( coreDataDir ); - if ( err != TALKBACK_NOERR ) - { - DescribeError( err ); - FreeLibrary( m_hHelper ); - return false; - } - - long verMajor = 0; - long verMinor = 0; - long verRevision = 0; - - err = talkback->TalkBackGetVersion( - &verMajor, - &verMinor, - &verRevision); - if ( err != TALKBACK_NOERR ) - { - DescribeError( err ); - FreeLibrary( m_hHelper ); - return false; - } - - Printf( "Lipsinc TalkBack Version %i.%i.%i\n", verMajor, verMinor, verRevision ); - - m_bInitialized = true; - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPhonemeExtractorLipSinc::ShutdownLipSinc( void ) -{ - // HACK HACK: This seems to crash on exit sometimes - __try - { - talkback->TalkBackShutdownLibrary(); - - FreeLibrary( m_hHelper ); - } - __except(EXCEPTION_EXECUTE_HANDLER ) - { - OutputDebugString( "----> Crash shutting down TALKBACK sdk, exception caught and ignored\n" ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : inwords - -// Output : char const -//----------------------------------------------------------------------------- -char const *CPhonemeExtractorLipSinc::ConstructInputSentence( CSentence& inwords ) -{ - static char sentence[ 16384 ]; - - sentence[ 0 ] = 0; - - int last = inwords.m_Words.Size() - 1; - - for ( int i = 0 ; i <= last; i++ ) - { - CWordTag *w = inwords.m_Words[ i ]; - - strcat( sentence, w->GetWord() ); - if ( i != last ) - { - strcat( sentence, " " ); - } - } - - if ( inwords.m_Words.Count() == 1 && - !Q_strnicmp( inwords.GetText(), TEXTLESS_WORDNAME, Q_strlen( TEXTLESS_WORDNAME ) ) ) - { - sentence[ 0 ] = 0; - } - - return sentence; -} - -bool CPhonemeExtractorLipSinc::AttemptAnalysis( TALKBACK_ANALYSIS **ppAnalysis, char const *wavfile, CSentence& inwords ) -{ - *ppAnalysis = NULL; - - TALKBACK_ANALYSIS_SETTINGS settings; - memset( &settings, 0, sizeof( settings ) ); - - // Set this field to sizeof(TALKBACK_ANALYSIS_SETTINGS) before using the - // structure. - settings.fSize = sizeof( TALKBACK_ANALYSIS_SETTINGS ); - - - // Default value: 30 (frames per second). - settings.fFrameRate = 100; - // Set this to 1 to optimize for flipbook output, 0 to do analysis normally. - // - // Default value: 0 (normal analysis). - settings.fOptimizeForFlipbook = 0; - // Set this to -1 to seed the random number generator with the current time. - // Any other number will be used directly for the random number seed, which - // is useful if you want repeatable speech gestures. This value does not - // influence lip-synching at all. - // - // Default value: -1 (use current time). - settings.fRandomSeed = -1; - // Path to the configuration (.INI) file with phoneme-to-speech-target - // mapping. Set this to NULL to use the default mapping. - // - // Default value: NULL (use default mapping). - settings.fConfigFile = NULL; - - char const *text = ConstructInputSentence( inwords ); - - Printf( "Analyzing: \"%s\"\n", text[ 0 ] ? text : TEXTLESS_WORDNAME ); - - TALKBACK_ERR err = talkback->TalkBackGetAnalysis( - ppAnalysis, - wavfile, - text, - &settings ); - - if ( err != TALKBACK_NOERR ) - { - DescribeError( err ); - return false; - } - - Printf( "Analysis successful...\n" ); - - return true; -} - -typedef struct -{ - TALKBACK_PHONEME phoneme; - char const *string; -} TBPHONEMES_t; - -static TBPHONEMES_t g_TBPhonemeList[]= -{ - { TALKBACK_PHONEME_IY, "iy" }, - { TALKBACK_PHONEME_IH, "ih" }, - { TALKBACK_PHONEME_EH, "eh" }, - { TALKBACK_PHONEME_EY, "ey" }, - { TALKBACK_PHONEME_AE, "ae" }, - { TALKBACK_PHONEME_AA, "aa" }, - { TALKBACK_PHONEME_AW, "aw" }, - { TALKBACK_PHONEME_AY, "ay" }, - { TALKBACK_PHONEME_AH, "ah" }, - { TALKBACK_PHONEME_AO, "ao" }, - { TALKBACK_PHONEME_OY, "oy" }, - { TALKBACK_PHONEME_OW, "ow" }, - { TALKBACK_PHONEME_UH, "uh" }, - { TALKBACK_PHONEME_UW, "uw" }, - { TALKBACK_PHONEME_ER, "er" }, - { TALKBACK_PHONEME_AX, "ax" }, - { TALKBACK_PHONEME_S, "s" }, - { TALKBACK_PHONEME_SH, "sh" }, - { TALKBACK_PHONEME_Z, "z" }, - { TALKBACK_PHONEME_ZH, "zh" }, - { TALKBACK_PHONEME_F, "f" }, - { TALKBACK_PHONEME_TH, "th" }, - { TALKBACK_PHONEME_V, "v" }, - { TALKBACK_PHONEME_DH, "dh" }, - { TALKBACK_PHONEME_M, "m" }, - { TALKBACK_PHONEME_N, "n" }, - { TALKBACK_PHONEME_NG, "ng" }, - { TALKBACK_PHONEME_L, "l" }, - { TALKBACK_PHONEME_R, "r" }, - { TALKBACK_PHONEME_W, "w" }, - { TALKBACK_PHONEME_Y, "y" }, - { TALKBACK_PHONEME_HH, "hh" }, - { TALKBACK_PHONEME_B, "b" }, - { TALKBACK_PHONEME_D, "d" }, - { TALKBACK_PHONEME_JH, "jh" }, - { TALKBACK_PHONEME_G, "g" }, - { TALKBACK_PHONEME_P, "p" }, - { TALKBACK_PHONEME_T, "t" }, - { TALKBACK_PHONEME_K, "k" }, - { TALKBACK_PHONEME_CH, "ch" }, - { TALKBACK_PHONEME_SIL, "" }, - { -1, NULL } -}; - -char const *TBPhonemeToString( TALKBACK_PHONEME phoneme ) -{ - if ( phoneme < TALKBACK_PHONEME_FIRST || phoneme > TALKBACK_PHONEME_LAST ) - { - return "Bogus"; - } - - TBPHONEMES_t *item = &g_TBPhonemeList[ phoneme ]; - return item->string; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *analysis - -// time - -// start - -// Output : int -//----------------------------------------------------------------------------- -int CPhonemeExtractorLipSinc::GetPhonemeIndexAtWord( TALKBACK_ANALYSIS *analysis, double time, bool start ) -{ - long count; - - TALKBACK_ERR err = talkback->TalkBackGetNumPhonemes( analysis, &count ); - if ( err != TALKBACK_NOERR ) - { - DescribeError( err ); - return -1; - } - - if ( count <= 0L ) - return -1; - - // Bogus - if ( count >= 100000L ) - return -1; - - for ( int i = 0; i < (int)count; i++ ) - { - TALKBACK_PHONEME tbPhoneme = TALKBACK_PHONEME_INVALID; - err = talkback->TalkBackGetPhonemeEnum( analysis, i, &tbPhoneme ); - if ( err != TALKBACK_NOERR ) - { - DescribeError( err ); - continue; - } - - double t; - - if ( start ) - { - err = talkback->TalkBackGetPhonemeStartTime( analysis, i, &t ); - } - else - { - err = talkback->TalkBackGetPhonemeEndTime( analysis, i, &t ); - } - - if ( err != TALKBACK_NOERR ) - { - DescribeError( err ); - continue; - } - - if ( t == time ) - { - return i; - } - } - - return -1; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *analysis - -// starttime - -// Output : int -//----------------------------------------------------------------------------- -int CPhonemeExtractorLipSinc::GetPhonemeIndexAtWordStart( TALKBACK_ANALYSIS *analysis, double starttime ) -{ - return GetPhonemeIndexAtWord( analysis, starttime, true ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *analysis - -// endtime - -// Output : int -//----------------------------------------------------------------------------- -int CPhonemeExtractorLipSinc::GetPhonemeIndexAtWordEnd( TALKBACK_ANALYSIS *analysis, double endtime ) -{ - return GetPhonemeIndexAtWord( analysis, endtime, false ); -} - -CPhonemeExtractorLipSinc::CAnalyzedPhoneme *CPhonemeExtractorLipSinc::GetAnalyzedPhoneme( TALKBACK_ANALYSIS *analysis, int index ) -{ - static CAnalyzedPhoneme p; - - memset( &p, 0, sizeof( p ) ); - - TALKBACK_PHONEME tb; - - TALKBACK_ERR err = talkback->TalkBackGetPhonemeEnum( analysis, index, &tb ); - if ( err != TALKBACK_NOERR ) - { - DescribeError( err ); - return NULL; - } - - strcpy( p.phoneme, TBPhonemeToString( tb ) ); - - err = talkback->TalkBackGetPhonemeStartTime( analysis, index, &p.starttime ); - if ( err != TALKBACK_NOERR ) - { - DescribeError( err ); - return NULL; - } - err = talkback->TalkBackGetPhonemeEndTime( analysis, index, &p.endtime ); - if ( err != TALKBACK_NOERR ) - { - DescribeError( err ); - return NULL; - } - - return &p; -} - -CPhonemeExtractorLipSinc::CAnalyzedWord *CPhonemeExtractorLipSinc::GetAnalyzedWord( TALKBACK_ANALYSIS *analysis, int index ) -{ - static CAnalyzedWord w; - - memset( &w, 0, sizeof( w ) ); - - long chars = sizeof( w.buffer ); - - TALKBACK_ERR err = talkback->TalkBackGetWord( analysis, index, chars, w.buffer ); - if ( err != TALKBACK_NOERR ) - { - DescribeError( err ); - return NULL; - } - - err = talkback->TalkBackGetWordStartTime( analysis, index, &w.starttime ); - if ( err != TALKBACK_NOERR ) - { - DescribeError( err ); - return NULL; - } - err = talkback->TalkBackGetWordEndTime( analysis, index, &w.endtime ); - if ( err != TALKBACK_NOERR ) - { - DescribeError( err ); - return NULL; - } - - return &w; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *w1 - -// *w2 - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool FuzzyWordMatch( char const *w1, char const *w2 ) -{ - int len1 = strlen( w1 ); - int len2 = strlen( w2 ); - - int minlen = min( len1, len2 ); - - // Found a match - if ( !strnicmp( w1, w2, minlen ) ) - return true; - - int letterdiff = abs( len1 - len2 ); - // More than three letters different, don't bother - if ( letterdiff > 5 ) - return false; - - // Compute a "delta" - char *p1 = (char *)w1; - char *p2 = (char *)w2; - - CUtlVector word1; - CUtlVector word2; - - while ( *p1 ) - { - if ( V_isalpha( *p1 ) ) - { - word1.AddToTail( *p1 ); - } - p1++; - } - - while ( *p2 ) - { - if ( V_isalpha( *p2 ) ) - { - word2.AddToTail( *p2 ); - } - p2++; - } - - int i; - for ( i = 0; i < word1.Size(); i++ ) - { - char c = word1[ i ]; - - // See if c is in word 2, if so subtract it out - int idx = word2.Find( c ); - - if ( idx != word2.InvalidIndex() ) - { - word2.Remove( idx ); - } - } - - if ( word2.Size() <= letterdiff ) - return true; - - word2.RemoveAll(); - - while ( *p2 ) - { - if ( V_isalpha( *p2 ) ) - { - word2.AddToTail( *p2 ); - } - p2++; - } - - for ( i = 0; i < word2.Size(); i++ ) - { - char c = word2[ i ]; - - // See if c is in word 2, if so subtract it out - int idx = word1.Find( c ); - - if ( idx != word1.InvalidIndex() ) - { - word1.Remove( idx ); - } - } - - if ( word1.Size() <= letterdiff ) - return true; - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: For foreign language stuff, if inwords is empty, process anyway... -// Input : *analysis - -// outwords - -//----------------------------------------------------------------------------- -void CPhonemeExtractorLipSinc::ProcessWordsTextless( TALKBACK_ANALYSIS *analysis, CSentence& outwords ) -{ - long count; - - TALKBACK_ERR err = talkback->TalkBackGetNumPhonemes( analysis, &count ); - if ( err != TALKBACK_NOERR ) - { - DescribeError( err ); - return; - } - - CWordTag *newWord = new CWordTag; - - newWord->SetWord( TEXTLESS_WORDNAME ); - - float starttime = 0.0f; - float endtime = 1.0f; - - - for ( int i = 0; i < count; ++i ) - { - // Get phoneme and timing info - CAnalyzedPhoneme *ph = GetAnalyzedPhoneme( analysis, i ); - if ( !ph ) - continue; - - CPhonemeTag *ptag = new CPhonemeTag; - - if ( i == 0 || ( ph->starttime < starttime ) ) - { - starttime = ph->starttime; - } - - if ( i == 0 || ( ph->endtime > endtime ) ) - { - endtime = ph->endtime; - } - - ptag->SetStartTime( ph->starttime ); - ptag->SetEndTime( ph->endtime ); - - ptag->m_uiStartByte = ComputeByteFromTime( ph->starttime ); - ptag->m_uiEndByte = ComputeByteFromTime( ph->endtime ); - - ptag->SetTag( ph->phoneme ); - ptag->SetPhonemeCode( TextToPhoneme( ptag->GetTag() ) ); - - newWord->m_Phonemes.AddToTail( ptag ); - } - - newWord->m_flStartTime = starttime; - newWord->m_flEndTime = endtime; - - newWord->m_uiStartByte = ComputeByteFromTime( starttime ); - newWord->m_uiEndByte = ComputeByteFromTime( endtime ); - - outwords.Reset(); - outwords.AddWordTag( newWord ); - outwords.SetTextFromWords(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *analysis - -// inwords - -// outwords - -//----------------------------------------------------------------------------- -void CPhonemeExtractorLipSinc::ProcessWords( TALKBACK_ANALYSIS *analysis, CSentence& inwords, CSentence& outwords ) -{ - long count; - - TALKBACK_ERR err = talkback->TalkBackGetNumWords( analysis, &count ); - if ( err != TALKBACK_NOERR ) - { - DescribeError( err ); - return; - } - - if ( count <= 0L ) - { - if ( inwords.m_Words.Count() == 0 || - !Q_strnicmp( inwords.GetText(), TEXTLESS_WORDNAME, Q_strlen( TEXTLESS_WORDNAME ) ) ) - { - ProcessWordsTextless( analysis, outwords ); - } - return; - } - - // Bogus - if ( count >= 100000L ) - return; - - int inwordpos = 0; - int awordpos = 0; - - outwords.Reset(); - - char previous[ 256 ]; - previous[ 0 ] = 0; - - while ( inwordpos < inwords.m_Words.Size() ) - { - CWordTag *in = inwords.m_Words[ inwordpos ]; - - if ( awordpos >= count ) - { - // Just copy the rest over without phonemes - CWordTag *copy = new CWordTag( *in ); - - outwords.AddWordTag( copy ); - - inwordpos++; - continue; - } - - // Should never fail - CAnalyzedWord *w = GetAnalyzedWord( analysis, awordpos ); - if ( !w ) - { - return; - } - - if ( !stricmp( w->buffer, "" ) ) - { - awordpos++; - continue; - } - - char const *check = ApplyTBWordRules( in->GetWord() ); - if ( !FuzzyWordMatch( check, w->buffer ) ) - { - bool advance_input = true; - if ( previous[ 0 ] ) - { - if ( FuzzyWordMatch( previous, w->buffer ) ) - { - advance_input = false; - } - } - - if ( advance_input ) - { - inwordpos++; - } - awordpos++; - continue; - } - strcpy( previous, check ); - - CWordTag *newWord = new CWordTag; - - newWord->SetWord( in->GetWord() ); - - newWord->m_flStartTime = w->starttime; - newWord->m_flEndTime = w->endtime; - - newWord->m_uiStartByte = ComputeByteFromTime( w->starttime ); - newWord->m_uiEndByte = ComputeByteFromTime( w->endtime ); - - int phonemestart, phonemeend; - - phonemestart = GetPhonemeIndexAtWordStart( analysis, w->starttime ); - phonemeend = GetPhonemeIndexAtWordEnd( analysis, w->endtime ); - - if ( phonemestart >= 0 && phonemeend >= 0 ) - { - for ( ; phonemestart <= phonemeend; phonemestart++ ) - { - // Get phoneme and timing info - CAnalyzedPhoneme *ph = GetAnalyzedPhoneme( analysis, phonemestart ); - if ( !ph ) - continue; - - CPhonemeTag *ptag = new CPhonemeTag; - ptag->SetStartTime( ph->starttime ); - ptag->SetEndTime( ph->endtime ); - - ptag->m_uiStartByte = ComputeByteFromTime( ph->starttime ); - ptag->m_uiEndByte = ComputeByteFromTime( ph->endtime ); - - ptag->SetTag( ph->phoneme ); - ptag->SetPhonemeCode( TextToPhoneme( ptag->GetTag() ) ); - - newWord->m_Phonemes.AddToTail( ptag ); - } - } - - outwords.AddWordTag( newWord ); - inwordpos++; - awordpos++; - } -} - -char const *CPhonemeExtractorLipSinc::ApplyTBWordRules( char const *word ) -{ - static char outword[ 256 ]; - - char const *in = word; - char *out = outword; - - while ( *in && ( ( out - outword ) <= 255 ) ) - { - if ( *in == '\t' || - *in == ' ' || - *in == '\n' || - *in == '-' || - *in == '.' || - *in == ',' || - *in == ';' || - *in == '?' || - *in == '"' || - *in == ':' || - *in == '(' || - *in == ')' ) - { - in++; - *out++ = ' '; - continue; - } - - if ( !V_isprint( *in ) ) - { - in++; - continue; - } - - if ( *in >= 128 ) - { - in++; - continue; - } - - // Skip numbers - if ( *in >= '0' && *in <= '9' ) - { - in++; - continue; - } - - // Convert all letters to upper case - if ( *in >= 'a' && *in <= 'z' ) - { - *out++ = ( *in++ ) - 'a' + 'A'; - continue; - } - - if ( *in >= 'A' && *in <= 'Z' ) - { - *out++ = *in++; - continue; - } - - if ( *in == '\'' ) - { - *out++ = *in++; - continue; - } - - in++; - } - - *out = 0; - - return outword; -} - -//----------------------------------------------------------------------------- -// Purpose: Given a wavfile and a list of inwords, determines the word/phonene -// sample counts for the sentce -// Output : SR_RESULT -//----------------------------------------------------------------------------- -SR_RESULT CPhonemeExtractorLipSinc::Extract( - const char *wavfile, - int numsamples, - void (*pfnPrint)( const char *fmt, ... ), - CSentence& inwords, - CSentence& outwords ) -{ - // g_enableTalkBackDebuggingOutput = 1; - - m_pfnPrint = pfnPrint; - - if ( !InitLipSinc() ) - { - return SR_RESULT_ERROR; - } - - m_flSampleCount = numsamples; - - if ( !CheckSoundFile( wavfile ) ) - { - FreeLibrary( m_hHelper ); - return SR_RESULT_ERROR; - } - - TALKBACK_ANALYSIS *analysis = NULL; - - if ( !AttemptAnalysis( &analysis, wavfile, inwords ) ) - { - FreeLibrary( m_hHelper ); - return SR_RESULT_FAILED; - } - - if ( strlen( inwords.GetText() ) <= 0 ) - { - inwords.SetTextFromWords(); - } - - outwords = inwords; - - // Examine data - ProcessWords( analysis, inwords, outwords ); - - if ( analysis ) - { - talkback->TalkBackFreeAnalysis( &analysis ); - } - - return SR_RESULT_SUCCESS; -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phonemeextractor/PhonemeExtractor.h" +#include "ims_helper/ims_helper.h" + +#include "tier0/dbg.h" +#include "sentence.h" +#include "PhonemeConverter.h" +#include "tier1/strtools.h" + +#define TEXTLESS_WORDNAME "[Textless]" + +static IImsHelper *talkback = NULL; + +//----------------------------------------------------------------------------- +// Purpose: Expose the interface +//----------------------------------------------------------------------------- +class CPhonemeExtractorLipSinc : public IPhonemeExtractor +{ +public: + virtual PE_APITYPE GetAPIType() const + { + return SPEECH_API_LIPSINC; + } + + // Used for menus, etc + virtual char const *GetName() const + { + return "IMS (LipSinc)"; + } + + SR_RESULT Extract( + const char *wavfile, + int numsamples, + void (*pfnPrint)( const char *fmt, ... ), + CSentence& inwords, + CSentence& outwords ); + + + CPhonemeExtractorLipSinc( void ); + ~CPhonemeExtractorLipSinc( void ); + + enum + { + MAX_WORD_LENGTH = 128, + }; +private: + + + class CAnalyzedWord + { + public: + char buffer[ MAX_WORD_LENGTH ]; + double starttime; + double endtime; + }; + + class CAnalyzedPhoneme + { + public: + char phoneme[ 32 ]; + double starttime; + double endtime; + }; + + bool InitLipSinc( void ); + void ShutdownLipSinc( void ); + + void DescribeError( TALKBACK_ERR err ); + void Printf( char const *fmt, ... ); + + bool CheckSoundFile( char const *filename ); + bool GetInitialized( void ); + void SetInitialized( bool init ); + + void (*m_pfnPrint)( const char *fmt, ... ); + + char const *ConstructInputSentence( CSentence& inwords ); + bool AttemptAnalysis( TALKBACK_ANALYSIS **ppAnalysis, char const *wavfile, CSentence& inwords ); + + char const *ApplyTBWordRules( char const *word ); + + void ProcessWords( TALKBACK_ANALYSIS *analysis, CSentence& inwords, CSentence& outwords ); + void ProcessWordsTextless( TALKBACK_ANALYSIS *analysis, CSentence& outwords ); + + int GetPhonemeIndexAtWord( TALKBACK_ANALYSIS *analysis, double time, bool checkstart ); + + int GetPhonemeIndexAtWordStart( TALKBACK_ANALYSIS *analysis, double starttime ); + int GetPhonemeIndexAtWordEnd( TALKBACK_ANALYSIS *analysis, double endtime ); + + CAnalyzedWord *GetAnalyzedWord( TALKBACK_ANALYSIS *analysis, int index ); + CAnalyzedPhoneme *GetAnalyzedPhoneme( TALKBACK_ANALYSIS *analysis, int index ); + + int ComputeByteFromTime( float time ); + + bool m_bInitialized; + + float m_flSampleCount; + float m_flDuration; + + float m_flSamplesPerSecond; + + int m_nBytesPerSample; + + HMODULE m_hHelper; +}; + +CPhonemeExtractorLipSinc::CPhonemeExtractorLipSinc( void ) +{ + m_hHelper = (HMODULE)0; + m_pfnPrint = NULL; + + m_bInitialized = false; + + m_flSampleCount = 0.0f; + m_flDuration = 0.0f; + + m_flSamplesPerSecond = 0.0f; + + m_nBytesPerSample = 0; +} + +CPhonemeExtractorLipSinc::~CPhonemeExtractorLipSinc( void ) +{ + if ( GetInitialized() ) + { + ShutdownLipSinc(); + } +} + +bool CPhonemeExtractorLipSinc::GetInitialized( void ) +{ + return m_bInitialized; +} + +void CPhonemeExtractorLipSinc::SetInitialized( bool init ) +{ + m_bInitialized = init; +} + +int CPhonemeExtractorLipSinc::ComputeByteFromTime( float time ) +{ + if ( !m_flDuration ) + return 0; + + float frac = time / m_flDuration; + + float sampleNumber = frac * m_flSampleCount; + + int bytenumber = sampleNumber * m_nBytesPerSample; + + return bytenumber; +} + +void CPhonemeExtractorLipSinc::DescribeError( TALKBACK_ERR err ) +{ + Assert( m_pfnPrint ); + + // Get the error description. + char errorDesc[256] = ""; + if ( err != TALKBACK_NOERR ) + { + talkback->TalkBackGetErrorString( err, sizeof(errorDesc), errorDesc ); + } + + // Report or log the error... + (*m_pfnPrint)( "LIPSINC ERROR: %s\n", errorDesc ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *fmt - +// .. - +//----------------------------------------------------------------------------- +void CPhonemeExtractorLipSinc::Printf( char const *fmt, ... ) +{ + Assert( m_pfnPrint ); + + char string[ 4096 ]; + + va_list argptr; + va_start( argptr, fmt ); + vsprintf( string, fmt, argptr ); + va_end( argptr ); + + (*m_pfnPrint)( "%s", string ); +} + +bool CPhonemeExtractorLipSinc::CheckSoundFile( char const *filename ) +{ + TALKBACK_SOUND_FILE_METRICS fm; + memset( &fm, 0, sizeof( fm ) ); + fm.m_size = sizeof( fm ); + + TALKBACK_ERR err = talkback->TalkBackGetSoundFileMetrics( filename, &fm ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return false; + } + + if ( fm.m_canBeAnalyzed ) + { + Printf( "%s: %.2f s, rate %i, bits %i, channels %i\n", + filename, + fm.m_duration, + fm.m_sampleRate, + fm.m_bitsPerSample, + fm.m_channelCount ); + } + + m_flDuration = fm.m_duration; + if ( m_flDuration > 0 ) + { + m_flSamplesPerSecond = m_flSampleCount / m_flDuration; + } + else + { + m_flSamplesPerSecond = 0.0f; + } + + m_nBytesPerSample = ( fm.m_bitsPerSample >> 3 ); + + m_flSampleCount /= m_nBytesPerSample; + + m_nBytesPerSample /= fm.m_channelCount; + + return fm.m_canBeAnalyzed ? true : false; +} + +typedef IImsHelper *(*pfnImsHelper)(void); + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CPhonemeExtractorLipSinc::InitLipSinc( void ) +{ + if ( GetInitialized() ) + { + return true; + } + + m_hHelper = LoadLibrary( "ims_helper.dll" ); + if ( !m_hHelper ) + { + return false; + } + + pfnImsHelper factory = (pfnImsHelper)::GetProcAddress( m_hHelper, "GetImsHelper" ); + if ( !factory ) + { + FreeLibrary( m_hHelper ); + return false; + } + + talkback = reinterpret_cast< IImsHelper * >( (*factory)() ); + if ( !talkback ) + { + FreeLibrary( m_hHelper ); + return false; + } + + char szExeName[ MAX_PATH ]; + szExeName[0] = 0; + GetModuleFileName( (HMODULE)0, szExeName, sizeof( szExeName ) ); + + char szBaseDir[ MAX_PATH ]; + Q_strncpy( szBaseDir, szExeName, sizeof( szBaseDir ) ); + + Q_StripLastDir( szBaseDir, sizeof( szBaseDir ) ); + Q_StripTrailingSlash( szBaseDir ); + Q_strlower( szBaseDir ); + + char coreDataDir[ 512 ]; + Q_snprintf( coreDataDir, sizeof( coreDataDir ), "%s\\lipsinc_data\\", + szBaseDir ); + Q_FixSlashes( coreDataDir ); + + char szCheck[ 512 ]; + Q_snprintf( szCheck, sizeof( szCheck ), "%sDtC6dal.dat", coreDataDir ); + struct __stat64 buf; + + if ( _stat64( szCheck, &buf ) != 0 ) + { + Q_snprintf( coreDataDir, sizeof( coreDataDir ), "%s\\bin\\lipsinc_data\\", + szBaseDir ); + Q_FixSlashes( coreDataDir ); + Q_snprintf( szCheck, sizeof( szCheck ), "%sDtC6dal.dat", coreDataDir ); + + if ( _stat64( szCheck, &buf ) != 0 ) + { + Error( "Unable to find talkback data files in %s.", coreDataDir ); + } + } + + TALKBACK_ERR err; + + err = talkback->TalkBackStartupLibrary( coreDataDir ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + FreeLibrary( m_hHelper ); + return false; + } + + long verMajor = 0; + long verMinor = 0; + long verRevision = 0; + + err = talkback->TalkBackGetVersion( + &verMajor, + &verMinor, + &verRevision); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + FreeLibrary( m_hHelper ); + return false; + } + + Printf( "Lipsinc TalkBack Version %i.%i.%i\n", verMajor, verMinor, verRevision ); + + m_bInitialized = true; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPhonemeExtractorLipSinc::ShutdownLipSinc( void ) +{ + // HACK HACK: This seems to crash on exit sometimes + __try + { + talkback->TalkBackShutdownLibrary(); + + FreeLibrary( m_hHelper ); + } + __except(EXCEPTION_EXECUTE_HANDLER ) + { + OutputDebugString( "----> Crash shutting down TALKBACK sdk, exception caught and ignored\n" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : inwords - +// Output : char const +//----------------------------------------------------------------------------- +char const *CPhonemeExtractorLipSinc::ConstructInputSentence( CSentence& inwords ) +{ + static char sentence[ 16384 ]; + + sentence[ 0 ] = 0; + + int last = inwords.m_Words.Size() - 1; + + for ( int i = 0 ; i <= last; i++ ) + { + CWordTag *w = inwords.m_Words[ i ]; + + strcat( sentence, w->GetWord() ); + if ( i != last ) + { + strcat( sentence, " " ); + } + } + + if ( inwords.m_Words.Count() == 1 && + !Q_strnicmp( inwords.GetText(), TEXTLESS_WORDNAME, Q_strlen( TEXTLESS_WORDNAME ) ) ) + { + sentence[ 0 ] = 0; + } + + return sentence; +} + +bool CPhonemeExtractorLipSinc::AttemptAnalysis( TALKBACK_ANALYSIS **ppAnalysis, char const *wavfile, CSentence& inwords ) +{ + *ppAnalysis = NULL; + + TALKBACK_ANALYSIS_SETTINGS settings; + memset( &settings, 0, sizeof( settings ) ); + + // Set this field to sizeof(TALKBACK_ANALYSIS_SETTINGS) before using the + // structure. + settings.fSize = sizeof( TALKBACK_ANALYSIS_SETTINGS ); + + + // Default value: 30 (frames per second). + settings.fFrameRate = 100; + // Set this to 1 to optimize for flipbook output, 0 to do analysis normally. + // + // Default value: 0 (normal analysis). + settings.fOptimizeForFlipbook = 0; + // Set this to -1 to seed the random number generator with the current time. + // Any other number will be used directly for the random number seed, which + // is useful if you want repeatable speech gestures. This value does not + // influence lip-synching at all. + // + // Default value: -1 (use current time). + settings.fRandomSeed = -1; + // Path to the configuration (.INI) file with phoneme-to-speech-target + // mapping. Set this to NULL to use the default mapping. + // + // Default value: NULL (use default mapping). + settings.fConfigFile = NULL; + + char const *text = ConstructInputSentence( inwords ); + + Printf( "Analyzing: \"%s\"\n", text[ 0 ] ? text : TEXTLESS_WORDNAME ); + + TALKBACK_ERR err = talkback->TalkBackGetAnalysis( + ppAnalysis, + wavfile, + text, + &settings ); + + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return false; + } + + Printf( "Analysis successful...\n" ); + + return true; +} + +typedef struct +{ + TALKBACK_PHONEME phoneme; + char const *string; +} TBPHONEMES_t; + +static TBPHONEMES_t g_TBPhonemeList[]= +{ + { TALKBACK_PHONEME_IY, "iy" }, + { TALKBACK_PHONEME_IH, "ih" }, + { TALKBACK_PHONEME_EH, "eh" }, + { TALKBACK_PHONEME_EY, "ey" }, + { TALKBACK_PHONEME_AE, "ae" }, + { TALKBACK_PHONEME_AA, "aa" }, + { TALKBACK_PHONEME_AW, "aw" }, + { TALKBACK_PHONEME_AY, "ay" }, + { TALKBACK_PHONEME_AH, "ah" }, + { TALKBACK_PHONEME_AO, "ao" }, + { TALKBACK_PHONEME_OY, "oy" }, + { TALKBACK_PHONEME_OW, "ow" }, + { TALKBACK_PHONEME_UH, "uh" }, + { TALKBACK_PHONEME_UW, "uw" }, + { TALKBACK_PHONEME_ER, "er" }, + { TALKBACK_PHONEME_AX, "ax" }, + { TALKBACK_PHONEME_S, "s" }, + { TALKBACK_PHONEME_SH, "sh" }, + { TALKBACK_PHONEME_Z, "z" }, + { TALKBACK_PHONEME_ZH, "zh" }, + { TALKBACK_PHONEME_F, "f" }, + { TALKBACK_PHONEME_TH, "th" }, + { TALKBACK_PHONEME_V, "v" }, + { TALKBACK_PHONEME_DH, "dh" }, + { TALKBACK_PHONEME_M, "m" }, + { TALKBACK_PHONEME_N, "n" }, + { TALKBACK_PHONEME_NG, "ng" }, + { TALKBACK_PHONEME_L, "l" }, + { TALKBACK_PHONEME_R, "r" }, + { TALKBACK_PHONEME_W, "w" }, + { TALKBACK_PHONEME_Y, "y" }, + { TALKBACK_PHONEME_HH, "hh" }, + { TALKBACK_PHONEME_B, "b" }, + { TALKBACK_PHONEME_D, "d" }, + { TALKBACK_PHONEME_JH, "jh" }, + { TALKBACK_PHONEME_G, "g" }, + { TALKBACK_PHONEME_P, "p" }, + { TALKBACK_PHONEME_T, "t" }, + { TALKBACK_PHONEME_K, "k" }, + { TALKBACK_PHONEME_CH, "ch" }, + { TALKBACK_PHONEME_SIL, "" }, + { -1, NULL } +}; + +char const *TBPhonemeToString( TALKBACK_PHONEME phoneme ) +{ + if ( phoneme < TALKBACK_PHONEME_FIRST || phoneme > TALKBACK_PHONEME_LAST ) + { + return "Bogus"; + } + + TBPHONEMES_t *item = &g_TBPhonemeList[ phoneme ]; + return item->string; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *analysis - +// time - +// start - +// Output : int +//----------------------------------------------------------------------------- +int CPhonemeExtractorLipSinc::GetPhonemeIndexAtWord( TALKBACK_ANALYSIS *analysis, double time, bool start ) +{ + long count; + + TALKBACK_ERR err = talkback->TalkBackGetNumPhonemes( analysis, &count ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return -1; + } + + if ( count <= 0L ) + return -1; + + // Bogus + if ( count >= 100000L ) + return -1; + + for ( int i = 0; i < (int)count; i++ ) + { + TALKBACK_PHONEME tbPhoneme = TALKBACK_PHONEME_INVALID; + err = talkback->TalkBackGetPhonemeEnum( analysis, i, &tbPhoneme ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + continue; + } + + double t; + + if ( start ) + { + err = talkback->TalkBackGetPhonemeStartTime( analysis, i, &t ); + } + else + { + err = talkback->TalkBackGetPhonemeEndTime( analysis, i, &t ); + } + + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + continue; + } + + if ( t == time ) + { + return i; + } + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *analysis - +// starttime - +// Output : int +//----------------------------------------------------------------------------- +int CPhonemeExtractorLipSinc::GetPhonemeIndexAtWordStart( TALKBACK_ANALYSIS *analysis, double starttime ) +{ + return GetPhonemeIndexAtWord( analysis, starttime, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *analysis - +// endtime - +// Output : int +//----------------------------------------------------------------------------- +int CPhonemeExtractorLipSinc::GetPhonemeIndexAtWordEnd( TALKBACK_ANALYSIS *analysis, double endtime ) +{ + return GetPhonemeIndexAtWord( analysis, endtime, false ); +} + +CPhonemeExtractorLipSinc::CAnalyzedPhoneme *CPhonemeExtractorLipSinc::GetAnalyzedPhoneme( TALKBACK_ANALYSIS *analysis, int index ) +{ + static CAnalyzedPhoneme p; + + memset( &p, 0, sizeof( p ) ); + + TALKBACK_PHONEME tb; + + TALKBACK_ERR err = talkback->TalkBackGetPhonemeEnum( analysis, index, &tb ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return NULL; + } + + strcpy( p.phoneme, TBPhonemeToString( tb ) ); + + err = talkback->TalkBackGetPhonemeStartTime( analysis, index, &p.starttime ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return NULL; + } + err = talkback->TalkBackGetPhonemeEndTime( analysis, index, &p.endtime ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return NULL; + } + + return &p; +} + +CPhonemeExtractorLipSinc::CAnalyzedWord *CPhonemeExtractorLipSinc::GetAnalyzedWord( TALKBACK_ANALYSIS *analysis, int index ) +{ + static CAnalyzedWord w; + + memset( &w, 0, sizeof( w ) ); + + long chars = sizeof( w.buffer ); + + TALKBACK_ERR err = talkback->TalkBackGetWord( analysis, index, chars, w.buffer ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return NULL; + } + + err = talkback->TalkBackGetWordStartTime( analysis, index, &w.starttime ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return NULL; + } + err = talkback->TalkBackGetWordEndTime( analysis, index, &w.endtime ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return NULL; + } + + return &w; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *w1 - +// *w2 - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool FuzzyWordMatch( char const *w1, char const *w2 ) +{ + int len1 = strlen( w1 ); + int len2 = strlen( w2 ); + + int minlen = min( len1, len2 ); + + // Found a match + if ( !strnicmp( w1, w2, minlen ) ) + return true; + + int letterdiff = abs( len1 - len2 ); + // More than three letters different, don't bother + if ( letterdiff > 5 ) + return false; + + // Compute a "delta" + char *p1 = (char *)w1; + char *p2 = (char *)w2; + + CUtlVector word1; + CUtlVector word2; + + while ( *p1 ) + { + if ( V_isalpha( *p1 ) ) + { + word1.AddToTail( *p1 ); + } + p1++; + } + + while ( *p2 ) + { + if ( V_isalpha( *p2 ) ) + { + word2.AddToTail( *p2 ); + } + p2++; + } + + int i; + for ( i = 0; i < word1.Size(); i++ ) + { + char c = word1[ i ]; + + // See if c is in word 2, if so subtract it out + int idx = word2.Find( c ); + + if ( idx != word2.InvalidIndex() ) + { + word2.Remove( idx ); + } + } + + if ( word2.Size() <= letterdiff ) + return true; + + word2.RemoveAll(); + + while ( *p2 ) + { + if ( V_isalpha( *p2 ) ) + { + word2.AddToTail( *p2 ); + } + p2++; + } + + for ( i = 0; i < word2.Size(); i++ ) + { + char c = word2[ i ]; + + // See if c is in word 2, if so subtract it out + int idx = word1.Find( c ); + + if ( idx != word1.InvalidIndex() ) + { + word1.Remove( idx ); + } + } + + if ( word1.Size() <= letterdiff ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: For foreign language stuff, if inwords is empty, process anyway... +// Input : *analysis - +// outwords - +//----------------------------------------------------------------------------- +void CPhonemeExtractorLipSinc::ProcessWordsTextless( TALKBACK_ANALYSIS *analysis, CSentence& outwords ) +{ + long count; + + TALKBACK_ERR err = talkback->TalkBackGetNumPhonemes( analysis, &count ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return; + } + + CWordTag *newWord = new CWordTag; + + newWord->SetWord( TEXTLESS_WORDNAME ); + + float starttime = 0.0f; + float endtime = 1.0f; + + + for ( int i = 0; i < count; ++i ) + { + // Get phoneme and timing info + CAnalyzedPhoneme *ph = GetAnalyzedPhoneme( analysis, i ); + if ( !ph ) + continue; + + CPhonemeTag *ptag = new CPhonemeTag; + + if ( i == 0 || ( ph->starttime < starttime ) ) + { + starttime = ph->starttime; + } + + if ( i == 0 || ( ph->endtime > endtime ) ) + { + endtime = ph->endtime; + } + + ptag->SetStartTime( ph->starttime ); + ptag->SetEndTime( ph->endtime ); + + ptag->m_uiStartByte = ComputeByteFromTime( ph->starttime ); + ptag->m_uiEndByte = ComputeByteFromTime( ph->endtime ); + + ptag->SetTag( ph->phoneme ); + ptag->SetPhonemeCode( TextToPhoneme( ptag->GetTag() ) ); + + newWord->m_Phonemes.AddToTail( ptag ); + } + + newWord->m_flStartTime = starttime; + newWord->m_flEndTime = endtime; + + newWord->m_uiStartByte = ComputeByteFromTime( starttime ); + newWord->m_uiEndByte = ComputeByteFromTime( endtime ); + + outwords.Reset(); + outwords.AddWordTag( newWord ); + outwords.SetTextFromWords(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *analysis - +// inwords - +// outwords - +//----------------------------------------------------------------------------- +void CPhonemeExtractorLipSinc::ProcessWords( TALKBACK_ANALYSIS *analysis, CSentence& inwords, CSentence& outwords ) +{ + long count; + + TALKBACK_ERR err = talkback->TalkBackGetNumWords( analysis, &count ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return; + } + + if ( count <= 0L ) + { + if ( inwords.m_Words.Count() == 0 || + !Q_strnicmp( inwords.GetText(), TEXTLESS_WORDNAME, Q_strlen( TEXTLESS_WORDNAME ) ) ) + { + ProcessWordsTextless( analysis, outwords ); + } + return; + } + + // Bogus + if ( count >= 100000L ) + return; + + int inwordpos = 0; + int awordpos = 0; + + outwords.Reset(); + + char previous[ 256 ]; + previous[ 0 ] = 0; + + while ( inwordpos < inwords.m_Words.Size() ) + { + CWordTag *in = inwords.m_Words[ inwordpos ]; + + if ( awordpos >= count ) + { + // Just copy the rest over without phonemes + CWordTag *copy = new CWordTag( *in ); + + outwords.AddWordTag( copy ); + + inwordpos++; + continue; + } + + // Should never fail + CAnalyzedWord *w = GetAnalyzedWord( analysis, awordpos ); + if ( !w ) + { + return; + } + + if ( !stricmp( w->buffer, "" ) ) + { + awordpos++; + continue; + } + + char const *check = ApplyTBWordRules( in->GetWord() ); + if ( !FuzzyWordMatch( check, w->buffer ) ) + { + bool advance_input = true; + if ( previous[ 0 ] ) + { + if ( FuzzyWordMatch( previous, w->buffer ) ) + { + advance_input = false; + } + } + + if ( advance_input ) + { + inwordpos++; + } + awordpos++; + continue; + } + strcpy( previous, check ); + + CWordTag *newWord = new CWordTag; + + newWord->SetWord( in->GetWord() ); + + newWord->m_flStartTime = w->starttime; + newWord->m_flEndTime = w->endtime; + + newWord->m_uiStartByte = ComputeByteFromTime( w->starttime ); + newWord->m_uiEndByte = ComputeByteFromTime( w->endtime ); + + int phonemestart, phonemeend; + + phonemestart = GetPhonemeIndexAtWordStart( analysis, w->starttime ); + phonemeend = GetPhonemeIndexAtWordEnd( analysis, w->endtime ); + + if ( phonemestart >= 0 && phonemeend >= 0 ) + { + for ( ; phonemestart <= phonemeend; phonemestart++ ) + { + // Get phoneme and timing info + CAnalyzedPhoneme *ph = GetAnalyzedPhoneme( analysis, phonemestart ); + if ( !ph ) + continue; + + CPhonemeTag *ptag = new CPhonemeTag; + ptag->SetStartTime( ph->starttime ); + ptag->SetEndTime( ph->endtime ); + + ptag->m_uiStartByte = ComputeByteFromTime( ph->starttime ); + ptag->m_uiEndByte = ComputeByteFromTime( ph->endtime ); + + ptag->SetTag( ph->phoneme ); + ptag->SetPhonemeCode( TextToPhoneme( ptag->GetTag() ) ); + + newWord->m_Phonemes.AddToTail( ptag ); + } + } + + outwords.AddWordTag( newWord ); + inwordpos++; + awordpos++; + } +} + +char const *CPhonemeExtractorLipSinc::ApplyTBWordRules( char const *word ) +{ + static char outword[ 256 ]; + + char const *in = word; + char *out = outword; + + while ( *in && ( ( out - outword ) <= 255 ) ) + { + if ( *in == '\t' || + *in == ' ' || + *in == '\n' || + *in == '-' || + *in == '.' || + *in == ',' || + *in == ';' || + *in == '?' || + *in == '"' || + *in == ':' || + *in == '(' || + *in == ')' ) + { + in++; + *out++ = ' '; + continue; + } + + if ( !V_isprint( *in ) ) + { + in++; + continue; + } + + if ( *in >= 128 ) + { + in++; + continue; + } + + // Skip numbers + if ( *in >= '0' && *in <= '9' ) + { + in++; + continue; + } + + // Convert all letters to upper case + if ( *in >= 'a' && *in <= 'z' ) + { + *out++ = ( *in++ ) - 'a' + 'A'; + continue; + } + + if ( *in >= 'A' && *in <= 'Z' ) + { + *out++ = *in++; + continue; + } + + if ( *in == '\'' ) + { + *out++ = *in++; + continue; + } + + in++; + } + + *out = 0; + + return outword; +} + +//----------------------------------------------------------------------------- +// Purpose: Given a wavfile and a list of inwords, determines the word/phonene +// sample counts for the sentce +// Output : SR_RESULT +//----------------------------------------------------------------------------- +SR_RESULT CPhonemeExtractorLipSinc::Extract( + const char *wavfile, + int numsamples, + void (*pfnPrint)( const char *fmt, ... ), + CSentence& inwords, + CSentence& outwords ) +{ + // g_enableTalkBackDebuggingOutput = 1; + + m_pfnPrint = pfnPrint; + + if ( !InitLipSinc() ) + { + return SR_RESULT_ERROR; + } + + m_flSampleCount = numsamples; + + if ( !CheckSoundFile( wavfile ) ) + { + FreeLibrary( m_hHelper ); + return SR_RESULT_ERROR; + } + + TALKBACK_ANALYSIS *analysis = NULL; + + if ( !AttemptAnalysis( &analysis, wavfile, inwords ) ) + { + FreeLibrary( m_hHelper ); + return SR_RESULT_FAILED; + } + + if ( strlen( inwords.GetText() ) <= 0 ) + { + inwords.SetTextFromWords(); + } + + outwords = inwords; + + // Examine data + ProcessWords( analysis, inwords, outwords ); + + if ( analysis ) + { + talkback->TalkBackFreeAnalysis( &analysis ); + } + + return SR_RESULT_SUCCESS; +} + EXPOSE_SINGLE_INTERFACE( CPhonemeExtractorLipSinc, IPhonemeExtractor, VPHONEME_EXTRACTOR_INTERFACE ); \ No newline at end of file diff --git a/mp/src/utils/phonemeextractor/phonemeextractor_ims.vpc b/mp/src/utils/phonemeextractor/phonemeextractor_ims.vpc index e3df0327..72d7a82b 100644 --- a/mp/src/utils/phonemeextractor/phonemeextractor_ims.vpc +++ b/mp/src/utils/phonemeextractor/phonemeextractor_ims.vpc @@ -1,98 +1,98 @@ -//----------------------------------------------------------------------------- -// PHONEMEEXTRACTOR_IMS.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin\phonemeextractors" - -$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" - -$Configuration -{ - $Compiler - { - $AdditionalIncludeDirectories "$BASE;../common,../hlfaceposer,../sapi51/include" - $PreprocessorDefinitions "$BASE;PHONEMEEXTRACTOR_EXPORTS" - } - - $Linker - { - $AdditionalDependencies "$BASE odbc32.lib odbccp32.lib" - } -} - -$Configuration "Debug" -{ - $General - { - $OutputDirectory ".\Debug_ims" [$WIN32] - $IntermediateDirectory ".\Debug_ims" [$WIN32] - } -} - -$Configuration "Release" -{ - $General - { - $OutputDirectory ".\Release_ims" [$WIN32] - $IntermediateDirectory ".\Release_ims" [$WIN32] - } -} - -$Project "Phonemeextractor_ims" -{ - $Folder "Source Files" - { - $File "extractor_utils.cpp" - $File "$SRCDIR\public\phonemeconverter.cpp" - $File "$SRCDIR\public\sentence.cpp" - $File "phonemeextractor_ims.cpp" - } - - $Folder "Header Files" - { - $File "talkback.h" - } - - $Folder "SAPI Header Files" - { - $File "..\sapi51\Include\sapi.h" - $File "..\sapi51\Include\sapiddk.h" - $File "..\sapi51\Include\Spddkhlp.h" - $File "..\sapi51\Include\spdebug.h" - $File "..\sapi51\Include\sperror.h" - $File "..\sapi51\Include\sphelper.h" - } - - $Folder "Public Header Files" - { - $File "$SRCDIR\public\mathlib\amd3dx.h" - $File "$SRCDIR\public\tier0\basetypes.h" - $File "$SRCDIR\public\tier0\commonmacros.h" - $File "$SRCDIR\public\tier0\dbg.h" - $File "$SRCDIR\public\tier0\fasttimer.h" - $File "$SRCDIR\public\appframework\IAppSystem.h" - $File "$SRCDIR\public\mathlib\mathlib.h" - $File "$SRCDIR\public\phonemeconverter.h" - $File "$SRCDIR\public\phonemeextractor\phonemeextractor.h" - $File "$SRCDIR\public\tier0\platform.h" - $File "$SRCDIR\public\tier0\protected_things.h" - $File "$SRCDIR\public\sentence.h" - $File "$SRCDIR\public\string_t.h" - $File "$SRCDIR\public\tier1\strtools.h" - $File "$SRCDIR\public\tier1\utllinkedlist.h" - $File "$SRCDIR\public\tier1\utlmemory.h" - $File "$SRCDIR\public\tier1\utlvector.h" - $File "$SRCDIR\public\mathlib\vector.h" - $File "$SRCDIR\public\mathlib\vector2d.h" - $File "$SRCDIR\public\vstdlib\vstdlib.h" - } - - $Folder "Link Libraries" - { - $Lib mathlib - $File "..\sapi51\lib\i386\sapi.lib" - } -} +//----------------------------------------------------------------------------- +// PHONEMEEXTRACTOR_IMS.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin\phonemeextractors" + +$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE;../common,../hlfaceposer,../sapi51/include" + $PreprocessorDefinitions "$BASE;PHONEMEEXTRACTOR_EXPORTS" + } + + $Linker + { + $AdditionalDependencies "$BASE odbc32.lib odbccp32.lib" + } +} + +$Configuration "Debug" +{ + $General + { + $OutputDirectory ".\Debug_ims" [$WIN32] + $IntermediateDirectory ".\Debug_ims" [$WIN32] + } +} + +$Configuration "Release" +{ + $General + { + $OutputDirectory ".\Release_ims" [$WIN32] + $IntermediateDirectory ".\Release_ims" [$WIN32] + } +} + +$Project "Phonemeextractor_ims" +{ + $Folder "Source Files" + { + $File "extractor_utils.cpp" + $File "$SRCDIR\public\phonemeconverter.cpp" + $File "$SRCDIR\public\sentence.cpp" + $File "phonemeextractor_ims.cpp" + } + + $Folder "Header Files" + { + $File "talkback.h" + } + + $Folder "SAPI Header Files" + { + $File "..\sapi51\Include\sapi.h" + $File "..\sapi51\Include\sapiddk.h" + $File "..\sapi51\Include\Spddkhlp.h" + $File "..\sapi51\Include\spdebug.h" + $File "..\sapi51\Include\sperror.h" + $File "..\sapi51\Include\sphelper.h" + } + + $Folder "Public Header Files" + { + $File "$SRCDIR\public\mathlib\amd3dx.h" + $File "$SRCDIR\public\tier0\basetypes.h" + $File "$SRCDIR\public\tier0\commonmacros.h" + $File "$SRCDIR\public\tier0\dbg.h" + $File "$SRCDIR\public\tier0\fasttimer.h" + $File "$SRCDIR\public\appframework\IAppSystem.h" + $File "$SRCDIR\public\mathlib\mathlib.h" + $File "$SRCDIR\public\phonemeconverter.h" + $File "$SRCDIR\public\phonemeextractor\phonemeextractor.h" + $File "$SRCDIR\public\tier0\platform.h" + $File "$SRCDIR\public\tier0\protected_things.h" + $File "$SRCDIR\public\sentence.h" + $File "$SRCDIR\public\string_t.h" + $File "$SRCDIR\public\tier1\strtools.h" + $File "$SRCDIR\public\tier1\utllinkedlist.h" + $File "$SRCDIR\public\tier1\utlmemory.h" + $File "$SRCDIR\public\tier1\utlvector.h" + $File "$SRCDIR\public\mathlib\vector.h" + $File "$SRCDIR\public\mathlib\vector2d.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + } + + $Folder "Link Libraries" + { + $Lib mathlib + $File "..\sapi51\lib\i386\sapi.lib" + } +} diff --git a/mp/src/utils/phonemeextractor/talkback.h b/mp/src/utils/phonemeextractor/talkback.h index 3a1b179a..bb6ee808 100644 --- a/mp/src/utils/phonemeextractor/talkback.h +++ b/mp/src/utils/phonemeextractor/talkback.h @@ -1,732 +1,732 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// ============================================================================= -// Interface to the LIPSinc TalkBack 1.1 library (TalkBack_*.lib). -// -// Copyright © 1998-2002 LIPSinc. All rights reserved. - -#if !defined(TalkBack_h) -#define TalkBack_h - -#include // size_t. - -// Enforce a C API. -#if defined(__cplusplus) -extern "C" -{ -#endif - -// ----------------------------------------------------------------------------- -// Use the preprocessor to make the new API compatible with the old one. - -#define TalkbackStartupLibrary TalkBackStartupLibrary -#define TalkbackShutdownLibrary TalkBackShutdownLibrary -#define TalkbackGetVersion TalkBackGetVersion -#define TalkbackGetVersionString TalkBackGetVersionString -#define TalkbackCheckSoundFile TalkBackCheckSoundFile -#define TalkbackCheckSpokenText TalkBackCheckSpokenText -#define TalkbackGetErrorString TalkBackGetErrorString -#define TalkbackGetAnalysis TalkBackGetAnalysis -#define TalkbackFreeAnalysis TalkBackFreeAnalysis -#define TalkbackGetFirstFrameNum TalkBackGetFirstFrameNum -#define TalkbackGetLastFrameNum TalkBackGetLastFrameNum -#define TalkbackGetFrameStartTime TalkBackGetFrameStartTime -#define TalkbackGetFrameEndTime TalkBackGetFrameEndTime -#define TalkbackGetNumPhonemes TalkBackGetNumPhonemes -#define TalkbackGetPhonemeEnum TalkBackGetPhonemeEnum -#define TalkbackGetPhonemeStartTime TalkBackGetPhonemeStartTime -#define TalkbackGetPhonemeEndTime TalkBackGetPhonemeEndTime -#define TalkbackInsertPhoneme TalkBackInsertPhoneme -#define TalkbackDeletePhoneme TalkBackDeletePhoneme -#define TalkbackChangePhonemeStart TalkBackChangePhonemeStart -#define TalkbackChangePhonemeEnd TalkBackChangePhonemeEnd -#define TalkbackChangePhonemeEnum TalkBackChangePhonemeEnum -#define TalkbackGetNumWords TalkBackGetNumWords -#define TalkbackGetWord TalkBackGetWord -#define TalkbackGetWordStartTime TalkBackGetWordStartTime -#define TalkbackGetWordEndTime TalkBackGetWordEndTime -#define TalkbackGetNumSpeechTargetTracks TalkBackGetNumSpeechTargetTracks -#define TalkbackGetNumSpeechTargetKeys TalkBackGetNumSpeechTargetKeys -#define TalkbackGetSpeechTargetKeyInfo TalkBackGetSpeechTargetKeyInfo -#define TalkbackGetSpeechTargetValueAtFrame TalkBackGetSpeechTargetValueAtFrame -#define TalkbackGetDominantSpeechTargetAtFrame TalkBackGetDominantSpeechTargetAtFrame -#define TalkbackGetSpeechTargetValueAtTime TalkBackGetSpeechTargetValueAtTime -#define TalkbackGetSpeechTargetDerivativesAtTime TalkBackGetSpeechTargetDerivativesAtTime -#define TalkbackGetNumGestureTracks TalkBackGetNumGestureTracks -#define TalkbackGetNumGestureKeys TalkBackGetNumGestureKeys -#define TalkbackGetGestureKeyInfo TalkBackGetGestureKeyInfo -#define TalkbackGetGestureValueAtFrame TalkBackGetGestureValueAtFrame -#define TalkbackGetGestureValueAtTime TalkBackGetGestureValueAtTime -#define TalkbackGetGestureDerivativesAtTime TalkBackGetGestureDerivativesAtTime - -// ----------------------------------------------------------------------------- -// For debug builds, set this to a non-zero value to get verbose debugging -// output from TalkBack. - -extern int g_enableTalkBackDebuggingOutput; - -// ----------------------------------------------------------------------------- -// Miscellaneous constants. - -// For calling TalkBackGetAnalysis() with all defaults. -#define TALKBACK_DEFAULT_SETTINGS NULL - -// For setting the iSoundText parameter in TalkBackGetAnalysis() to "no text." -#define TALKBACK_NO_TEXT NULL - -// Handy constants for TALKBACK_ANALYSIS_SETTINGS fields: - - // For setting fSize. -#define TALKBACK_SETTINGS_SIZE sizeof(TALKBACK_ANALYSIS_SETTINGS) - // For setting fFrameRate to the - // default. -#define TALKBACK_DEFAULT_FRAME_RATE 30 - // For setting fOptimizeForFlipbook - // to *not* optimize for flipbook. -#define TALKBACK_OPTIMIZE_FOR_FLIPBOOK_OFF 0 - // For setting fOptimizeForFlipbook - // to optimize for flipbook. -#define TALKBACK_OPTIMIZE_FOR_FLIPBOOK_ON 1 - // For setting fRandomSeed to use the - // current time to seed the random - // number generator and thereby get - // non-deterministic speech gestures. -#define TALKBACK_RANDOM_SEED -1 - // For setting fConfigFile to "no - // config file." -#define TALKBACK_NO_CONFIG_FILE NULL - -// ----------------------------------------------------------------------------- -// Data types. - -// TALKBACK_NOERR if successful, TalkBack error code if not. -typedef long TALKBACK_ERR; - -// Opaque analysis results. -typedef void TALKBACK_ANALYSIS; - -// Speech target. -typedef long TALKBACK_SPEECH_TARGET; - -// Speech gesture. -typedef long TALKBACK_GESTURE; - -// Phoneme. -typedef long TALKBACK_PHONEME; - -// ----------------------------------------------------------------------------- -// Data structures. - -#pragma pack(push, 1) - -// Optional analysis settings passed to TalkBackGetAnalysis(). -typedef struct -{ - // Set this field to sizeof(TALKBACK_ANALYSIS_SETTINGS) before using the - // structure. - long fSize; - // Frame rate for analysis. This only matters if you will be using *AtFrame - // functions. - // - // Default value: 30 (frames per second). - long fFrameRate; - // Set this to 1 to optimize for flipbook output, 0 to do analysis normally. - // - // Default value: 0 (normal analysis). - long fOptimizeForFlipbook; - // Set this to -1 to seed the random number generator with the current time. - // Any other number will be used directly for the random number seed, which - // is useful if you want repeatable speech gestures. This value does not - // influence lip-synching at all. - // - // Default value: -1 (use current time). - long fRandomSeed; - // Path to the configuration (.INI) file with phoneme-to-speech-target - // mapping. Set this to NULL to use the default mapping. - // - // Default value: NULL (use default mapping). - char const *fConfigFile; -} TALKBACK_ANALYSIS_SETTINGS; - -typedef struct -{ - // Set this field to sizeof(TALKBACK_SOUND_FILE_METRICS) before using the - // structure. This will allow the structure to evolve if necessary. - size_t m_size; - // Bits per sample. - long m_bitsPerSample; - // Sample rate in Hertz. - long m_sampleRate; - // Duration of the audio in seconds. - double m_duration; - // 1 if the sound file can be analyzed, 0 if not. - long m_canBeAnalyzed; - // 1 if the sound file is clipped, 0 if not. - long m_isClipped; - // The decibel range of the sound file. - double m_decibelRange; - // A quality value for the sound file: the nominal range is 0 to 100. Try - // to keep it above 45 for good results. - int m_quality; - - // Added for version 2 of the metrics structure: - // --------------------------------------------- - // The number of channels in the sound file: 1 for mono, 2 for stereo, etc. - long m_channelCount; -} TALKBACK_SOUND_FILE_METRICS; - -#pragma pack(pop) - -// ----------------------------------------------------------------------------- -// Constants. - -// TalkBack error codes. Use TalkBackGetErrorString() to return text -// descriptions for these codes. -enum -{ - // Windows convention: set this bit to indicate an application-defined error - // code. - BIT29 = (1 << 29), - // Success (not an error). - TALKBACK_NOERR = 0, - // The first error code: useful for iterating through the error codes. - TALKBACK_ERROR_FIRST = 4201 | BIT29, - // Generic error. - TALKBACK_ERROR = TALKBACK_ERROR_FIRST, - // TalkBackStartupLibrary() failed [internal error] or was never called. - TALKBACK_STARTUP_FAILED_ERR, - // TalkBackShutdownLibrary() failed, either because - // TalkBackStartupLibrary() was never called or because - // TalkBackShutdownLibrary() has already been called. - TALKBACK_SHUTDOWN_FAILED_ERR, - // The TalkBack data files could not be found [invalid path or missing - // files]. - TALKBACK_CORE_DATA_NOT_FOUND_ERR, - // One or more of the parameters are NULL. - TALKBACK_NULL_PARAMETER_ERR, - // One or more of the parameters is invalid. - TALKBACK_INVALID_PARAMETER_ERR, - // The analysis object pointer is invalid. - TALKBACK_INVALID_ANALYSIS_ERR, - // Analysis failed [the sound file cannot be analyzed or an internal error - // occurred]. - TALKBACK_ANALYSIS_FAILED_ERR, - // One or more of the indices (track, key, frame, word, phoneme) are - // invalid (out of range). - TALKBACK_INVALID_INDEX_ERR, - // The time parameter is invalid (out of range). - TALKBACK_INVALID_TIME_ERR, - // A serious internal error occurred in TalkBack; please alert LIPSinc by - // sending mail with a description of how the error was triggered to - // talkback-support@LIPSinc.com. - TALKBACK_INTERNAL_ERR, - // Could not open the specified sound file. - TALKBACK_COULD_NOT_LOAD_SOUND_ERR, - // TalkBackStartupLibrary() has not been called. - TALKBACK_STARTUP_NOT_CALLED, - // The configuration file specified in the TALKBACK_ANALYSIS_SETTINGS - // structure is invalid. - TALKBACK_CONFIG_PARSE_ERROR, - // The last error code: useful for iterating through the error codes. - TALKBACK_ERROR_LAST = TALKBACK_CONFIG_PARSE_ERROR -}; - -// Default lip-synching track identifiers. -// -// NOTE: these track identifiers apply *only* to the default phoneme-to-track -// mapping! Consult the TalkBack Reference Guide for more details. -// -// NOTE: these values are valid *only* if you use the default mapping and are -// provided as a convenience. If you use your own mapping, these values -// are invalid and should not be used. - -enum -{ - TALKBACK_SPEECH_TARGET_INVALID = -1, - TALKBACK_SPEECH_TARGET_FIRST = 0, - TALKBACK_SPEECH_TARGET_EAT = TALKBACK_SPEECH_TARGET_FIRST, // 0 - TALKBACK_SPEECH_TARGET_EARTH, // 1 - TALKBACK_SPEECH_TARGET_IF, // 2 - TALKBACK_SPEECH_TARGET_OX, // 3 - TALKBACK_SPEECH_TARGET_OAT, // 4 - TALKBACK_SPEECH_TARGET_WET, // 5 - TALKBACK_SPEECH_TARGET_SIZE, // 6 - TALKBACK_SPEECH_TARGET_CHURCH, // 7 - TALKBACK_SPEECH_TARGET_FAVE, // 8 - TALKBACK_SPEECH_TARGET_THOUGH, // 9 - TALKBACK_SPEECH_TARGET_TOLD, // 10 - TALKBACK_SPEECH_TARGET_BUMP, // 11 - TALKBACK_SPEECH_TARGET_NEW, // 12 - TALKBACK_SPEECH_TARGET_ROAR, // 13 - TALKBACK_SPEECH_TARGET_CAGE, // 14 - TALKBACK_SPEECH_TARGET_LAST = TALKBACK_SPEECH_TARGET_CAGE, // 14 - TALKBACK_NUM_SPEECH_TARGETS // 15 (0..14) -}; - -// Speech gesture track identifiers. - -enum -{ - TALKBACK_GESTURE_INVALID = -1, - TALKBACK_GESTURE_FIRST = 0, - TALKBACK_GESTURE_EYEBROW_RAISE_LEFT = TALKBACK_GESTURE_FIRST, // 0 - TALKBACK_GESTURE_EYEBROW_RAISE_RIGHT, // 1 - TALKBACK_GESTURE_BLINK_LEFT, // 2 - TALKBACK_GESTURE_BLINK_RIGHT, // 3 - TALKBACK_GESTURE_HEAD_BEND, // 4 - TALKBACK_GESTURE_HEAD_SIDE_SIDE, // 5 - TALKBACK_GESTURE_HEAD_TWIST, // 6 - TALKBACK_GESTURE_EYE_SIDE_SIDE_LEFT, // 7 - TALKBACK_GESTURE_EYE_SIDE_SIDE_RIGHT, // 8 - TALKBACK_GESTURE_EYE_UP_DOWN_LEFT, // 9 - TALKBACK_GESTURE_EYE_UP_DOWN_RIGHT, // 10 - TALKBACK_GESTURE_LAST = TALKBACK_GESTURE_EYE_UP_DOWN_RIGHT, // 10 - TALKBACK_NUM_GESTURES // 11 (0..10) -}; - -// Phoneme identifiers. - -enum -{ - TALKBACK_PHONEME_INVALID = -1, - TALKBACK_PHONEME_FIRST = 0, - TALKBACK_PHONEME_IY = TALKBACK_PHONEME_FIRST, // 0 - TALKBACK_PHONEME_IH, // 1 - TALKBACK_PHONEME_EH, // 2 - TALKBACK_PHONEME_EY, // 3 - TALKBACK_PHONEME_AE, // 4 - TALKBACK_PHONEME_AA, // 5 - TALKBACK_PHONEME_AW, // 6 - TALKBACK_PHONEME_AY, // 7 - TALKBACK_PHONEME_AH, // 8 - TALKBACK_PHONEME_AO, // 9 - TALKBACK_PHONEME_OY, // 10 - TALKBACK_PHONEME_OW, // 11 - TALKBACK_PHONEME_UH, // 12 - TALKBACK_PHONEME_UW, // 13 - TALKBACK_PHONEME_ER, // 14 - TALKBACK_PHONEME_AX, // 15 - TALKBACK_PHONEME_S, // 16 - TALKBACK_PHONEME_SH, // 17 - TALKBACK_PHONEME_Z, // 18 - TALKBACK_PHONEME_ZH, // 19 - TALKBACK_PHONEME_F, // 20 - TALKBACK_PHONEME_TH, // 21 - TALKBACK_PHONEME_V, // 22 - TALKBACK_PHONEME_DH, // 23 - TALKBACK_PHONEME_M, // 24 - TALKBACK_PHONEME_N, // 25 - TALKBACK_PHONEME_NG, // 26 - TALKBACK_PHONEME_L, // 27 - TALKBACK_PHONEME_R, // 28 - TALKBACK_PHONEME_W, // 29 - TALKBACK_PHONEME_Y, // 30 - TALKBACK_PHONEME_HH, // 31 - TALKBACK_PHONEME_B, // 32 - TALKBACK_PHONEME_D, // 33 - TALKBACK_PHONEME_JH, // 34 - TALKBACK_PHONEME_G, // 35 - TALKBACK_PHONEME_P, // 36 - TALKBACK_PHONEME_T, // 37 - TALKBACK_PHONEME_K, // 38 - TALKBACK_PHONEME_CH, // 39 - TALKBACK_PHONEME_SIL, // 40 - TALKBACK_PHONEME_LAST = TALKBACK_PHONEME_SIL, // 40 - TALKBACK_NUM_PHONEMES // 41 (0..40) -}; - -// ----------------------------------------------------------------------------- -// Function declarations. - -// --------------------------- -// Startup/shutdown functions. -// --------------------------- - -// Must be the first function called when using TalkBack. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackStartupLibrary( - char const *iCoreDataDir); // IN: full path of folder containing TalkBack data files. - -// Should be the last function called when using TalkBack. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackShutdownLibrary(); // IN: nothing. - -// ------------------ -// Version functions. -// ------------------ - -// Gets the TalkBack version number. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetVersion( - long *oMajor, // OUT: major version number. - long *oMinor, // OUT: minor version number. - long *oRevision); // OUT: revision version number. - -// Gets the TalkBack version number as a string. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetVersionString( - long iMaxChars, // IN: size of version string buffer. - char *oVersion); // OUT: version string buffer. - -// ------------------ -// Utility functions. -// ------------------ - -// Checks whether a sound file can be analyzed and returns some quality metrics. -// -// NOTE: this function is deprecated and has been supplanted by -// TalkBackGetSoundFileMetrics(). -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackCheckSoundFile( - char const *iSoundFileName, // IN: name of sound file to be checked. - long *oCanBeAnalyzed, // OUT: 1 if sound can be analyzed, 0 if not. - long *oIsClipped, // OUT: 1 if sound is clipped, 0 if not. - double *oDecibelRange); // OUT: used decibel range of sound. - -// Returns metrics for the specified sound file. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetSoundFileMetrics( - char const *iSoundFileName, // IN: name of sound file to be checked. - TALKBACK_SOUND_FILE_METRICS *ioMetrics); // IN/OUT: address of a structure where the metrics will be stored. - -// Checks whether text can be used for text-based analysis, returning the text -// as it will be analyzed. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackCheckSpokenText( - char const *iSpokenText, // IN: text to check. - long iMaxChars, // IN: size of analyzed text buffer. - char *oAnalyzedText); // OUT: buffer for text as it will be analyzed. - -// Convert a TalkBack error code to a description string. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetErrorString( - TALKBACK_ERR iErrorCode, // IN: TalkBack error code to convert. - long iMaxChars, // IN: size of the buffer. - char *oErrorString); // OUT: buffer for the description string. - -// Gets the error code and text for the most recent TalkBack error. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetLastError( - long iMaxChars, // IN: size of the buffer. - char *oErrorString, // OUT: buffer for the description string. - TALKBACK_ERR *oErrorCode); // OUT: most recent TalkBack error code. - -// ------------------- -// Analysis functions. -// ------------------- - -// Gets an opaque TALKBACK_ANALYSIS object. This object is then queried with the -// TalkBackGet* functions below. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetAnalysis( - TALKBACK_ANALYSIS **ioAnalysis, // IN/OUT: address of a TALKBACK_ANALYSIS *variable where analysis will be stored. - char const *iSoundFileName, // IN: name of the sound file to analyze. - char const *iSoundText, // IN: text spoken in sound file (can be NULL to use textless analysis). - TALKBACK_ANALYSIS_SETTINGS *iSettings); // IN: pointer to a TALKBACK_ANALYSIS_SETTINGS structure (can be NULL for defaults). - -// Frees an opaque TALKBACK_ANALYSIS object. This releases all memory used by -// the analysis. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackFreeAnalysis( - TALKBACK_ANALYSIS **ioAnalysis); // IN/OUT: analysis to free. - -// ####################################################################### -// NOTE: all functions from this point on require a valid analysis object. -// ####################################################################### - -// ------------------------ -// Speech target functions. -// ------------------------ - -// Gets the number of speech target tracks. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetNumSpeechTargetTracks( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long *oResult); // OUT: number of speech target tracks. - -// Gets the number of keys in the specified speech target track. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetNumSpeechTargetKeys( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iTrackNum, // IN: speech target track. - long *oResult); // OUT: number of keys in the speech target track. - -// Gets key information (time, value, derivative in, and derivative out) for the -// specified key in the specified speech target track. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetSpeechTargetKeyInfo( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iTrackNum, // IN: speech target track. - long iKeyNum, // IN: speech target key. - double *oTime, // OUT: time of key. - double *oValue, // OUT: value of key. - double *oDerivativeIn, // OUT: incoming derivative of key. - double *oDerivativeOut); // OUT: outgoing derivative of key. - -// Gets the value of the function curve for the specified speech target track at -// the specified time. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetSpeechTargetValueAtTime( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iTrackNum, // IN: speech target track. - double iTime, // IN: time in seconds. - double *oResult); // OUT: value of the function curve. - -// Gets the derivatives of the function curve for the specified speech target -// track at the specified time. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetSpeechTargetDerivativesAtTime( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iTrackNum, // IN: speech target track. - double iTime, // IN: time in seconds. - double *oDerivativeIn, // OUT: value of the incoming derivative of the function curve. - double *oDerivativeOut); // OUT: value of the outgoing derivative of the function curve. - -// ------------------------- -// Speech gesture functions. -// ------------------------- - -// Gets the number of speech gesture tracks. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetNumGestureTracks( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long *oResult); // OUT: number of speech gesture tracks - -// Gets the number of keys in the specified speech gesture track. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetNumGestureKeys( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iTrackNum, // IN: speech gesture track. - long *oResult); // OUT: number of keys in the speech gesture track. - -// Gets key information (time, value, derivative in, and derivative out) for the -// specified key in the specified speech gesture track. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetGestureKeyInfo( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iTrackNum, // IN: speech gesture track. - long iKeyNum, // IN: speech gesture key. - double *oTime, // OUT: time of key. - double *oValue, // OUT: value of key. - double *oDerivativeIn, // OUT: incoming derivative of key. - double *oDerivativeOut); // OUT: outgoing derivative of key. - -// Gets the value of the function curve for the specified speech gesture track -// at the specified time. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetGestureValueAtTime( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iTrackNum, // IN: speech gesture track. - double iTime, // IN: time in seconds. - double *oResult); // OUT: value of the function curve. - -// Gets the derivatives of the function curve for the specified speech gesture -// track at the specified time. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetGestureDerivativesAtTime( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iTrackNum, // IN: speech gesture track. - double iTime, // IN: time in seconds. - double *oDerivativeIn, // OUT: value of the incoming derivative of the function curve. - double *oDerivativeOut); // OUT: value of the outgoing derivative of the function curve. - -// ---------------- -// Frame functions. -// ---------------- - -// NOTE: these functions use the frame rate specified in the -// TALKBACK_ANALYSIS_SETTINGS structure passed to TalkBackGetAnalysis() and -// default to 30 fps (TALKBACK_DEFAULT_FRAME_RATE) if the structure pointer was -// NULL. - -// Gets the first frame number. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetFirstFrameNum( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long *oResult); // OUT: number of the first frame. - -// Gets the last frame number. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetLastFrameNum( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long *oResult); // OUT: number of the last frame. - -// Gets the start time of the specified frame. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetFrameStartTime( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iFrameNum, // IN: frame. - double *oResult); // OUT: start time of the frame in seconds. - -// Gets the end time of the specified frame. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetFrameEndTime( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iFrameNum, // IN: frame. - double *oResult); // OUT: end time of the frame in seconds. - -// Gets the value of the function curve for a speech target integrated over the -// specified frame. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetSpeechTargetValueAtFrame( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iTrackNum, // IN: speech target track. - long iFrameNum, // IN: frame number. - double *oResult); // OUT: value of the function curve integrated over the frame. - -// Gets the dominant speech target at the specified frame. -// -// NOTE: this function is meant to be used in flipbook mode only. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetDominantSpeechTargetAtFrame( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iFrameNum, // IN: frame number. - TALKBACK_SPEECH_TARGET *oSpeechTarget); // OUT: dominant speech target. - -// Gets the value of the function curve for a speech gesture integrated over the -// specified frame. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetGestureValueAtFrame( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iTrackNum, // IN: speech gesture track. - long iFrameNum, // IN: frame number. - double *oResult); // OUT: value of the function curve integrated over the frame. - -// ------------------ -// Phoneme functions. -// ------------------ - -// Gets the number of phonemes. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetNumPhonemes( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long *oResult); // OUT: number of phonemes. - -// Gets the enumeration of the specified phoneme. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetPhonemeEnum( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iPhonemeNum, // IN: phoneme. - TALKBACK_PHONEME *oResult); // OUT: enumeration of the specified phoneme. - -// Gets the start time of the specified phoneme. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetPhonemeStartTime( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iPhonemeNum, // IN: phoneme. - double *oResult); // OUT: start time of the phoneme in seconds. - -// Gets the end time of the specified phoneme. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetPhonemeEndTime( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iPhonemeNum, // IN: phoneme. - double *oResult); // OUT: end time of the phoneme in seconds. - -// --------------- -// Word functions. -// --------------- - -// NOTE: these functions only yield data for text-based analysis. - -// Gets the number of words. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetNumWords( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long *oResult); // OUT: number of words. - -// Gets the text of the specified word. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetWord( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iWordNum, // IN: word. - long iMaxChars, // IN: size of word buffer. - char *oWord); // OUT: word buffer. - -// Gets the start time of the specified word. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetWordStartTime( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iWordNum, // IN: word. - double *oResult); // OUT: start time of the word in seconds. - -// Gets the end time of the specified word. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackGetWordEndTime( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iWordNum, // IN: word. - double *oResult); // OUT: end time of the word in seconds. - -// -------------------------- -// Phoneme editing functions. -// -------------------------- - -// Use these functions to modify the phoneme list after you get an opaque -// analysis object from TalkBackGetAnalysis(). After modifying the phoneme list -// in the opaque analysis object, subsequent TalkBackGet* calls on that opaque -// analysis object for speech target (lip-synching) data will return values -// based on the modified phoneme list. However, speech gesture data is not -// affected by phoneme editing. -// -// NOTE: phoneme editing is only provided in order to support Ventriloquist-like -// applications where tweaking of the phoneme segmenation (and subsequent -// recalculation of the animation data) is required. Most customers probably -// won't need this functionality. - -// Inserts a phoneme at the specified position in the specified manner. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackInsertPhoneme( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - TALKBACK_PHONEME iPhoneme, // IN: enumeration of phoneme to insert. - long iInsertPosition, // IN: position (phoneme number) at which to insert. - int iInsertBefore); // IN: manner of insertion: - // 0 means put phoneme after insert position; - // 1 means put phoneme before insert position. - -// Deletes the specified phoneme. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackDeletePhoneme( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iPhonemeToDelete); // IN: phoneme to delete. - -// Changes the start time of the specified phoneme. -// -// NOTE: the start time specified may not be the actual start time for a number -// of reasons, most notably if the specified start time will make the phoneme -// too short. This function returns the actual start time so the caller can -// check the result without having to query the phoneme. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackChangePhonemeStart( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iPhonemeToChange, // IN: phoneme to change. - double *ioNewTime); // IN/OUT: new start time value in seconds (in); actual start time (out). - -// Changes the end time of the specified phoneme. -// -// NOTE: the end time specified may not be the actual end time for a number of -// reasons, most notably if the specified end time will make the phoneme too -// short. This function returns the actual end time so the caller can check the -// result without having to query the phoneme. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackChangePhonemeEnd( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iPhonemeToChange, // IN: phoneme to change. - double *ioNewTime); // IN/OUT: new end time value in seconds (in); actual end time (out). - -// Changes the enumeration of the specified phoneme. -TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. -TalkBackChangePhonemeEnum( - TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). - long iPhonemeToChange, // IN: phoneme to change. - TALKBACK_PHONEME iNewPhoneme); // IN: new phoneme enumeration. - -#if defined(__cplusplus) -} -#endif - -#endif +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// ============================================================================= +// Interface to the LIPSinc TalkBack 1.1 library (TalkBack_*.lib). +// +// Copyright © 1998-2002 LIPSinc. All rights reserved. + +#if !defined(TalkBack_h) +#define TalkBack_h + +#include // size_t. + +// Enforce a C API. +#if defined(__cplusplus) +extern "C" +{ +#endif + +// ----------------------------------------------------------------------------- +// Use the preprocessor to make the new API compatible with the old one. + +#define TalkbackStartupLibrary TalkBackStartupLibrary +#define TalkbackShutdownLibrary TalkBackShutdownLibrary +#define TalkbackGetVersion TalkBackGetVersion +#define TalkbackGetVersionString TalkBackGetVersionString +#define TalkbackCheckSoundFile TalkBackCheckSoundFile +#define TalkbackCheckSpokenText TalkBackCheckSpokenText +#define TalkbackGetErrorString TalkBackGetErrorString +#define TalkbackGetAnalysis TalkBackGetAnalysis +#define TalkbackFreeAnalysis TalkBackFreeAnalysis +#define TalkbackGetFirstFrameNum TalkBackGetFirstFrameNum +#define TalkbackGetLastFrameNum TalkBackGetLastFrameNum +#define TalkbackGetFrameStartTime TalkBackGetFrameStartTime +#define TalkbackGetFrameEndTime TalkBackGetFrameEndTime +#define TalkbackGetNumPhonemes TalkBackGetNumPhonemes +#define TalkbackGetPhonemeEnum TalkBackGetPhonemeEnum +#define TalkbackGetPhonemeStartTime TalkBackGetPhonemeStartTime +#define TalkbackGetPhonemeEndTime TalkBackGetPhonemeEndTime +#define TalkbackInsertPhoneme TalkBackInsertPhoneme +#define TalkbackDeletePhoneme TalkBackDeletePhoneme +#define TalkbackChangePhonemeStart TalkBackChangePhonemeStart +#define TalkbackChangePhonemeEnd TalkBackChangePhonemeEnd +#define TalkbackChangePhonemeEnum TalkBackChangePhonemeEnum +#define TalkbackGetNumWords TalkBackGetNumWords +#define TalkbackGetWord TalkBackGetWord +#define TalkbackGetWordStartTime TalkBackGetWordStartTime +#define TalkbackGetWordEndTime TalkBackGetWordEndTime +#define TalkbackGetNumSpeechTargetTracks TalkBackGetNumSpeechTargetTracks +#define TalkbackGetNumSpeechTargetKeys TalkBackGetNumSpeechTargetKeys +#define TalkbackGetSpeechTargetKeyInfo TalkBackGetSpeechTargetKeyInfo +#define TalkbackGetSpeechTargetValueAtFrame TalkBackGetSpeechTargetValueAtFrame +#define TalkbackGetDominantSpeechTargetAtFrame TalkBackGetDominantSpeechTargetAtFrame +#define TalkbackGetSpeechTargetValueAtTime TalkBackGetSpeechTargetValueAtTime +#define TalkbackGetSpeechTargetDerivativesAtTime TalkBackGetSpeechTargetDerivativesAtTime +#define TalkbackGetNumGestureTracks TalkBackGetNumGestureTracks +#define TalkbackGetNumGestureKeys TalkBackGetNumGestureKeys +#define TalkbackGetGestureKeyInfo TalkBackGetGestureKeyInfo +#define TalkbackGetGestureValueAtFrame TalkBackGetGestureValueAtFrame +#define TalkbackGetGestureValueAtTime TalkBackGetGestureValueAtTime +#define TalkbackGetGestureDerivativesAtTime TalkBackGetGestureDerivativesAtTime + +// ----------------------------------------------------------------------------- +// For debug builds, set this to a non-zero value to get verbose debugging +// output from TalkBack. + +extern int g_enableTalkBackDebuggingOutput; + +// ----------------------------------------------------------------------------- +// Miscellaneous constants. + +// For calling TalkBackGetAnalysis() with all defaults. +#define TALKBACK_DEFAULT_SETTINGS NULL + +// For setting the iSoundText parameter in TalkBackGetAnalysis() to "no text." +#define TALKBACK_NO_TEXT NULL + +// Handy constants for TALKBACK_ANALYSIS_SETTINGS fields: + + // For setting fSize. +#define TALKBACK_SETTINGS_SIZE sizeof(TALKBACK_ANALYSIS_SETTINGS) + // For setting fFrameRate to the + // default. +#define TALKBACK_DEFAULT_FRAME_RATE 30 + // For setting fOptimizeForFlipbook + // to *not* optimize for flipbook. +#define TALKBACK_OPTIMIZE_FOR_FLIPBOOK_OFF 0 + // For setting fOptimizeForFlipbook + // to optimize for flipbook. +#define TALKBACK_OPTIMIZE_FOR_FLIPBOOK_ON 1 + // For setting fRandomSeed to use the + // current time to seed the random + // number generator and thereby get + // non-deterministic speech gestures. +#define TALKBACK_RANDOM_SEED -1 + // For setting fConfigFile to "no + // config file." +#define TALKBACK_NO_CONFIG_FILE NULL + +// ----------------------------------------------------------------------------- +// Data types. + +// TALKBACK_NOERR if successful, TalkBack error code if not. +typedef long TALKBACK_ERR; + +// Opaque analysis results. +typedef void TALKBACK_ANALYSIS; + +// Speech target. +typedef long TALKBACK_SPEECH_TARGET; + +// Speech gesture. +typedef long TALKBACK_GESTURE; + +// Phoneme. +typedef long TALKBACK_PHONEME; + +// ----------------------------------------------------------------------------- +// Data structures. + +#pragma pack(push, 1) + +// Optional analysis settings passed to TalkBackGetAnalysis(). +typedef struct +{ + // Set this field to sizeof(TALKBACK_ANALYSIS_SETTINGS) before using the + // structure. + long fSize; + // Frame rate for analysis. This only matters if you will be using *AtFrame + // functions. + // + // Default value: 30 (frames per second). + long fFrameRate; + // Set this to 1 to optimize for flipbook output, 0 to do analysis normally. + // + // Default value: 0 (normal analysis). + long fOptimizeForFlipbook; + // Set this to -1 to seed the random number generator with the current time. + // Any other number will be used directly for the random number seed, which + // is useful if you want repeatable speech gestures. This value does not + // influence lip-synching at all. + // + // Default value: -1 (use current time). + long fRandomSeed; + // Path to the configuration (.INI) file with phoneme-to-speech-target + // mapping. Set this to NULL to use the default mapping. + // + // Default value: NULL (use default mapping). + char const *fConfigFile; +} TALKBACK_ANALYSIS_SETTINGS; + +typedef struct +{ + // Set this field to sizeof(TALKBACK_SOUND_FILE_METRICS) before using the + // structure. This will allow the structure to evolve if necessary. + size_t m_size; + // Bits per sample. + long m_bitsPerSample; + // Sample rate in Hertz. + long m_sampleRate; + // Duration of the audio in seconds. + double m_duration; + // 1 if the sound file can be analyzed, 0 if not. + long m_canBeAnalyzed; + // 1 if the sound file is clipped, 0 if not. + long m_isClipped; + // The decibel range of the sound file. + double m_decibelRange; + // A quality value for the sound file: the nominal range is 0 to 100. Try + // to keep it above 45 for good results. + int m_quality; + + // Added for version 2 of the metrics structure: + // --------------------------------------------- + // The number of channels in the sound file: 1 for mono, 2 for stereo, etc. + long m_channelCount; +} TALKBACK_SOUND_FILE_METRICS; + +#pragma pack(pop) + +// ----------------------------------------------------------------------------- +// Constants. + +// TalkBack error codes. Use TalkBackGetErrorString() to return text +// descriptions for these codes. +enum +{ + // Windows convention: set this bit to indicate an application-defined error + // code. + BIT29 = (1 << 29), + // Success (not an error). + TALKBACK_NOERR = 0, + // The first error code: useful for iterating through the error codes. + TALKBACK_ERROR_FIRST = 4201 | BIT29, + // Generic error. + TALKBACK_ERROR = TALKBACK_ERROR_FIRST, + // TalkBackStartupLibrary() failed [internal error] or was never called. + TALKBACK_STARTUP_FAILED_ERR, + // TalkBackShutdownLibrary() failed, either because + // TalkBackStartupLibrary() was never called or because + // TalkBackShutdownLibrary() has already been called. + TALKBACK_SHUTDOWN_FAILED_ERR, + // The TalkBack data files could not be found [invalid path or missing + // files]. + TALKBACK_CORE_DATA_NOT_FOUND_ERR, + // One or more of the parameters are NULL. + TALKBACK_NULL_PARAMETER_ERR, + // One or more of the parameters is invalid. + TALKBACK_INVALID_PARAMETER_ERR, + // The analysis object pointer is invalid. + TALKBACK_INVALID_ANALYSIS_ERR, + // Analysis failed [the sound file cannot be analyzed or an internal error + // occurred]. + TALKBACK_ANALYSIS_FAILED_ERR, + // One or more of the indices (track, key, frame, word, phoneme) are + // invalid (out of range). + TALKBACK_INVALID_INDEX_ERR, + // The time parameter is invalid (out of range). + TALKBACK_INVALID_TIME_ERR, + // A serious internal error occurred in TalkBack; please alert LIPSinc by + // sending mail with a description of how the error was triggered to + // talkback-support@LIPSinc.com. + TALKBACK_INTERNAL_ERR, + // Could not open the specified sound file. + TALKBACK_COULD_NOT_LOAD_SOUND_ERR, + // TalkBackStartupLibrary() has not been called. + TALKBACK_STARTUP_NOT_CALLED, + // The configuration file specified in the TALKBACK_ANALYSIS_SETTINGS + // structure is invalid. + TALKBACK_CONFIG_PARSE_ERROR, + // The last error code: useful for iterating through the error codes. + TALKBACK_ERROR_LAST = TALKBACK_CONFIG_PARSE_ERROR +}; + +// Default lip-synching track identifiers. +// +// NOTE: these track identifiers apply *only* to the default phoneme-to-track +// mapping! Consult the TalkBack Reference Guide for more details. +// +// NOTE: these values are valid *only* if you use the default mapping and are +// provided as a convenience. If you use your own mapping, these values +// are invalid and should not be used. + +enum +{ + TALKBACK_SPEECH_TARGET_INVALID = -1, + TALKBACK_SPEECH_TARGET_FIRST = 0, + TALKBACK_SPEECH_TARGET_EAT = TALKBACK_SPEECH_TARGET_FIRST, // 0 + TALKBACK_SPEECH_TARGET_EARTH, // 1 + TALKBACK_SPEECH_TARGET_IF, // 2 + TALKBACK_SPEECH_TARGET_OX, // 3 + TALKBACK_SPEECH_TARGET_OAT, // 4 + TALKBACK_SPEECH_TARGET_WET, // 5 + TALKBACK_SPEECH_TARGET_SIZE, // 6 + TALKBACK_SPEECH_TARGET_CHURCH, // 7 + TALKBACK_SPEECH_TARGET_FAVE, // 8 + TALKBACK_SPEECH_TARGET_THOUGH, // 9 + TALKBACK_SPEECH_TARGET_TOLD, // 10 + TALKBACK_SPEECH_TARGET_BUMP, // 11 + TALKBACK_SPEECH_TARGET_NEW, // 12 + TALKBACK_SPEECH_TARGET_ROAR, // 13 + TALKBACK_SPEECH_TARGET_CAGE, // 14 + TALKBACK_SPEECH_TARGET_LAST = TALKBACK_SPEECH_TARGET_CAGE, // 14 + TALKBACK_NUM_SPEECH_TARGETS // 15 (0..14) +}; + +// Speech gesture track identifiers. + +enum +{ + TALKBACK_GESTURE_INVALID = -1, + TALKBACK_GESTURE_FIRST = 0, + TALKBACK_GESTURE_EYEBROW_RAISE_LEFT = TALKBACK_GESTURE_FIRST, // 0 + TALKBACK_GESTURE_EYEBROW_RAISE_RIGHT, // 1 + TALKBACK_GESTURE_BLINK_LEFT, // 2 + TALKBACK_GESTURE_BLINK_RIGHT, // 3 + TALKBACK_GESTURE_HEAD_BEND, // 4 + TALKBACK_GESTURE_HEAD_SIDE_SIDE, // 5 + TALKBACK_GESTURE_HEAD_TWIST, // 6 + TALKBACK_GESTURE_EYE_SIDE_SIDE_LEFT, // 7 + TALKBACK_GESTURE_EYE_SIDE_SIDE_RIGHT, // 8 + TALKBACK_GESTURE_EYE_UP_DOWN_LEFT, // 9 + TALKBACK_GESTURE_EYE_UP_DOWN_RIGHT, // 10 + TALKBACK_GESTURE_LAST = TALKBACK_GESTURE_EYE_UP_DOWN_RIGHT, // 10 + TALKBACK_NUM_GESTURES // 11 (0..10) +}; + +// Phoneme identifiers. + +enum +{ + TALKBACK_PHONEME_INVALID = -1, + TALKBACK_PHONEME_FIRST = 0, + TALKBACK_PHONEME_IY = TALKBACK_PHONEME_FIRST, // 0 + TALKBACK_PHONEME_IH, // 1 + TALKBACK_PHONEME_EH, // 2 + TALKBACK_PHONEME_EY, // 3 + TALKBACK_PHONEME_AE, // 4 + TALKBACK_PHONEME_AA, // 5 + TALKBACK_PHONEME_AW, // 6 + TALKBACK_PHONEME_AY, // 7 + TALKBACK_PHONEME_AH, // 8 + TALKBACK_PHONEME_AO, // 9 + TALKBACK_PHONEME_OY, // 10 + TALKBACK_PHONEME_OW, // 11 + TALKBACK_PHONEME_UH, // 12 + TALKBACK_PHONEME_UW, // 13 + TALKBACK_PHONEME_ER, // 14 + TALKBACK_PHONEME_AX, // 15 + TALKBACK_PHONEME_S, // 16 + TALKBACK_PHONEME_SH, // 17 + TALKBACK_PHONEME_Z, // 18 + TALKBACK_PHONEME_ZH, // 19 + TALKBACK_PHONEME_F, // 20 + TALKBACK_PHONEME_TH, // 21 + TALKBACK_PHONEME_V, // 22 + TALKBACK_PHONEME_DH, // 23 + TALKBACK_PHONEME_M, // 24 + TALKBACK_PHONEME_N, // 25 + TALKBACK_PHONEME_NG, // 26 + TALKBACK_PHONEME_L, // 27 + TALKBACK_PHONEME_R, // 28 + TALKBACK_PHONEME_W, // 29 + TALKBACK_PHONEME_Y, // 30 + TALKBACK_PHONEME_HH, // 31 + TALKBACK_PHONEME_B, // 32 + TALKBACK_PHONEME_D, // 33 + TALKBACK_PHONEME_JH, // 34 + TALKBACK_PHONEME_G, // 35 + TALKBACK_PHONEME_P, // 36 + TALKBACK_PHONEME_T, // 37 + TALKBACK_PHONEME_K, // 38 + TALKBACK_PHONEME_CH, // 39 + TALKBACK_PHONEME_SIL, // 40 + TALKBACK_PHONEME_LAST = TALKBACK_PHONEME_SIL, // 40 + TALKBACK_NUM_PHONEMES // 41 (0..40) +}; + +// ----------------------------------------------------------------------------- +// Function declarations. + +// --------------------------- +// Startup/shutdown functions. +// --------------------------- + +// Must be the first function called when using TalkBack. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackStartupLibrary( + char const *iCoreDataDir); // IN: full path of folder containing TalkBack data files. + +// Should be the last function called when using TalkBack. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackShutdownLibrary(); // IN: nothing. + +// ------------------ +// Version functions. +// ------------------ + +// Gets the TalkBack version number. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetVersion( + long *oMajor, // OUT: major version number. + long *oMinor, // OUT: minor version number. + long *oRevision); // OUT: revision version number. + +// Gets the TalkBack version number as a string. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetVersionString( + long iMaxChars, // IN: size of version string buffer. + char *oVersion); // OUT: version string buffer. + +// ------------------ +// Utility functions. +// ------------------ + +// Checks whether a sound file can be analyzed and returns some quality metrics. +// +// NOTE: this function is deprecated and has been supplanted by +// TalkBackGetSoundFileMetrics(). +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackCheckSoundFile( + char const *iSoundFileName, // IN: name of sound file to be checked. + long *oCanBeAnalyzed, // OUT: 1 if sound can be analyzed, 0 if not. + long *oIsClipped, // OUT: 1 if sound is clipped, 0 if not. + double *oDecibelRange); // OUT: used decibel range of sound. + +// Returns metrics for the specified sound file. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetSoundFileMetrics( + char const *iSoundFileName, // IN: name of sound file to be checked. + TALKBACK_SOUND_FILE_METRICS *ioMetrics); // IN/OUT: address of a structure where the metrics will be stored. + +// Checks whether text can be used for text-based analysis, returning the text +// as it will be analyzed. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackCheckSpokenText( + char const *iSpokenText, // IN: text to check. + long iMaxChars, // IN: size of analyzed text buffer. + char *oAnalyzedText); // OUT: buffer for text as it will be analyzed. + +// Convert a TalkBack error code to a description string. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetErrorString( + TALKBACK_ERR iErrorCode, // IN: TalkBack error code to convert. + long iMaxChars, // IN: size of the buffer. + char *oErrorString); // OUT: buffer for the description string. + +// Gets the error code and text for the most recent TalkBack error. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetLastError( + long iMaxChars, // IN: size of the buffer. + char *oErrorString, // OUT: buffer for the description string. + TALKBACK_ERR *oErrorCode); // OUT: most recent TalkBack error code. + +// ------------------- +// Analysis functions. +// ------------------- + +// Gets an opaque TALKBACK_ANALYSIS object. This object is then queried with the +// TalkBackGet* functions below. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetAnalysis( + TALKBACK_ANALYSIS **ioAnalysis, // IN/OUT: address of a TALKBACK_ANALYSIS *variable where analysis will be stored. + char const *iSoundFileName, // IN: name of the sound file to analyze. + char const *iSoundText, // IN: text spoken in sound file (can be NULL to use textless analysis). + TALKBACK_ANALYSIS_SETTINGS *iSettings); // IN: pointer to a TALKBACK_ANALYSIS_SETTINGS structure (can be NULL for defaults). + +// Frees an opaque TALKBACK_ANALYSIS object. This releases all memory used by +// the analysis. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackFreeAnalysis( + TALKBACK_ANALYSIS **ioAnalysis); // IN/OUT: analysis to free. + +// ####################################################################### +// NOTE: all functions from this point on require a valid analysis object. +// ####################################################################### + +// ------------------------ +// Speech target functions. +// ------------------------ + +// Gets the number of speech target tracks. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetNumSpeechTargetTracks( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long *oResult); // OUT: number of speech target tracks. + +// Gets the number of keys in the specified speech target track. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetNumSpeechTargetKeys( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech target track. + long *oResult); // OUT: number of keys in the speech target track. + +// Gets key information (time, value, derivative in, and derivative out) for the +// specified key in the specified speech target track. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetSpeechTargetKeyInfo( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech target track. + long iKeyNum, // IN: speech target key. + double *oTime, // OUT: time of key. + double *oValue, // OUT: value of key. + double *oDerivativeIn, // OUT: incoming derivative of key. + double *oDerivativeOut); // OUT: outgoing derivative of key. + +// Gets the value of the function curve for the specified speech target track at +// the specified time. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetSpeechTargetValueAtTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech target track. + double iTime, // IN: time in seconds. + double *oResult); // OUT: value of the function curve. + +// Gets the derivatives of the function curve for the specified speech target +// track at the specified time. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetSpeechTargetDerivativesAtTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech target track. + double iTime, // IN: time in seconds. + double *oDerivativeIn, // OUT: value of the incoming derivative of the function curve. + double *oDerivativeOut); // OUT: value of the outgoing derivative of the function curve. + +// ------------------------- +// Speech gesture functions. +// ------------------------- + +// Gets the number of speech gesture tracks. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetNumGestureTracks( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long *oResult); // OUT: number of speech gesture tracks + +// Gets the number of keys in the specified speech gesture track. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetNumGestureKeys( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech gesture track. + long *oResult); // OUT: number of keys in the speech gesture track. + +// Gets key information (time, value, derivative in, and derivative out) for the +// specified key in the specified speech gesture track. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetGestureKeyInfo( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech gesture track. + long iKeyNum, // IN: speech gesture key. + double *oTime, // OUT: time of key. + double *oValue, // OUT: value of key. + double *oDerivativeIn, // OUT: incoming derivative of key. + double *oDerivativeOut); // OUT: outgoing derivative of key. + +// Gets the value of the function curve for the specified speech gesture track +// at the specified time. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetGestureValueAtTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech gesture track. + double iTime, // IN: time in seconds. + double *oResult); // OUT: value of the function curve. + +// Gets the derivatives of the function curve for the specified speech gesture +// track at the specified time. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetGestureDerivativesAtTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech gesture track. + double iTime, // IN: time in seconds. + double *oDerivativeIn, // OUT: value of the incoming derivative of the function curve. + double *oDerivativeOut); // OUT: value of the outgoing derivative of the function curve. + +// ---------------- +// Frame functions. +// ---------------- + +// NOTE: these functions use the frame rate specified in the +// TALKBACK_ANALYSIS_SETTINGS structure passed to TalkBackGetAnalysis() and +// default to 30 fps (TALKBACK_DEFAULT_FRAME_RATE) if the structure pointer was +// NULL. + +// Gets the first frame number. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetFirstFrameNum( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long *oResult); // OUT: number of the first frame. + +// Gets the last frame number. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetLastFrameNum( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long *oResult); // OUT: number of the last frame. + +// Gets the start time of the specified frame. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetFrameStartTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iFrameNum, // IN: frame. + double *oResult); // OUT: start time of the frame in seconds. + +// Gets the end time of the specified frame. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetFrameEndTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iFrameNum, // IN: frame. + double *oResult); // OUT: end time of the frame in seconds. + +// Gets the value of the function curve for a speech target integrated over the +// specified frame. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetSpeechTargetValueAtFrame( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech target track. + long iFrameNum, // IN: frame number. + double *oResult); // OUT: value of the function curve integrated over the frame. + +// Gets the dominant speech target at the specified frame. +// +// NOTE: this function is meant to be used in flipbook mode only. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetDominantSpeechTargetAtFrame( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iFrameNum, // IN: frame number. + TALKBACK_SPEECH_TARGET *oSpeechTarget); // OUT: dominant speech target. + +// Gets the value of the function curve for a speech gesture integrated over the +// specified frame. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetGestureValueAtFrame( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech gesture track. + long iFrameNum, // IN: frame number. + double *oResult); // OUT: value of the function curve integrated over the frame. + +// ------------------ +// Phoneme functions. +// ------------------ + +// Gets the number of phonemes. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetNumPhonemes( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long *oResult); // OUT: number of phonemes. + +// Gets the enumeration of the specified phoneme. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetPhonemeEnum( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iPhonemeNum, // IN: phoneme. + TALKBACK_PHONEME *oResult); // OUT: enumeration of the specified phoneme. + +// Gets the start time of the specified phoneme. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetPhonemeStartTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iPhonemeNum, // IN: phoneme. + double *oResult); // OUT: start time of the phoneme in seconds. + +// Gets the end time of the specified phoneme. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetPhonemeEndTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iPhonemeNum, // IN: phoneme. + double *oResult); // OUT: end time of the phoneme in seconds. + +// --------------- +// Word functions. +// --------------- + +// NOTE: these functions only yield data for text-based analysis. + +// Gets the number of words. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetNumWords( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long *oResult); // OUT: number of words. + +// Gets the text of the specified word. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetWord( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iWordNum, // IN: word. + long iMaxChars, // IN: size of word buffer. + char *oWord); // OUT: word buffer. + +// Gets the start time of the specified word. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetWordStartTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iWordNum, // IN: word. + double *oResult); // OUT: start time of the word in seconds. + +// Gets the end time of the specified word. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetWordEndTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iWordNum, // IN: word. + double *oResult); // OUT: end time of the word in seconds. + +// -------------------------- +// Phoneme editing functions. +// -------------------------- + +// Use these functions to modify the phoneme list after you get an opaque +// analysis object from TalkBackGetAnalysis(). After modifying the phoneme list +// in the opaque analysis object, subsequent TalkBackGet* calls on that opaque +// analysis object for speech target (lip-synching) data will return values +// based on the modified phoneme list. However, speech gesture data is not +// affected by phoneme editing. +// +// NOTE: phoneme editing is only provided in order to support Ventriloquist-like +// applications where tweaking of the phoneme segmenation (and subsequent +// recalculation of the animation data) is required. Most customers probably +// won't need this functionality. + +// Inserts a phoneme at the specified position in the specified manner. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackInsertPhoneme( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + TALKBACK_PHONEME iPhoneme, // IN: enumeration of phoneme to insert. + long iInsertPosition, // IN: position (phoneme number) at which to insert. + int iInsertBefore); // IN: manner of insertion: + // 0 means put phoneme after insert position; + // 1 means put phoneme before insert position. + +// Deletes the specified phoneme. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackDeletePhoneme( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iPhonemeToDelete); // IN: phoneme to delete. + +// Changes the start time of the specified phoneme. +// +// NOTE: the start time specified may not be the actual start time for a number +// of reasons, most notably if the specified start time will make the phoneme +// too short. This function returns the actual start time so the caller can +// check the result without having to query the phoneme. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackChangePhonemeStart( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iPhonemeToChange, // IN: phoneme to change. + double *ioNewTime); // IN/OUT: new start time value in seconds (in); actual start time (out). + +// Changes the end time of the specified phoneme. +// +// NOTE: the end time specified may not be the actual end time for a number of +// reasons, most notably if the specified end time will make the phoneme too +// short. This function returns the actual end time so the caller can check the +// result without having to query the phoneme. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackChangePhonemeEnd( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iPhonemeToChange, // IN: phoneme to change. + double *ioNewTime); // IN/OUT: new end time value in seconds (in); actual end time (out). + +// Changes the enumeration of the specified phoneme. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackChangePhonemeEnum( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iPhonemeToChange, // IN: phoneme to change. + TALKBACK_PHONEME iNewPhoneme); // IN: new phoneme enumeration. + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/mp/src/utils/qc_eyes/QC_Eyes.cpp b/mp/src/utils/qc_eyes/QC_Eyes.cpp index d62fe124..179391df 100644 --- a/mp/src/utils/qc_eyes/QC_Eyes.cpp +++ b/mp/src/utils/qc_eyes/QC_Eyes.cpp @@ -1,73 +1,73 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// QC_Eyes.cpp : Defines the class behaviors for the application. -// - -#include "stdafx.h" -#include "QC_Eyes.h" -#include "QC_EyesDlg.h" - -#ifdef _DEBUG -#define new DEBUG_NEW -#undef THIS_FILE -static char THIS_FILE[] = __FILE__; -#endif - -///////////////////////////////////////////////////////////////////////////// -// CQC_EyesApp - -BEGIN_MESSAGE_MAP(CQC_EyesApp, CWinApp) - //{{AFX_MSG_MAP(CQC_EyesApp) - // NOTE - the ClassWizard will add and remove mapping macros here. - // DO NOT EDIT what you see in these blocks of generated code! - //}}AFX_MSG - ON_COMMAND(ID_HELP, CWinApp::OnHelp) -END_MESSAGE_MAP() - -///////////////////////////////////////////////////////////////////////////// -// CQC_EyesApp construction - -CQC_EyesApp::CQC_EyesApp() -{ - // TODO: add construction code here, - // Place all significant initialization in InitInstance -} - -///////////////////////////////////////////////////////////////////////////// -// The one and only CQC_EyesApp object - -CQC_EyesApp theApp; - -///////////////////////////////////////////////////////////////////////////// -// CQC_EyesApp initialization - -BOOL CQC_EyesApp::InitInstance() -{ - AfxEnableControlContainer(); - - // Standard initialization - // If you are not using these features and wish to reduce the size - // of your final executable, you should remove from the following - // the specific initialization routines you do not need. - -#ifdef _AFXDLL - Enable3dControls(); // Call this when using MFC in a shared DLL -#endif - - CQC_EyesDlg dlg; - m_pMainWnd = &dlg; - INT_PTR nResponse = dlg.DoModal(); - if (nResponse == IDOK) - { - // TODO: Place code here to handle when the dialog is - // dismissed with OK - } - else if (nResponse == IDCANCEL) - { - // TODO: Place code here to handle when the dialog is - // dismissed with Cancel - } - - // Since the dialog has been closed, return FALSE so that we exit the - // application, rather than start the application's message pump. - return FALSE; -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// QC_Eyes.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "QC_Eyes.h" +#include "QC_EyesDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CQC_EyesApp + +BEGIN_MESSAGE_MAP(CQC_EyesApp, CWinApp) + //{{AFX_MSG_MAP(CQC_EyesApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CQC_EyesApp construction + +CQC_EyesApp::CQC_EyesApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CQC_EyesApp object + +CQC_EyesApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CQC_EyesApp initialization + +BOOL CQC_EyesApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. + +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#endif + + CQC_EyesDlg dlg; + m_pMainWnd = &dlg; + INT_PTR nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/mp/src/utils/qc_eyes/QC_Eyes.h b/mp/src/utils/qc_eyes/QC_Eyes.h index 240cb3d1..e1068d65 100644 --- a/mp/src/utils/qc_eyes/QC_Eyes.h +++ b/mp/src/utils/qc_eyes/QC_Eyes.h @@ -1,50 +1,50 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// QC_Eyes.h : main header file for the QC_EYES application -// - -#if !defined(AFX_QC_EYES_H__398BAF8D_D3C0_4326_BEF0_5129884EE1A3__INCLUDED_) -#define AFX_QC_EYES_H__398BAF8D_D3C0_4326_BEF0_5129884EE1A3__INCLUDED_ - -#if _MSC_VER > 1000 -#pragma once -#endif // _MSC_VER > 1000 - -#ifndef __AFXWIN_H__ - #error include 'stdafx.h' before including this file for PCH -#endif - -#include "resource.h" // main symbols - -///////////////////////////////////////////////////////////////////////////// -// CQC_EyesApp: -// See QC_Eyes.cpp for the implementation of this class -// - -class CQC_EyesApp : public CWinApp -{ -public: - CQC_EyesApp(); - -// Overrides - // ClassWizard generated virtual function overrides - //{{AFX_VIRTUAL(CQC_EyesApp) - public: - virtual BOOL InitInstance(); - //}}AFX_VIRTUAL - -// Implementation - - //{{AFX_MSG(CQC_EyesApp) - // NOTE - the ClassWizard will add and remove member functions here. - // DO NOT EDIT what you see in these blocks of generated code ! - //}}AFX_MSG - DECLARE_MESSAGE_MAP() -}; - - -///////////////////////////////////////////////////////////////////////////// - -//{{AFX_INSERT_LOCATION}} -// Microsoft Visual C++ will insert additional declarations immediately before the previous line. - -#endif // !defined(AFX_QC_EYES_H__398BAF8D_D3C0_4326_BEF0_5129884EE1A3__INCLUDED_) +//========= Copyright Valve Corporation, All rights reserved. ============// +// QC_Eyes.h : main header file for the QC_EYES application +// + +#if !defined(AFX_QC_EYES_H__398BAF8D_D3C0_4326_BEF0_5129884EE1A3__INCLUDED_) +#define AFX_QC_EYES_H__398BAF8D_D3C0_4326_BEF0_5129884EE1A3__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CQC_EyesApp: +// See QC_Eyes.cpp for the implementation of this class +// + +class CQC_EyesApp : public CWinApp +{ +public: + CQC_EyesApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CQC_EyesApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CQC_EyesApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_QC_EYES_H__398BAF8D_D3C0_4326_BEF0_5129884EE1A3__INCLUDED_) diff --git a/mp/src/utils/qc_eyes/QC_EyesDlg.cpp b/mp/src/utils/qc_eyes/QC_EyesDlg.cpp index 863e556a..d76d916a 100644 --- a/mp/src/utils/qc_eyes/QC_EyesDlg.cpp +++ b/mp/src/utils/qc_eyes/QC_EyesDlg.cpp @@ -1,705 +1,705 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// QC_EyesDlg.cpp : implementation file -// - -#include "stdafx.h" -#include -#include "QC_Eyes.h" -#include "QC_EyesDlg.h" - -#ifdef _DEBUG -#define new DEBUG_NEW -#undef THIS_FILE -static char THIS_FILE[] = __FILE__; -#endif - -///////////////////////////////////////////////////////////////////////////// -// CQC_EyesDlg dialog - -CQC_EyesDlg::CQC_EyesDlg(CWnd* pParent /*=NULL*/) - : CDialog(CQC_EyesDlg::IDD, pParent) -{ - //{{AFX_DATA_INIT(CQC_EyesDlg) - // NOTE: the ClassWizard will add member initialization here - //}}AFX_DATA_INIT - // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 - m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); - m_pBitmapHead = NULL; -} - -void CQC_EyesDlg::DoDataExchange(CDataExchange* pDX) -{ - CDialog::DoDataExchange(pDX); - //{{AFX_DATA_MAP(CQC_EyesDlg) - DDX_Control(pDX, IDC_LEFT_LID_CONTROL, m_IndependentLeftLidControl); - DDX_Control(pDX, IDC_PICTURES, m_PictureControl); - //}}AFX_DATA_MAP -} - -BEGIN_MESSAGE_MAP(CQC_EyesDlg, CDialog) - //{{AFX_MSG_MAP(CQC_EyesDlg) - ON_WM_PAINT() - ON_WM_QUERYDRAGICON() - ON_BN_CLICKED(IDC_CREATE_QC_TEXT, OnCreateQcText) - ON_BN_CLICKED(IDC_IRIS_COLOR_BROWN, OnIrisColorBrown) - ON_BN_CLICKED(IDC_IRIS_COLOR_GREEN, OnIrisColorGreen) - ON_BN_CLICKED(IDC_IRIS_COLOR_BLUE, OnIrisColorBlue) - ON_BN_CLICKED(IDC_EYE_COLOR_DARK, OnEyeColorDark) - ON_BN_CLICKED(IDC_EYE_COLOR_LIGHT, OnEyeColorLight) - ON_EN_SETFOCUS(IDC_RIGHT_EYE_X, OnSetfocusRightEyeX) - ON_EN_SETFOCUS(IDC_RIGHT_EYE_Y, OnSetfocusRightEyeY) - ON_EN_SETFOCUS(IDC_RIGHT_EYE_Z, OnSetfocusRightEyeZ) - ON_EN_SETFOCUS(IDC_LEFT_EYE_X, OnSetfocusLeftEyeX) - ON_EN_SETFOCUS(IDC_LEFT_EYE_Y, OnSetfocusLeftEyeY) - ON_EN_SETFOCUS(IDC_LEFT_EYE_Z, OnSetfocusLeftEyeZ) - ON_EN_SETFOCUS(IDC_UPPER_LID_LOWERED, OnSetfocusUpperLidLowered) - ON_EN_SETFOCUS(IDC_UPPER_LID_NEUTRAL, OnSetfocusUpperLidNeutral) - ON_EN_SETFOCUS(IDC_UPPER_LID_RAISED, OnSetfocusUpperLidRaised) - ON_EN_SETFOCUS(IDC_LOWER_LID_LOWERED, OnSetfocusLowerLidLowered) - ON_EN_SETFOCUS(IDC_LOWER_LID_NEUTRAL, OnSetfocusLowerLidNeutral) - ON_EN_SETFOCUS(IDC_LOWER_LID_RAISED, OnSetfocusLowerLidRaised) - ON_BN_CLICKED(IDC_COPY_TEXT_TO_CLIPBOARD, OnCopyTextToClipboard) - ON_BN_CLICKED(IDC_DEFAULT_CONTROLS, OnDefaultControls) - ON_BN_CLICKED(IDC_ADVANCED_CONTROLS, OnAdvancedControls) - ON_BN_CLICKED(IDC_LEFT_LID_CONTROL, OnLeftLidControl) - //}}AFX_MSG_MAP -END_MESSAGE_MAP() - - -void CQC_EyesDlg::AddText( const char *pFormat, ... ) -{ - char tempMsg[4096]; - va_list marker; - va_start( marker, pFormat ); - _vsnprintf( tempMsg, sizeof( tempMsg ), pFormat, marker ); - tempMsg[ ARRAYSIZE(tempMsg) - 1 ] = 0; - va_end( marker ); - - size_t nCharsInBuf = strlen( m_Buf ); - size_t nCharsInMsg = strlen( tempMsg ); - - if ( nCharsInBuf + nCharsInMsg + 1 > m_BufSize ) - { - m_BufSize = nCharsInBuf + nCharsInMsg + 4096; - char *newbuf = new char[m_BufSize]; - memcpy( newbuf, m_Buf, nCharsInBuf + 1 ); - delete [] m_Buf; - m_Buf = newbuf; - } - - strcat( m_Buf, tempMsg ); -} - - -void SendToEditControl( HWND hEditControl, const char *pText ) -{ - LRESULT nLen = SendMessage( hEditControl, EM_GETLIMITTEXT, 0, 0 ); - SendMessage( hEditControl, EM_SETSEL, nLen, nLen ); - SendMessage( hEditControl, EM_REPLACESEL, FALSE, (LPARAM)pText ); -} - - -void FormatAndSendToEditControl( void *hWnd, const char *pText ) -{ - HWND hEditControl = (HWND)hWnd; - - // Translate \n to \r\n. - char outMsg[1024]; - const char *pIn = pText; - char *pOut = outMsg; - while ( *pIn ) - { - if ( *pIn == '\n' ) - { - *pOut = '\r'; - pOut++; - } - *pOut = *pIn; - - ++pIn; - ++pOut; - - if ( pOut - outMsg >= 1020 ) - { - *pOut = 0; - SendToEditControl( hEditControl, outMsg ); - pOut = outMsg; - } - } - *pOut = 0; - SendToEditControl( hEditControl, outMsg ); -} - - -HBITMAP CQC_EyesDlg::GetCachedBitmap( UINT id ) -{ - for ( CBitmapRef *pCur=m_pBitmapHead; pCur; pCur=pCur->m_pNext ) - { - if ( pCur->m_iResource == id ) - return pCur->m_hBitmap; - } - - CBitmapRef *pNew = new CBitmapRef; - pNew->m_iResource = id; - pNew->m_hBitmap = ::LoadBitmap( AfxGetInstanceHandle(), MAKEINTRESOURCE(id) ); - pNew->m_pNext = m_pBitmapHead; - m_pBitmapHead = pNew; - - return pNew->m_hBitmap; -} - - -///////////////////////////////////////////////////////////////////////////// -// CQC_EyesDlg message handlers - -BOOL CQC_EyesDlg::OnInitDialog() -{ - CDialog::OnInitDialog(); - - // Set the icon for this dialog. The framework does this automatically - // when the application's main window is not a dialog - SetIcon(m_hIcon, TRUE); // Set big icon - SetIcon(m_hIcon, FALSE); // Set small icon - - // TODO: Add extra initialization here - GetDlgItem( IDC_REFERENCE_FILENAME )->SetWindowText( "filename_reference" ); - GetDlgItem( IDC_EXPRESSIONS_FILENAME )->SetWindowText( "filename_expressions" ); - GetDlgItem( IDC_MODEL_FILENAME )->SetWindowText( "filename_model" ); - GetDlgItem( IDC_IRIS_SIZE )->SetWindowText( "0.63" ); - GetDlgItem( IDC_EYEBALL_SIZE )->SetWindowText( "1.0" ); - - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_Y_AXIS_UP ), BM_SETCHECK, BST_CHECKED, 0 ); - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_DEFAULT_CONTROLS ), BM_SETCHECK, BST_CHECKED, 0 ); - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BROWN ), BM_SETCHECK, BST_CHECKED, 0 ); - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_EYE_COLOR_LIGHT ), BM_SETCHECK, BST_CHECKED, 0 ); - - m_hOutputText = ::GetDlgItem( m_hWnd, IDC_OUTPUT_TEXT ); - - m_PictureControl.SetBitmap( GetCachedBitmap( IDB_EYE_DEFAULT ) ); - OnDefaultControls(); // Hide the advanced controls. - - return TRUE; // return TRUE unless you set the focus to a control -} - -// If you add a minimize button to your dialog, you will need the code below -// to draw the icon. For MFC applications using the document/view model, -// this is automatically done for you by the framework. - -void CQC_EyesDlg::OnPaint() -{ - if (IsIconic()) - { - CPaintDC dc(this); // device context for painting - - SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); - - // Center icon in client rectangle - int cxIcon = GetSystemMetrics(SM_CXICON); - int cyIcon = GetSystemMetrics(SM_CYICON); - CRect rect; - GetClientRect(&rect); - int x = (rect.Width() - cxIcon + 1) / 2; - int y = (rect.Height() - cyIcon + 1) / 2; - - // Draw the icon - dc.DrawIcon(x, y, m_hIcon); - } - else - { - CDialog::OnPaint(); - } -} - -// The system calls this to obtain the cursor to display while the user drags -// the minimized window. -HCURSOR CQC_EyesDlg::OnQueryDragIcon() -{ - return (HCURSOR) m_hIcon; -} - - -void HandleYAxisUp( float &yVal, float &zVal ) -{ - float flTemp = yVal; - yVal = -zVal; - zVal = flTemp; -} - - -float CQC_EyesDlg::GetDlgItemFloat( UINT id ) -{ - char text[4096]; - GetDlgItemText( id, text, sizeof( text ) ); - return (float)atof( text ); -} - - -bool CQC_EyesDlg::IsOptionChecked( UINT option ) -{ - return (::SendMessage( ::GetDlgItem( m_hWnd, option ), BM_GETCHECK, 0, 0 ) == BST_CHECKED); -} - - -void CQC_EyesDlg::GetDialogParams( CDialogParams &p ) -{ - p.m_flLeftEye[0] = GetDlgItemFloat( IDC_LEFT_EYE_X ); - p.m_flLeftEye[1] = GetDlgItemFloat( IDC_LEFT_EYE_Y ); - p.m_flLeftEye[2] = GetDlgItemFloat( IDC_LEFT_EYE_Z ); - - p.m_flRightEye[0] = GetDlgItemFloat( IDC_RIGHT_EYE_X ); - p.m_flRightEye[1] = GetDlgItemFloat( IDC_RIGHT_EYE_Y ); - p.m_flRightEye[2] = GetDlgItemFloat( IDC_RIGHT_EYE_Z ); - - bool bYAxisUp = IsOptionChecked( IDC_Y_AXIS_UP ); - if ( bYAxisUp ) - { - HandleYAxisUp( p.m_flLeftEye[1], p.m_flLeftEye[2] ); - HandleYAxisUp( p.m_flRightEye[1], p.m_flRightEye[2] ); - } - - GetDlgItemText( IDC_REFERENCE_FILENAME, p.m_ReferenceFilename, sizeof( p.m_ReferenceFilename ) ); - GetDlgItemText( IDC_EXPRESSIONS_FILENAME, p.m_ExpressionsFilename, sizeof( p.m_ExpressionsFilename ) ); - GetDlgItemText( IDC_MODEL_FILENAME, p.m_ModelFilename, sizeof( p.m_ModelFilename ) ); - - p.m_flIrisSize = GetDlgItemFloat( IDC_IRIS_SIZE ); - p.m_flEyeballSize = GetDlgItemFloat( IDC_EYEBALL_SIZE ); - - p.m_flRightUpperLidRaised = GetDlgItemFloat( IDC_UPPER_LID_RAISED ); - p.m_flRightUpperLidNeutral = GetDlgItemFloat( IDC_UPPER_LID_NEUTRAL ); - p.m_flRightUpperLidLowered = GetDlgItemFloat( IDC_UPPER_LID_LOWERED ); - - p.m_flRightLowerLidRaised = GetDlgItemFloat( IDC_LOWER_LID_RAISED ); - p.m_flRightLowerLidNeutral = GetDlgItemFloat( IDC_LOWER_LID_NEUTRAL ); - p.m_flRightLowerLidLowered = GetDlgItemFloat( IDC_LOWER_LID_LOWERED ); - - if ( IsIndependentLeftLidControlEnabled() ) - { - p.m_flLeftUpperLidRaised = GetDlgItemFloat( IDC_UPPER_LEFT_LID_RAISED ); - p.m_flLeftUpperLidNeutral = GetDlgItemFloat( IDC_UPPER_LEFT_LID_NEUTRAL ); - p.m_flLeftUpperLidLowered = GetDlgItemFloat( IDC_UPPER_LEFT_LID_LOWERED ); - - p.m_flLeftLowerLidRaised = GetDlgItemFloat( IDC_LOWER_LEFT_LID_RAISED ); - p.m_flLeftLowerLidNeutral = GetDlgItemFloat( IDC_LOWER_LEFT_LID_NEUTRAL ); - p.m_flLeftLowerLidLowered = GetDlgItemFloat( IDC_LOWER_LEFT_LID_LOWERED ); - } - else - { - // Left lids follow the right lids. - p.m_flLeftUpperLidRaised = p.m_flRightUpperLidRaised; - p.m_flLeftUpperLidNeutral = p.m_flRightUpperLidNeutral; - p.m_flLeftUpperLidLowered = p.m_flRightUpperLidLowered; - - p.m_flLeftLowerLidRaised = p.m_flRightLowerLidRaised; - p.m_flLeftLowerLidNeutral = p.m_flRightLowerLidNeutral; - p.m_flLeftLowerLidLowered = p.m_flRightLowerLidLowered; - } - - // Figure out the eyeball prefix. - if ( IsOptionChecked( IDC_EYE_COLOR_LIGHT ) ) - strcpy( p.m_EyeballPrefix, "eyeball" ); - else - strcpy( p.m_EyeballPrefix, "dark_eyeball" ); - - // Figure out the pupil prefix. - if ( IsOptionChecked( IDC_IRIS_COLOR_BROWN ) ) - strcpy( p.m_PupilPrefix, "pupil" ); - else if ( IsOptionChecked( IDC_IRIS_COLOR_GREEN ) ) - strcpy( p.m_PupilPrefix, "grn_pupil" ); - else - strcpy( p.m_PupilPrefix, "bl_pupil" ); -} - - -void CQC_EyesDlg::GenerateQCText() -{ - CDialogParams p; - GetDialogParams( p ); - - - m_BufSize = 16 * 1024; - m_Buf = new char[m_BufSize]; - m_Buf[0] = 0; - - AddText( "//start eye/face data\n" ); - AddText( "$eyeposition 0 0 70\n\n" ); - - AddText( "//head controllers\n" ); - AddText( "$attachment \"eyes\" \"ValveBiped.Bip01_Head1\" %.2f %.2f %.2f absolute\n", - p.m_flLeftEye[0] - ((fabs( p.m_flRightEye[0] ) + p.m_flLeftEye[0]) * 0.5), - (p.m_flLeftEye[1] + p.m_flRightEye[1]) * 0.5, - (p.m_flLeftEye[2] + p.m_flRightEye[2]) * 0.5 ); - - AddText( "$attachment \"mouth\" \"ValveBiped.Bip01_Head1\" 0.80 -5.80 -0.15 rotate 0 -80 -90\n\n" ); - - AddText( "$model %s \"%s.smd\" {\n", - p.m_ModelFilename, p.m_ReferenceFilename ); - - AddText( "\teyeball righteye \"ValveBiped.Bip01_Head1\" %.2f %.2f %.2f \"%s_r\" %.2f 4 \"%s_r\" %.2f\n", - p.m_flRightEye[0], - p.m_flRightEye[1], - p.m_flRightEye[2], - p.m_EyeballPrefix, - p.m_flEyeballSize, - p.m_PupilPrefix, - p.m_flIrisSize ); - - AddText( "\teyeball lefteye \"ValveBiped.Bip01_Head1\" %.2f %.2f %.2f \"%s_l\" %.2f -4 \"%s_l\" %.2f\n\n", - p.m_flLeftEye[0], - p.m_flLeftEye[1], - p.m_flLeftEye[2], - p.m_EyeballPrefix, - p.m_flEyeballSize, - p.m_PupilPrefix, - p.m_flIrisSize ); - - AddText( "\teyelid upper_right \"%s\" lowerer 1 %.2f neutral 0 %.2f raiser 2 %.2f split 0.1 eyeball righteye\n", - p.m_ExpressionsFilename, - p.m_flRightUpperLidLowered - p.m_flRightEye[2], - p.m_flRightUpperLidNeutral - p.m_flRightEye[2], - p.m_flRightUpperLidRaised - p.m_flRightEye[2] ); - - AddText( "\teyelid lower_right \"%s\" lowerer 3 %.2f neutral 0 %.2f raiser 4 %.2f split 0.1 eyeball righteye\n", - p.m_ExpressionsFilename, - p.m_flRightLowerLidLowered - p.m_flRightEye[2], - p.m_flRightLowerLidNeutral - p.m_flRightEye[2], - p.m_flRightLowerLidRaised - p.m_flRightEye[2] ); - - AddText( "\teyelid upper_left \"%s\" lowerer 1 %.2f neutral 0 %.2f raiser 2 %.2f split -0.1 eyeball lefteye\n", - p.m_ExpressionsFilename, - p.m_flLeftUpperLidLowered - p.m_flLeftEye[2], - p.m_flLeftUpperLidNeutral - p.m_flLeftEye[2], - p.m_flLeftUpperLidRaised - p.m_flLeftEye[2] ); - - AddText( "\teyelid lower_left \"%s\" lowerer 3 %.2f neutral 0 %.2f raiser 4 %.2f split -0.1 eyeball lefteye\n\n", - p.m_ExpressionsFilename, - p.m_flLeftLowerLidLowered - p.m_flLeftEye[2], - p.m_flLeftLowerLidNeutral - p.m_flLeftEye[2], - p.m_flLeftLowerLidRaised - p.m_flLeftEye[2] ); - - AddText( "\tmouth 0 \"mouth\" \"ValveBiped.Bip01_Head1\" 0 1 0 // mouth illumination\n" ); - AddText( "\tflexfile \"%s\" {\n", p.m_ExpressionsFilename ); - AddText( "\t\t$include \"../standardflex_xsi.qci\"\n" ); - AddText( "\t}\n" ); - AddText( "\t$include \"../facerules_xsi.qci\"\n" ); - AddText( "\t$include \"../bodyrules_xsi.qci\"\n" ); - AddText( "}\n" ); - AddText( "//end eye/face data\n" ); -} - - -bool CQC_EyesDlg::CheckNumericInputs() -{ - struct - { - const char *pControlName; - UINT controlID; - } - controls[] = - { - {"Right Eye X", IDC_RIGHT_EYE_X}, - {"Right Eye Y", IDC_RIGHT_EYE_Y}, - {"Right Eye Z", IDC_RIGHT_EYE_Z}, - - {"Left Eye X", IDC_LEFT_EYE_X}, - {"Left Eye Y", IDC_LEFT_EYE_Y}, - {"Left Eye Z", IDC_LEFT_EYE_Z}, - - {"Upper Lid Raised", IDC_UPPER_LID_RAISED}, - {"Upper Lid Neutral", IDC_UPPER_LID_NEUTRAL}, - {"Upper Lid Lowered", IDC_UPPER_LID_LOWERED}, - - {"Lower Lid Raised", IDC_LOWER_LID_RAISED}, - {"Lower Lid Neutral", IDC_LOWER_LID_NEUTRAL}, - {"Lower Lid Lowered", IDC_LOWER_LID_LOWERED}, - - {"Upper Left Lid Raised", IDC_UPPER_LEFT_LID_RAISED}, - {"Upper Left Lid Neutral", IDC_UPPER_LEFT_LID_NEUTRAL}, - {"Upper Left Lid Lowered", IDC_UPPER_LEFT_LID_LOWERED}, - - {"Lower Left Lid Raised", IDC_LOWER_LEFT_LID_RAISED}, - {"Lower Left Lid Neutral", IDC_LOWER_LEFT_LID_NEUTRAL}, - {"Lower Left Lid Lowered", IDC_LOWER_LEFT_LID_LOWERED}, - - {"Iris Size", IDC_IRIS_SIZE}, - {"Eyeball Size", IDC_EYEBALL_SIZE} - }; - - for ( int i=0; i < sizeof( controls ) / sizeof( controls[0] ); i++ ) - { - char text[512]; - GetDlgItem( controls[i].controlID )->GetWindowText( text, sizeof( text ) ); - - for ( int z=0; z < (int)strlen( text ); z++ ) - { - if ( text[z] < '0' || text[z] > '9' ) - { - if ( text[z] != '.' && text[z] != '-' ) - { - char errMsg[512]; - _snprintf( errMsg, sizeof( errMsg ), "The '%s' control must have a numeric value.", controls[i].pControlName ); - AfxMessageBox( errMsg, MB_OK ); - return false; - } - } - } - } - - return true; -} - - -void CQC_EyesDlg::OnCreateQcText() -{ - if ( !CheckNumericInputs() ) - return; - - GenerateQCText(); - - // Clear the edit control. - LRESULT nLen = ::SendMessage( m_hOutputText, EM_GETLIMITTEXT, 0, 0 ); - ::SendMessage( m_hOutputText, EM_SETSEL, 0, nLen ); - ::SendMessage( m_hOutputText, EM_REPLACESEL, FALSE, (LPARAM)"" ); - - FormatAndSendToEditControl( m_hOutputText, m_Buf ); - - delete [] m_Buf; -} - -void CQC_EyesDlg::OnIrisColorBrown() -{ - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BROWN ), BM_SETCHECK, BST_CHECKED, 0 ); - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_GREEN ), BM_SETCHECK, BST_UNCHECKED, 0 ); - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BLUE ), BM_SETCHECK, BST_UNCHECKED, 0 ); - SetupBitmapLabel( IDB_EYE_DEFAULT, "" ); -} - -void CQC_EyesDlg::OnIrisColorGreen() -{ - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BROWN ), BM_SETCHECK, BST_UNCHECKED, 0 ); - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_GREEN ), BM_SETCHECK, BST_CHECKED, 0 ); - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BLUE ), BM_SETCHECK, BST_UNCHECKED, 0 ); - SetupBitmapLabel( IDB_EYE_DEFAULT, "" ); -} - -void CQC_EyesDlg::OnIrisColorBlue() -{ - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BROWN ), BM_SETCHECK, BST_UNCHECKED, 0 ); - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_GREEN ), BM_SETCHECK, BST_UNCHECKED, 0 ); - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BLUE ), BM_SETCHECK, BST_CHECKED, 0 ); - SetupBitmapLabel( IDB_EYE_DEFAULT, "" ); -} - -void CQC_EyesDlg::OnEyeColorDark() -{ - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_EYE_COLOR_LIGHT ), BM_SETCHECK, BST_UNCHECKED, 0 ); - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_EYE_COLOR_DARK ), BM_SETCHECK, BST_CHECKED, 0 ); - SetupBitmapLabel( IDB_EYE_DEFAULT, "" ); -} - -void CQC_EyesDlg::OnEyeColorLight() -{ - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_EYE_COLOR_LIGHT ), BM_SETCHECK, BST_CHECKED, 0 ); - ::SendMessage( ::GetDlgItem( m_hWnd, IDC_EYE_COLOR_DARK ), BM_SETCHECK, BST_UNCHECKED, 0 ); - SetupBitmapLabel( IDB_EYE_DEFAULT, "" ); -} - -void CQC_EyesDlg::SetupBitmapLabel( UINT iBitmapResourceID, const char *pString, ... ) -{ - char msg[4096]; - va_list marker; - va_start( marker, pString ); - _vsnprintf( msg, sizeof( msg ), pString, marker ); - msg[ ARRAYSIZE(msg) - 1 ] = 0; - va_end( marker ); - - m_PictureControl.SetBitmap( GetCachedBitmap( iBitmapResourceID ) ); - GetDlgItem( IDC_PICTURE_LABEL )->SetWindowText( msg ); -} - -void CQC_EyesDlg::OnSetfocusRightEyeX() -{ - SetupBitmapLabel( IDB_EYE_XY_R, "Enter the X position of the center vertex of the right eye" ); -} - -void CQC_EyesDlg::OnSetfocusRightEyeY() -{ - SetupBitmapLabel( IsOptionChecked( IDC_Y_AXIS_UP ) ? IDB_EYE_XY_R : IDB_EYE_Z_R, "Enter the Y position of the center vertex of the right eye" ); -} - -void CQC_EyesDlg::OnSetfocusRightEyeZ() -{ - SetupBitmapLabel( IsOptionChecked( IDC_Y_AXIS_UP ) ? IDB_EYE_Z_R : IDB_EYE_XY_R, "Enter the Z position of the center vertex of the right eye" ); -} - -void CQC_EyesDlg::OnSetfocusLeftEyeX() -{ - SetupBitmapLabel( IDB_EYE_XY_L, "Enter the X position of the center vertex of the right eye" ); -} - -void CQC_EyesDlg::OnSetfocusLeftEyeY() -{ - SetupBitmapLabel( IsOptionChecked( IDC_Y_AXIS_UP ) ? IDB_EYE_XY_L : IDB_EYE_Z_L, "Enter the Y position of the center vertex of the right eye" ); -} - -void CQC_EyesDlg::OnSetfocusLeftEyeZ() -{ - SetupBitmapLabel( IsOptionChecked( IDC_Y_AXIS_UP ) ? IDB_EYE_Z_L : IDB_EYE_XY_L, "Enter the Z position of the center vertex of the right eye" ); -} - -void CQC_EyesDlg::OnSetfocusUpperLidLowered() -{ - const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; - SetupBitmapLabel( IDB_EYE_UPPER_LO, "At Frame 1, enter the %s position of the center vertex of the right upper eye lid", pCoord ); -} - -void CQC_EyesDlg::OnSetfocusUpperLidNeutral() -{ - const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; - SetupBitmapLabel( IDB_EYE_UPPER_MID, "At Frame 0, enter the %s position of the center vertex of the right upper eye lid", pCoord ); -} - -void CQC_EyesDlg::OnSetfocusUpperLidRaised() -{ - const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; - SetupBitmapLabel( IDB_EYE_UPPER_HI, "At Frame 2, enter the %s position of the center vertex of the right upper eye lid", pCoord ); -} - - -void CQC_EyesDlg::OnSetfocusLowerLidLowered() -{ - const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; - SetupBitmapLabel( IDB_EYE_LOWER_LO, "At Frame 3, enter the %s position of the center vertex of the right lower eye lid", pCoord ); -} - -void CQC_EyesDlg::OnSetfocusLowerLidNeutral() -{ - const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; - SetupBitmapLabel( IDB_EYE_LOWER_MID, "At Frame 0, enter the %s position of the center vertex of the right lower eye lid", pCoord ); -} - -void CQC_EyesDlg::OnSetfocusLowerLidRaised() -{ - const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; - SetupBitmapLabel( IDB_EYE_LOWER_HI, "At Frame 4, enter the %s position of the center vertex of the right lower eye lid", pCoord ); -} - -void CQC_EyesDlg::OnCopyTextToClipboard() -{ - if ( !CheckNumericInputs() ) - return; - - GenerateQCText(); - - if ( !OpenClipboard() ) - return; - - size_t textLen = strlen( m_Buf ); - HANDLE hmem = GlobalAlloc( GMEM_MOVEABLE | GMEM_DDESHARE, textLen + 1 ); - if ( hmem ) - { - void *ptr = GlobalLock( hmem ); - if ( ptr ) - { - memcpy( ptr, m_Buf, textLen+1 ); - GlobalUnlock( hmem ); - - SetClipboardData( CF_TEXT, hmem ); - } - } - - CloseClipboard(); - - delete [] m_Buf; -} - - -int g_AdvancedControls[] = -{ - IDC_EYE_DETAIL_CONTROL_FRAME, - IDC_IRIS_SIZE, - IDC_IRIS_SIZE_LABEL, - IDC_EYEBALL_SIZE, - IDC_EYEBALL_SIZE_LABEL, - IDC_LEFT_LID_CONTROL -}; -#define NUM_ADVANCED_CONTROLS ( sizeof( g_AdvancedControls ) / sizeof( g_AdvancedControls[0] ) ) - -int g_LeftLidPositionControls[] = -{ - IDC_UPPER_LEFT_LID_PANEL, - IDC_UPPER_LEFT_LID_RAISED, - IDC_UPPER_LEFT_LID_RAISED_LABEL, - IDC_UPPER_LEFT_LID_NEUTRAL, - IDC_UPPER_LEFT_LID_NEUTRAL_LABEL, - IDC_UPPER_LEFT_LID_LOWERED, - IDC_UPPER_LEFT_LID_LOWERED_LABEL, - IDC_LOWER_LEFT_LID_PANEL, - IDC_LOWER_LEFT_LID_RAISED, - IDC_LOWER_LEFT_LID_RAISED_LABEL, - IDC_LOWER_LEFT_LID_NEUTRAL, - IDC_LOWER_LEFT_LID_NEUTRAL_LABEL, - IDC_LOWER_LEFT_LID_LOWERED, - IDC_LOWER_LEFT_LID_LOWERED_LABEL -}; -#define NUM_LEFT_LID_POSITION_CONTROLS ( sizeof( g_LeftLidPositionControls ) / sizeof( g_LeftLidPositionControls[0] ) ) - - -void CQC_EyesDlg::OnDefaultControls() -{ - GetDlgItem( IDC_PICTURES )->ShowWindow( SW_SHOW ); - - // Hide all the advanced controls. - for ( int i=0; i < NUM_ADVANCED_CONTROLS; i++ ) - { - GetDlgItem( g_AdvancedControls[i] )->ShowWindow( SW_HIDE ); - } - - for ( int i=0; i < NUM_LEFT_LID_POSITION_CONTROLS; i++ ) - { - GetDlgItem( g_LeftLidPositionControls[i] )->ShowWindow( SW_HIDE ); - } - -} - -void CQC_EyesDlg::OnAdvancedControls() -{ - GetDlgItem( IDC_PICTURES )->ShowWindow( SW_HIDE ); - - // Show the advanced controls. - for ( int i=0; i < NUM_ADVANCED_CONTROLS; i++ ) - { - GetDlgItem( g_AdvancedControls[i] )->ShowWindow( SW_SHOW ); - GetDlgItem( g_AdvancedControls[i] )->InvalidateRect( NULL ); - } - - if ( IsIndependentLeftLidControlEnabled() ) - { - OnLeftLidControl(); - } -} - - -bool CQC_EyesDlg::IsIndependentLeftLidControlEnabled() -{ - return m_IndependentLeftLidControl.GetCheck() == 1; -} - -void CQC_EyesDlg::OnLeftLidControl() -{ - if ( IsIndependentLeftLidControlEnabled() ) - { - for ( int i=0; i < NUM_LEFT_LID_POSITION_CONTROLS; i++ ) - { - GetDlgItem( g_LeftLidPositionControls[i] )->ShowWindow( SW_SHOW ); - GetDlgItem( g_LeftLidPositionControls[i] )->InvalidateRect( NULL ); - } - } - else - { - for ( int i=0; i < NUM_LEFT_LID_POSITION_CONTROLS; i++ ) - { - GetDlgItem( g_LeftLidPositionControls[i] )->ShowWindow( SW_HIDE ); - GetDlgItem( g_LeftLidPositionControls[i] )->InvalidateRect( NULL ); - } - } -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// QC_EyesDlg.cpp : implementation file +// + +#include "stdafx.h" +#include +#include "QC_Eyes.h" +#include "QC_EyesDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CQC_EyesDlg dialog + +CQC_EyesDlg::CQC_EyesDlg(CWnd* pParent /*=NULL*/) + : CDialog(CQC_EyesDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CQC_EyesDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT + // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); + m_pBitmapHead = NULL; +} + +void CQC_EyesDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CQC_EyesDlg) + DDX_Control(pDX, IDC_LEFT_LID_CONTROL, m_IndependentLeftLidControl); + DDX_Control(pDX, IDC_PICTURES, m_PictureControl); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CQC_EyesDlg, CDialog) + //{{AFX_MSG_MAP(CQC_EyesDlg) + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_BN_CLICKED(IDC_CREATE_QC_TEXT, OnCreateQcText) + ON_BN_CLICKED(IDC_IRIS_COLOR_BROWN, OnIrisColorBrown) + ON_BN_CLICKED(IDC_IRIS_COLOR_GREEN, OnIrisColorGreen) + ON_BN_CLICKED(IDC_IRIS_COLOR_BLUE, OnIrisColorBlue) + ON_BN_CLICKED(IDC_EYE_COLOR_DARK, OnEyeColorDark) + ON_BN_CLICKED(IDC_EYE_COLOR_LIGHT, OnEyeColorLight) + ON_EN_SETFOCUS(IDC_RIGHT_EYE_X, OnSetfocusRightEyeX) + ON_EN_SETFOCUS(IDC_RIGHT_EYE_Y, OnSetfocusRightEyeY) + ON_EN_SETFOCUS(IDC_RIGHT_EYE_Z, OnSetfocusRightEyeZ) + ON_EN_SETFOCUS(IDC_LEFT_EYE_X, OnSetfocusLeftEyeX) + ON_EN_SETFOCUS(IDC_LEFT_EYE_Y, OnSetfocusLeftEyeY) + ON_EN_SETFOCUS(IDC_LEFT_EYE_Z, OnSetfocusLeftEyeZ) + ON_EN_SETFOCUS(IDC_UPPER_LID_LOWERED, OnSetfocusUpperLidLowered) + ON_EN_SETFOCUS(IDC_UPPER_LID_NEUTRAL, OnSetfocusUpperLidNeutral) + ON_EN_SETFOCUS(IDC_UPPER_LID_RAISED, OnSetfocusUpperLidRaised) + ON_EN_SETFOCUS(IDC_LOWER_LID_LOWERED, OnSetfocusLowerLidLowered) + ON_EN_SETFOCUS(IDC_LOWER_LID_NEUTRAL, OnSetfocusLowerLidNeutral) + ON_EN_SETFOCUS(IDC_LOWER_LID_RAISED, OnSetfocusLowerLidRaised) + ON_BN_CLICKED(IDC_COPY_TEXT_TO_CLIPBOARD, OnCopyTextToClipboard) + ON_BN_CLICKED(IDC_DEFAULT_CONTROLS, OnDefaultControls) + ON_BN_CLICKED(IDC_ADVANCED_CONTROLS, OnAdvancedControls) + ON_BN_CLICKED(IDC_LEFT_LID_CONTROL, OnLeftLidControl) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + + +void CQC_EyesDlg::AddText( const char *pFormat, ... ) +{ + char tempMsg[4096]; + va_list marker; + va_start( marker, pFormat ); + _vsnprintf( tempMsg, sizeof( tempMsg ), pFormat, marker ); + tempMsg[ ARRAYSIZE(tempMsg) - 1 ] = 0; + va_end( marker ); + + size_t nCharsInBuf = strlen( m_Buf ); + size_t nCharsInMsg = strlen( tempMsg ); + + if ( nCharsInBuf + nCharsInMsg + 1 > m_BufSize ) + { + m_BufSize = nCharsInBuf + nCharsInMsg + 4096; + char *newbuf = new char[m_BufSize]; + memcpy( newbuf, m_Buf, nCharsInBuf + 1 ); + delete [] m_Buf; + m_Buf = newbuf; + } + + strcat( m_Buf, tempMsg ); +} + + +void SendToEditControl( HWND hEditControl, const char *pText ) +{ + LRESULT nLen = SendMessage( hEditControl, EM_GETLIMITTEXT, 0, 0 ); + SendMessage( hEditControl, EM_SETSEL, nLen, nLen ); + SendMessage( hEditControl, EM_REPLACESEL, FALSE, (LPARAM)pText ); +} + + +void FormatAndSendToEditControl( void *hWnd, const char *pText ) +{ + HWND hEditControl = (HWND)hWnd; + + // Translate \n to \r\n. + char outMsg[1024]; + const char *pIn = pText; + char *pOut = outMsg; + while ( *pIn ) + { + if ( *pIn == '\n' ) + { + *pOut = '\r'; + pOut++; + } + *pOut = *pIn; + + ++pIn; + ++pOut; + + if ( pOut - outMsg >= 1020 ) + { + *pOut = 0; + SendToEditControl( hEditControl, outMsg ); + pOut = outMsg; + } + } + *pOut = 0; + SendToEditControl( hEditControl, outMsg ); +} + + +HBITMAP CQC_EyesDlg::GetCachedBitmap( UINT id ) +{ + for ( CBitmapRef *pCur=m_pBitmapHead; pCur; pCur=pCur->m_pNext ) + { + if ( pCur->m_iResource == id ) + return pCur->m_hBitmap; + } + + CBitmapRef *pNew = new CBitmapRef; + pNew->m_iResource = id; + pNew->m_hBitmap = ::LoadBitmap( AfxGetInstanceHandle(), MAKEINTRESOURCE(id) ); + pNew->m_pNext = m_pBitmapHead; + m_pBitmapHead = pNew; + + return pNew->m_hBitmap; +} + + +///////////////////////////////////////////////////////////////////////////// +// CQC_EyesDlg message handlers + +BOOL CQC_EyesDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Set the icon for this dialog. The framework does this automatically + // when the application's main window is not a dialog + SetIcon(m_hIcon, TRUE); // Set big icon + SetIcon(m_hIcon, FALSE); // Set small icon + + // TODO: Add extra initialization here + GetDlgItem( IDC_REFERENCE_FILENAME )->SetWindowText( "filename_reference" ); + GetDlgItem( IDC_EXPRESSIONS_FILENAME )->SetWindowText( "filename_expressions" ); + GetDlgItem( IDC_MODEL_FILENAME )->SetWindowText( "filename_model" ); + GetDlgItem( IDC_IRIS_SIZE )->SetWindowText( "0.63" ); + GetDlgItem( IDC_EYEBALL_SIZE )->SetWindowText( "1.0" ); + + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_Y_AXIS_UP ), BM_SETCHECK, BST_CHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_DEFAULT_CONTROLS ), BM_SETCHECK, BST_CHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BROWN ), BM_SETCHECK, BST_CHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_EYE_COLOR_LIGHT ), BM_SETCHECK, BST_CHECKED, 0 ); + + m_hOutputText = ::GetDlgItem( m_hWnd, IDC_OUTPUT_TEXT ); + + m_PictureControl.SetBitmap( GetCachedBitmap( IDB_EYE_DEFAULT ) ); + OnDefaultControls(); // Hide the advanced controls. + + return TRUE; // return TRUE unless you set the focus to a control +} + +// If you add a minimize button to your dialog, you will need the code below +// to draw the icon. For MFC applications using the document/view model, +// this is automatically done for you by the framework. + +void CQC_EyesDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +// The system calls this to obtain the cursor to display while the user drags +// the minimized window. +HCURSOR CQC_EyesDlg::OnQueryDragIcon() +{ + return (HCURSOR) m_hIcon; +} + + +void HandleYAxisUp( float &yVal, float &zVal ) +{ + float flTemp = yVal; + yVal = -zVal; + zVal = flTemp; +} + + +float CQC_EyesDlg::GetDlgItemFloat( UINT id ) +{ + char text[4096]; + GetDlgItemText( id, text, sizeof( text ) ); + return (float)atof( text ); +} + + +bool CQC_EyesDlg::IsOptionChecked( UINT option ) +{ + return (::SendMessage( ::GetDlgItem( m_hWnd, option ), BM_GETCHECK, 0, 0 ) == BST_CHECKED); +} + + +void CQC_EyesDlg::GetDialogParams( CDialogParams &p ) +{ + p.m_flLeftEye[0] = GetDlgItemFloat( IDC_LEFT_EYE_X ); + p.m_flLeftEye[1] = GetDlgItemFloat( IDC_LEFT_EYE_Y ); + p.m_flLeftEye[2] = GetDlgItemFloat( IDC_LEFT_EYE_Z ); + + p.m_flRightEye[0] = GetDlgItemFloat( IDC_RIGHT_EYE_X ); + p.m_flRightEye[1] = GetDlgItemFloat( IDC_RIGHT_EYE_Y ); + p.m_flRightEye[2] = GetDlgItemFloat( IDC_RIGHT_EYE_Z ); + + bool bYAxisUp = IsOptionChecked( IDC_Y_AXIS_UP ); + if ( bYAxisUp ) + { + HandleYAxisUp( p.m_flLeftEye[1], p.m_flLeftEye[2] ); + HandleYAxisUp( p.m_flRightEye[1], p.m_flRightEye[2] ); + } + + GetDlgItemText( IDC_REFERENCE_FILENAME, p.m_ReferenceFilename, sizeof( p.m_ReferenceFilename ) ); + GetDlgItemText( IDC_EXPRESSIONS_FILENAME, p.m_ExpressionsFilename, sizeof( p.m_ExpressionsFilename ) ); + GetDlgItemText( IDC_MODEL_FILENAME, p.m_ModelFilename, sizeof( p.m_ModelFilename ) ); + + p.m_flIrisSize = GetDlgItemFloat( IDC_IRIS_SIZE ); + p.m_flEyeballSize = GetDlgItemFloat( IDC_EYEBALL_SIZE ); + + p.m_flRightUpperLidRaised = GetDlgItemFloat( IDC_UPPER_LID_RAISED ); + p.m_flRightUpperLidNeutral = GetDlgItemFloat( IDC_UPPER_LID_NEUTRAL ); + p.m_flRightUpperLidLowered = GetDlgItemFloat( IDC_UPPER_LID_LOWERED ); + + p.m_flRightLowerLidRaised = GetDlgItemFloat( IDC_LOWER_LID_RAISED ); + p.m_flRightLowerLidNeutral = GetDlgItemFloat( IDC_LOWER_LID_NEUTRAL ); + p.m_flRightLowerLidLowered = GetDlgItemFloat( IDC_LOWER_LID_LOWERED ); + + if ( IsIndependentLeftLidControlEnabled() ) + { + p.m_flLeftUpperLidRaised = GetDlgItemFloat( IDC_UPPER_LEFT_LID_RAISED ); + p.m_flLeftUpperLidNeutral = GetDlgItemFloat( IDC_UPPER_LEFT_LID_NEUTRAL ); + p.m_flLeftUpperLidLowered = GetDlgItemFloat( IDC_UPPER_LEFT_LID_LOWERED ); + + p.m_flLeftLowerLidRaised = GetDlgItemFloat( IDC_LOWER_LEFT_LID_RAISED ); + p.m_flLeftLowerLidNeutral = GetDlgItemFloat( IDC_LOWER_LEFT_LID_NEUTRAL ); + p.m_flLeftLowerLidLowered = GetDlgItemFloat( IDC_LOWER_LEFT_LID_LOWERED ); + } + else + { + // Left lids follow the right lids. + p.m_flLeftUpperLidRaised = p.m_flRightUpperLidRaised; + p.m_flLeftUpperLidNeutral = p.m_flRightUpperLidNeutral; + p.m_flLeftUpperLidLowered = p.m_flRightUpperLidLowered; + + p.m_flLeftLowerLidRaised = p.m_flRightLowerLidRaised; + p.m_flLeftLowerLidNeutral = p.m_flRightLowerLidNeutral; + p.m_flLeftLowerLidLowered = p.m_flRightLowerLidLowered; + } + + // Figure out the eyeball prefix. + if ( IsOptionChecked( IDC_EYE_COLOR_LIGHT ) ) + strcpy( p.m_EyeballPrefix, "eyeball" ); + else + strcpy( p.m_EyeballPrefix, "dark_eyeball" ); + + // Figure out the pupil prefix. + if ( IsOptionChecked( IDC_IRIS_COLOR_BROWN ) ) + strcpy( p.m_PupilPrefix, "pupil" ); + else if ( IsOptionChecked( IDC_IRIS_COLOR_GREEN ) ) + strcpy( p.m_PupilPrefix, "grn_pupil" ); + else + strcpy( p.m_PupilPrefix, "bl_pupil" ); +} + + +void CQC_EyesDlg::GenerateQCText() +{ + CDialogParams p; + GetDialogParams( p ); + + + m_BufSize = 16 * 1024; + m_Buf = new char[m_BufSize]; + m_Buf[0] = 0; + + AddText( "//start eye/face data\n" ); + AddText( "$eyeposition 0 0 70\n\n" ); + + AddText( "//head controllers\n" ); + AddText( "$attachment \"eyes\" \"ValveBiped.Bip01_Head1\" %.2f %.2f %.2f absolute\n", + p.m_flLeftEye[0] - ((fabs( p.m_flRightEye[0] ) + p.m_flLeftEye[0]) * 0.5), + (p.m_flLeftEye[1] + p.m_flRightEye[1]) * 0.5, + (p.m_flLeftEye[2] + p.m_flRightEye[2]) * 0.5 ); + + AddText( "$attachment \"mouth\" \"ValveBiped.Bip01_Head1\" 0.80 -5.80 -0.15 rotate 0 -80 -90\n\n" ); + + AddText( "$model %s \"%s.smd\" {\n", + p.m_ModelFilename, p.m_ReferenceFilename ); + + AddText( "\teyeball righteye \"ValveBiped.Bip01_Head1\" %.2f %.2f %.2f \"%s_r\" %.2f 4 \"%s_r\" %.2f\n", + p.m_flRightEye[0], + p.m_flRightEye[1], + p.m_flRightEye[2], + p.m_EyeballPrefix, + p.m_flEyeballSize, + p.m_PupilPrefix, + p.m_flIrisSize ); + + AddText( "\teyeball lefteye \"ValveBiped.Bip01_Head1\" %.2f %.2f %.2f \"%s_l\" %.2f -4 \"%s_l\" %.2f\n\n", + p.m_flLeftEye[0], + p.m_flLeftEye[1], + p.m_flLeftEye[2], + p.m_EyeballPrefix, + p.m_flEyeballSize, + p.m_PupilPrefix, + p.m_flIrisSize ); + + AddText( "\teyelid upper_right \"%s\" lowerer 1 %.2f neutral 0 %.2f raiser 2 %.2f split 0.1 eyeball righteye\n", + p.m_ExpressionsFilename, + p.m_flRightUpperLidLowered - p.m_flRightEye[2], + p.m_flRightUpperLidNeutral - p.m_flRightEye[2], + p.m_flRightUpperLidRaised - p.m_flRightEye[2] ); + + AddText( "\teyelid lower_right \"%s\" lowerer 3 %.2f neutral 0 %.2f raiser 4 %.2f split 0.1 eyeball righteye\n", + p.m_ExpressionsFilename, + p.m_flRightLowerLidLowered - p.m_flRightEye[2], + p.m_flRightLowerLidNeutral - p.m_flRightEye[2], + p.m_flRightLowerLidRaised - p.m_flRightEye[2] ); + + AddText( "\teyelid upper_left \"%s\" lowerer 1 %.2f neutral 0 %.2f raiser 2 %.2f split -0.1 eyeball lefteye\n", + p.m_ExpressionsFilename, + p.m_flLeftUpperLidLowered - p.m_flLeftEye[2], + p.m_flLeftUpperLidNeutral - p.m_flLeftEye[2], + p.m_flLeftUpperLidRaised - p.m_flLeftEye[2] ); + + AddText( "\teyelid lower_left \"%s\" lowerer 3 %.2f neutral 0 %.2f raiser 4 %.2f split -0.1 eyeball lefteye\n\n", + p.m_ExpressionsFilename, + p.m_flLeftLowerLidLowered - p.m_flLeftEye[2], + p.m_flLeftLowerLidNeutral - p.m_flLeftEye[2], + p.m_flLeftLowerLidRaised - p.m_flLeftEye[2] ); + + AddText( "\tmouth 0 \"mouth\" \"ValveBiped.Bip01_Head1\" 0 1 0 // mouth illumination\n" ); + AddText( "\tflexfile \"%s\" {\n", p.m_ExpressionsFilename ); + AddText( "\t\t$include \"../standardflex_xsi.qci\"\n" ); + AddText( "\t}\n" ); + AddText( "\t$include \"../facerules_xsi.qci\"\n" ); + AddText( "\t$include \"../bodyrules_xsi.qci\"\n" ); + AddText( "}\n" ); + AddText( "//end eye/face data\n" ); +} + + +bool CQC_EyesDlg::CheckNumericInputs() +{ + struct + { + const char *pControlName; + UINT controlID; + } + controls[] = + { + {"Right Eye X", IDC_RIGHT_EYE_X}, + {"Right Eye Y", IDC_RIGHT_EYE_Y}, + {"Right Eye Z", IDC_RIGHT_EYE_Z}, + + {"Left Eye X", IDC_LEFT_EYE_X}, + {"Left Eye Y", IDC_LEFT_EYE_Y}, + {"Left Eye Z", IDC_LEFT_EYE_Z}, + + {"Upper Lid Raised", IDC_UPPER_LID_RAISED}, + {"Upper Lid Neutral", IDC_UPPER_LID_NEUTRAL}, + {"Upper Lid Lowered", IDC_UPPER_LID_LOWERED}, + + {"Lower Lid Raised", IDC_LOWER_LID_RAISED}, + {"Lower Lid Neutral", IDC_LOWER_LID_NEUTRAL}, + {"Lower Lid Lowered", IDC_LOWER_LID_LOWERED}, + + {"Upper Left Lid Raised", IDC_UPPER_LEFT_LID_RAISED}, + {"Upper Left Lid Neutral", IDC_UPPER_LEFT_LID_NEUTRAL}, + {"Upper Left Lid Lowered", IDC_UPPER_LEFT_LID_LOWERED}, + + {"Lower Left Lid Raised", IDC_LOWER_LEFT_LID_RAISED}, + {"Lower Left Lid Neutral", IDC_LOWER_LEFT_LID_NEUTRAL}, + {"Lower Left Lid Lowered", IDC_LOWER_LEFT_LID_LOWERED}, + + {"Iris Size", IDC_IRIS_SIZE}, + {"Eyeball Size", IDC_EYEBALL_SIZE} + }; + + for ( int i=0; i < sizeof( controls ) / sizeof( controls[0] ); i++ ) + { + char text[512]; + GetDlgItem( controls[i].controlID )->GetWindowText( text, sizeof( text ) ); + + for ( int z=0; z < (int)strlen( text ); z++ ) + { + if ( text[z] < '0' || text[z] > '9' ) + { + if ( text[z] != '.' && text[z] != '-' ) + { + char errMsg[512]; + _snprintf( errMsg, sizeof( errMsg ), "The '%s' control must have a numeric value.", controls[i].pControlName ); + AfxMessageBox( errMsg, MB_OK ); + return false; + } + } + } + } + + return true; +} + + +void CQC_EyesDlg::OnCreateQcText() +{ + if ( !CheckNumericInputs() ) + return; + + GenerateQCText(); + + // Clear the edit control. + LRESULT nLen = ::SendMessage( m_hOutputText, EM_GETLIMITTEXT, 0, 0 ); + ::SendMessage( m_hOutputText, EM_SETSEL, 0, nLen ); + ::SendMessage( m_hOutputText, EM_REPLACESEL, FALSE, (LPARAM)"" ); + + FormatAndSendToEditControl( m_hOutputText, m_Buf ); + + delete [] m_Buf; +} + +void CQC_EyesDlg::OnIrisColorBrown() +{ + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BROWN ), BM_SETCHECK, BST_CHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_GREEN ), BM_SETCHECK, BST_UNCHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BLUE ), BM_SETCHECK, BST_UNCHECKED, 0 ); + SetupBitmapLabel( IDB_EYE_DEFAULT, "" ); +} + +void CQC_EyesDlg::OnIrisColorGreen() +{ + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BROWN ), BM_SETCHECK, BST_UNCHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_GREEN ), BM_SETCHECK, BST_CHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BLUE ), BM_SETCHECK, BST_UNCHECKED, 0 ); + SetupBitmapLabel( IDB_EYE_DEFAULT, "" ); +} + +void CQC_EyesDlg::OnIrisColorBlue() +{ + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BROWN ), BM_SETCHECK, BST_UNCHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_GREEN ), BM_SETCHECK, BST_UNCHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BLUE ), BM_SETCHECK, BST_CHECKED, 0 ); + SetupBitmapLabel( IDB_EYE_DEFAULT, "" ); +} + +void CQC_EyesDlg::OnEyeColorDark() +{ + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_EYE_COLOR_LIGHT ), BM_SETCHECK, BST_UNCHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_EYE_COLOR_DARK ), BM_SETCHECK, BST_CHECKED, 0 ); + SetupBitmapLabel( IDB_EYE_DEFAULT, "" ); +} + +void CQC_EyesDlg::OnEyeColorLight() +{ + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_EYE_COLOR_LIGHT ), BM_SETCHECK, BST_CHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_EYE_COLOR_DARK ), BM_SETCHECK, BST_UNCHECKED, 0 ); + SetupBitmapLabel( IDB_EYE_DEFAULT, "" ); +} + +void CQC_EyesDlg::SetupBitmapLabel( UINT iBitmapResourceID, const char *pString, ... ) +{ + char msg[4096]; + va_list marker; + va_start( marker, pString ); + _vsnprintf( msg, sizeof( msg ), pString, marker ); + msg[ ARRAYSIZE(msg) - 1 ] = 0; + va_end( marker ); + + m_PictureControl.SetBitmap( GetCachedBitmap( iBitmapResourceID ) ); + GetDlgItem( IDC_PICTURE_LABEL )->SetWindowText( msg ); +} + +void CQC_EyesDlg::OnSetfocusRightEyeX() +{ + SetupBitmapLabel( IDB_EYE_XY_R, "Enter the X position of the center vertex of the right eye" ); +} + +void CQC_EyesDlg::OnSetfocusRightEyeY() +{ + SetupBitmapLabel( IsOptionChecked( IDC_Y_AXIS_UP ) ? IDB_EYE_XY_R : IDB_EYE_Z_R, "Enter the Y position of the center vertex of the right eye" ); +} + +void CQC_EyesDlg::OnSetfocusRightEyeZ() +{ + SetupBitmapLabel( IsOptionChecked( IDC_Y_AXIS_UP ) ? IDB_EYE_Z_R : IDB_EYE_XY_R, "Enter the Z position of the center vertex of the right eye" ); +} + +void CQC_EyesDlg::OnSetfocusLeftEyeX() +{ + SetupBitmapLabel( IDB_EYE_XY_L, "Enter the X position of the center vertex of the right eye" ); +} + +void CQC_EyesDlg::OnSetfocusLeftEyeY() +{ + SetupBitmapLabel( IsOptionChecked( IDC_Y_AXIS_UP ) ? IDB_EYE_XY_L : IDB_EYE_Z_L, "Enter the Y position of the center vertex of the right eye" ); +} + +void CQC_EyesDlg::OnSetfocusLeftEyeZ() +{ + SetupBitmapLabel( IsOptionChecked( IDC_Y_AXIS_UP ) ? IDB_EYE_Z_L : IDB_EYE_XY_L, "Enter the Z position of the center vertex of the right eye" ); +} + +void CQC_EyesDlg::OnSetfocusUpperLidLowered() +{ + const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; + SetupBitmapLabel( IDB_EYE_UPPER_LO, "At Frame 1, enter the %s position of the center vertex of the right upper eye lid", pCoord ); +} + +void CQC_EyesDlg::OnSetfocusUpperLidNeutral() +{ + const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; + SetupBitmapLabel( IDB_EYE_UPPER_MID, "At Frame 0, enter the %s position of the center vertex of the right upper eye lid", pCoord ); +} + +void CQC_EyesDlg::OnSetfocusUpperLidRaised() +{ + const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; + SetupBitmapLabel( IDB_EYE_UPPER_HI, "At Frame 2, enter the %s position of the center vertex of the right upper eye lid", pCoord ); +} + + +void CQC_EyesDlg::OnSetfocusLowerLidLowered() +{ + const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; + SetupBitmapLabel( IDB_EYE_LOWER_LO, "At Frame 3, enter the %s position of the center vertex of the right lower eye lid", pCoord ); +} + +void CQC_EyesDlg::OnSetfocusLowerLidNeutral() +{ + const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; + SetupBitmapLabel( IDB_EYE_LOWER_MID, "At Frame 0, enter the %s position of the center vertex of the right lower eye lid", pCoord ); +} + +void CQC_EyesDlg::OnSetfocusLowerLidRaised() +{ + const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; + SetupBitmapLabel( IDB_EYE_LOWER_HI, "At Frame 4, enter the %s position of the center vertex of the right lower eye lid", pCoord ); +} + +void CQC_EyesDlg::OnCopyTextToClipboard() +{ + if ( !CheckNumericInputs() ) + return; + + GenerateQCText(); + + if ( !OpenClipboard() ) + return; + + size_t textLen = strlen( m_Buf ); + HANDLE hmem = GlobalAlloc( GMEM_MOVEABLE | GMEM_DDESHARE, textLen + 1 ); + if ( hmem ) + { + void *ptr = GlobalLock( hmem ); + if ( ptr ) + { + memcpy( ptr, m_Buf, textLen+1 ); + GlobalUnlock( hmem ); + + SetClipboardData( CF_TEXT, hmem ); + } + } + + CloseClipboard(); + + delete [] m_Buf; +} + + +int g_AdvancedControls[] = +{ + IDC_EYE_DETAIL_CONTROL_FRAME, + IDC_IRIS_SIZE, + IDC_IRIS_SIZE_LABEL, + IDC_EYEBALL_SIZE, + IDC_EYEBALL_SIZE_LABEL, + IDC_LEFT_LID_CONTROL +}; +#define NUM_ADVANCED_CONTROLS ( sizeof( g_AdvancedControls ) / sizeof( g_AdvancedControls[0] ) ) + +int g_LeftLidPositionControls[] = +{ + IDC_UPPER_LEFT_LID_PANEL, + IDC_UPPER_LEFT_LID_RAISED, + IDC_UPPER_LEFT_LID_RAISED_LABEL, + IDC_UPPER_LEFT_LID_NEUTRAL, + IDC_UPPER_LEFT_LID_NEUTRAL_LABEL, + IDC_UPPER_LEFT_LID_LOWERED, + IDC_UPPER_LEFT_LID_LOWERED_LABEL, + IDC_LOWER_LEFT_LID_PANEL, + IDC_LOWER_LEFT_LID_RAISED, + IDC_LOWER_LEFT_LID_RAISED_LABEL, + IDC_LOWER_LEFT_LID_NEUTRAL, + IDC_LOWER_LEFT_LID_NEUTRAL_LABEL, + IDC_LOWER_LEFT_LID_LOWERED, + IDC_LOWER_LEFT_LID_LOWERED_LABEL +}; +#define NUM_LEFT_LID_POSITION_CONTROLS ( sizeof( g_LeftLidPositionControls ) / sizeof( g_LeftLidPositionControls[0] ) ) + + +void CQC_EyesDlg::OnDefaultControls() +{ + GetDlgItem( IDC_PICTURES )->ShowWindow( SW_SHOW ); + + // Hide all the advanced controls. + for ( int i=0; i < NUM_ADVANCED_CONTROLS; i++ ) + { + GetDlgItem( g_AdvancedControls[i] )->ShowWindow( SW_HIDE ); + } + + for ( int i=0; i < NUM_LEFT_LID_POSITION_CONTROLS; i++ ) + { + GetDlgItem( g_LeftLidPositionControls[i] )->ShowWindow( SW_HIDE ); + } + +} + +void CQC_EyesDlg::OnAdvancedControls() +{ + GetDlgItem( IDC_PICTURES )->ShowWindow( SW_HIDE ); + + // Show the advanced controls. + for ( int i=0; i < NUM_ADVANCED_CONTROLS; i++ ) + { + GetDlgItem( g_AdvancedControls[i] )->ShowWindow( SW_SHOW ); + GetDlgItem( g_AdvancedControls[i] )->InvalidateRect( NULL ); + } + + if ( IsIndependentLeftLidControlEnabled() ) + { + OnLeftLidControl(); + } +} + + +bool CQC_EyesDlg::IsIndependentLeftLidControlEnabled() +{ + return m_IndependentLeftLidControl.GetCheck() == 1; +} + +void CQC_EyesDlg::OnLeftLidControl() +{ + if ( IsIndependentLeftLidControlEnabled() ) + { + for ( int i=0; i < NUM_LEFT_LID_POSITION_CONTROLS; i++ ) + { + GetDlgItem( g_LeftLidPositionControls[i] )->ShowWindow( SW_SHOW ); + GetDlgItem( g_LeftLidPositionControls[i] )->InvalidateRect( NULL ); + } + } + else + { + for ( int i=0; i < NUM_LEFT_LID_POSITION_CONTROLS; i++ ) + { + GetDlgItem( g_LeftLidPositionControls[i] )->ShowWindow( SW_HIDE ); + GetDlgItem( g_LeftLidPositionControls[i] )->InvalidateRect( NULL ); + } + } +} diff --git a/mp/src/utils/qc_eyes/QC_EyesDlg.h b/mp/src/utils/qc_eyes/QC_EyesDlg.h index 62b42b1a..754e9ec5 100644 --- a/mp/src/utils/qc_eyes/QC_EyesDlg.h +++ b/mp/src/utils/qc_eyes/QC_EyesDlg.h @@ -1,134 +1,134 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// QC_EyesDlg.h : header file -// - -#if !defined(AFX_QC_EYESDLG_H__9130E22D_05ED_4851_960C_38D90DA94967__INCLUDED_) -#define AFX_QC_EYESDLG_H__9130E22D_05ED_4851_960C_38D90DA94967__INCLUDED_ - -#if _MSC_VER > 1000 -#pragma once -#endif // _MSC_VER > 1000 - -class CDialogParams -{ -public: - float m_flLeftEye[3]; - float m_flRightEye[3]; - - float m_flIrisSize; - float m_flEyeballSize; - - float m_flLeftUpperLidRaised; - float m_flLeftUpperLidNeutral; - float m_flLeftUpperLidLowered; - - float m_flLeftLowerLidRaised; - float m_flLeftLowerLidNeutral; - float m_flLeftLowerLidLowered; - - float m_flRightUpperLidRaised; - float m_flRightUpperLidNeutral; - float m_flRightUpperLidLowered; - - float m_flRightLowerLidRaised; - float m_flRightLowerLidNeutral; - float m_flRightLowerLidLowered; - - char m_ReferenceFilename[1024]; - char m_ExpressionsFilename[1024]; - char m_ModelFilename[1024]; - - char m_EyeballPrefix[1024]; // eyeball_ or dark_eyeball_ - char m_PupilPrefix[1024]; // pupil_ or grn_pupil_ or bl_pupil_ -}; - -///////////////////////////////////////////////////////////////////////////// -// CQC_EyesDlg dialog - -class CQC_EyesDlg : public CDialog -{ -// Construction -public: - CQC_EyesDlg(CWnd* pParent = NULL); // standard constructor - -// Dialog Data - //{{AFX_DATA(CQC_EyesDlg) - enum { IDD = IDD_QC_EYES_DIALOG }; - CButton m_IndependentLeftLidControl; - CStatic m_PictureControl; - //}}AFX_DATA - - // ClassWizard generated virtual function overrides - //{{AFX_VIRTUAL(CQC_EyesDlg) - protected: - virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support - //}}AFX_VIRTUAL - -// Implementation -protected: - HICON m_hIcon; - - void GenerateQCText(); - void AddText( const char *pFormat, ... ); - bool IsOptionChecked( UINT option ); - float GetDlgItemFloat( UINT id ); - void GetDialogParams( CDialogParams &p ); - void SetupBitmapLabel( UINT iBitmapResourceID, const char *pString, ... ); - - HWND m_hOutputText; - - - // Cached list of bitmaps. - class CBitmapRef - { - public: - UINT m_iResource; - HBITMAP m_hBitmap; - CBitmapRef *m_pNext; - }; - CBitmapRef *m_pBitmapHead; - HBITMAP GetCachedBitmap( UINT id ); - - - size_t m_BufSize; - char *m_Buf; - bool IsIndependentLeftLidControlEnabled(); - - bool CheckNumericInputs(); - - - // Generated message map functions - //{{AFX_MSG(CQC_EyesDlg) - virtual BOOL OnInitDialog(); - afx_msg void OnPaint(); - afx_msg HCURSOR OnQueryDragIcon(); - afx_msg void OnCreateQcText(); - afx_msg void OnIrisColorBrown(); - afx_msg void OnIrisColorGreen(); - afx_msg void OnIrisColorBlue(); - afx_msg void OnEyeColorDark(); - afx_msg void OnEyeColorLight(); - afx_msg void OnSetfocusRightEyeX(); - afx_msg void OnSetfocusRightEyeY(); - afx_msg void OnSetfocusRightEyeZ(); - afx_msg void OnSetfocusLeftEyeX(); - afx_msg void OnSetfocusLeftEyeY(); - afx_msg void OnSetfocusLeftEyeZ(); - afx_msg void OnSetfocusUpperLidLowered(); - afx_msg void OnSetfocusUpperLidNeutral(); - afx_msg void OnSetfocusUpperLidRaised(); - afx_msg void OnSetfocusLowerLidLowered(); - afx_msg void OnSetfocusLowerLidNeutral(); - afx_msg void OnSetfocusLowerLidRaised(); - afx_msg void OnCopyTextToClipboard(); - afx_msg void OnDefaultControls(); - afx_msg void OnAdvancedControls(); - afx_msg void OnLeftLidControl(); - //}}AFX_MSG - DECLARE_MESSAGE_MAP() -}; - -//{{AFX_INSERT_LOCATION}} -// Microsoft Visual C++ will insert additional declarations immediately before the previous line. - -#endif // !defined(AFX_QC_EYESDLG_H__9130E22D_05ED_4851_960C_38D90DA94967__INCLUDED_) +//========= Copyright Valve Corporation, All rights reserved. ============// +// QC_EyesDlg.h : header file +// + +#if !defined(AFX_QC_EYESDLG_H__9130E22D_05ED_4851_960C_38D90DA94967__INCLUDED_) +#define AFX_QC_EYESDLG_H__9130E22D_05ED_4851_960C_38D90DA94967__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +class CDialogParams +{ +public: + float m_flLeftEye[3]; + float m_flRightEye[3]; + + float m_flIrisSize; + float m_flEyeballSize; + + float m_flLeftUpperLidRaised; + float m_flLeftUpperLidNeutral; + float m_flLeftUpperLidLowered; + + float m_flLeftLowerLidRaised; + float m_flLeftLowerLidNeutral; + float m_flLeftLowerLidLowered; + + float m_flRightUpperLidRaised; + float m_flRightUpperLidNeutral; + float m_flRightUpperLidLowered; + + float m_flRightLowerLidRaised; + float m_flRightLowerLidNeutral; + float m_flRightLowerLidLowered; + + char m_ReferenceFilename[1024]; + char m_ExpressionsFilename[1024]; + char m_ModelFilename[1024]; + + char m_EyeballPrefix[1024]; // eyeball_ or dark_eyeball_ + char m_PupilPrefix[1024]; // pupil_ or grn_pupil_ or bl_pupil_ +}; + +///////////////////////////////////////////////////////////////////////////// +// CQC_EyesDlg dialog + +class CQC_EyesDlg : public CDialog +{ +// Construction +public: + CQC_EyesDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CQC_EyesDlg) + enum { IDD = IDD_QC_EYES_DIALOG }; + CButton m_IndependentLeftLidControl; + CStatic m_PictureControl; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CQC_EyesDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + HICON m_hIcon; + + void GenerateQCText(); + void AddText( const char *pFormat, ... ); + bool IsOptionChecked( UINT option ); + float GetDlgItemFloat( UINT id ); + void GetDialogParams( CDialogParams &p ); + void SetupBitmapLabel( UINT iBitmapResourceID, const char *pString, ... ); + + HWND m_hOutputText; + + + // Cached list of bitmaps. + class CBitmapRef + { + public: + UINT m_iResource; + HBITMAP m_hBitmap; + CBitmapRef *m_pNext; + }; + CBitmapRef *m_pBitmapHead; + HBITMAP GetCachedBitmap( UINT id ); + + + size_t m_BufSize; + char *m_Buf; + bool IsIndependentLeftLidControlEnabled(); + + bool CheckNumericInputs(); + + + // Generated message map functions + //{{AFX_MSG(CQC_EyesDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnCreateQcText(); + afx_msg void OnIrisColorBrown(); + afx_msg void OnIrisColorGreen(); + afx_msg void OnIrisColorBlue(); + afx_msg void OnEyeColorDark(); + afx_msg void OnEyeColorLight(); + afx_msg void OnSetfocusRightEyeX(); + afx_msg void OnSetfocusRightEyeY(); + afx_msg void OnSetfocusRightEyeZ(); + afx_msg void OnSetfocusLeftEyeX(); + afx_msg void OnSetfocusLeftEyeY(); + afx_msg void OnSetfocusLeftEyeZ(); + afx_msg void OnSetfocusUpperLidLowered(); + afx_msg void OnSetfocusUpperLidNeutral(); + afx_msg void OnSetfocusUpperLidRaised(); + afx_msg void OnSetfocusLowerLidLowered(); + afx_msg void OnSetfocusLowerLidNeutral(); + afx_msg void OnSetfocusLowerLidRaised(); + afx_msg void OnCopyTextToClipboard(); + afx_msg void OnDefaultControls(); + afx_msg void OnAdvancedControls(); + afx_msg void OnLeftLidControl(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_QC_EYESDLG_H__9130E22D_05ED_4851_960C_38D90DA94967__INCLUDED_) diff --git a/mp/src/utils/qc_eyes/StdAfx.cpp b/mp/src/utils/qc_eyes/StdAfx.cpp index 8fdb97ce..8ffd6415 100644 --- a/mp/src/utils/qc_eyes/StdAfx.cpp +++ b/mp/src/utils/qc_eyes/StdAfx.cpp @@ -1,9 +1,9 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// stdafx.cpp : source file that includes just the standard includes -// QC_Eyes.pch will be the pre-compiled header -// stdafx.obj will contain the pre-compiled type information - -#include "stdafx.h" - - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// stdafx.cpp : source file that includes just the standard includes +// QC_Eyes.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/mp/src/utils/qc_eyes/StdAfx.h b/mp/src/utils/qc_eyes/StdAfx.h index c65813d7..55723a60 100644 --- a/mp/src/utils/qc_eyes/StdAfx.h +++ b/mp/src/utils/qc_eyes/StdAfx.h @@ -1,28 +1,28 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// stdafx.h : include file for standard system include files, -// or project specific include files that are used frequently, but -// are changed infrequently -// - -#if !defined(AFX_STDAFX_H__88FA48B3_A92C_49CA_8A82_50D120A84756__INCLUDED_) -#define AFX_STDAFX_H__88FA48B3_A92C_49CA_8A82_50D120A84756__INCLUDED_ - -#if _MSC_VER > 1000 -#pragma once -#endif // _MSC_VER > 1000 - -#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers - -#include // MFC core and standard components -#include // MFC extensions -#include // MFC Automation classes -#include // MFC support for Internet Explorer 4 Common Controls -#ifndef _AFX_NO_AFXCMN_SUPPORT -#include // MFC support for Windows Common Controls -#endif // _AFX_NO_AFXCMN_SUPPORT - - -//{{AFX_INSERT_LOCATION}} -// Microsoft Visual C++ will insert additional declarations immediately before the previous line. - -#endif // !defined(AFX_STDAFX_H__88FA48B3_A92C_49CA_8A82_50D120A84756__INCLUDED_) +//========= Copyright Valve Corporation, All rights reserved. ============// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__88FA48B3_A92C_49CA_8A82_50D120A84756__INCLUDED_) +#define AFX_STDAFX_H__88FA48B3_A92C_49CA_8A82_50D120A84756__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__88FA48B3_A92C_49CA_8A82_50D120A84756__INCLUDED_) diff --git a/mp/src/utils/qc_eyes/qc_eyes.vpc b/mp/src/utils/qc_eyes/qc_eyes.vpc index 54b35528..ee3b4e39 100644 --- a/mp/src/utils/qc_eyes/qc_eyes.vpc +++ b/mp/src/utils/qc_eyes/qc_eyes.vpc @@ -1,67 +1,67 @@ -//----------------------------------------------------------------------------- -// QC_EYES.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" - -$Include "$SRCDIR\vpc_scripts\source_exe_base.vpc" - -$Configuration -{ - $Compiler - { - $Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)" - $PrecompiledHeaderFile "Debug/QC_Eyes.pch" - $EnableC++Exceptions "Yes (/EHsc)" - } -} - -$Project "QC_Eyes" -{ - $Folder "Source Files" - { - -$File "$SRCDIR\public\tier0\memoverride.cpp" - - $File "QC_Eyes.cpp" - $File "QC_Eyes.rc" - $File "QC_EyesDlg.cpp" - $File "StdAfx.cpp" - { - $Configuration - { - $Compiler - { - $Create/UsePrecompiledHeader "Create Precompiled Header (/Yc)" - } - } - } - } - - $Folder "Header Files" - { - $File "QC_Eyes.h" - $File "QC_EyesDlg.h" - $File "Resource.h" - $File "StdAfx.h" - } - - $Folder "Resources" - { - $File "res\eye_default.bmp" - $File "res\eye_lower_hi.bmp" - $File "res\eye_lower_lo.bmp" - $File "res\eye_lower_mid.bmp" - $File "res\eye_upper_hi.bmp" - $File "res\eye_upper_lo.bmp" - $File "res\eye_upper_mid.bmp" - $File "res\eye_XY_L.bmp" - $File "res\eye_XY_R.bmp" - $File "res\eye_Z_L.bmp" - $File "res\eye_Z_R.bmp" - $File "res\QC_Eyes.ico" - $File "res\QC_Eyes.rc2" - } -} +//----------------------------------------------------------------------------- +// QC_EYES.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_base.vpc" + +$Configuration +{ + $Compiler + { + $Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)" + $PrecompiledHeaderFile "Debug/QC_Eyes.pch" + $EnableC++Exceptions "Yes (/EHsc)" + } +} + +$Project "QC_Eyes" +{ + $Folder "Source Files" + { + -$File "$SRCDIR\public\tier0\memoverride.cpp" + + $File "QC_Eyes.cpp" + $File "QC_Eyes.rc" + $File "QC_EyesDlg.cpp" + $File "StdAfx.cpp" + { + $Configuration + { + $Compiler + { + $Create/UsePrecompiledHeader "Create Precompiled Header (/Yc)" + } + } + } + } + + $Folder "Header Files" + { + $File "QC_Eyes.h" + $File "QC_EyesDlg.h" + $File "Resource.h" + $File "StdAfx.h" + } + + $Folder "Resources" + { + $File "res\eye_default.bmp" + $File "res\eye_lower_hi.bmp" + $File "res\eye_lower_lo.bmp" + $File "res\eye_lower_mid.bmp" + $File "res\eye_upper_hi.bmp" + $File "res\eye_upper_lo.bmp" + $File "res\eye_upper_mid.bmp" + $File "res\eye_XY_L.bmp" + $File "res\eye_XY_R.bmp" + $File "res\eye_Z_L.bmp" + $File "res\eye_Z_R.bmp" + $File "res\QC_Eyes.ico" + $File "res\QC_Eyes.rc2" + } +} diff --git a/mp/src/utils/qc_eyes/resource.h b/mp/src/utils/qc_eyes/resource.h index d16935a9..e963ea22 100644 --- a/mp/src/utils/qc_eyes/resource.h +++ b/mp/src/utils/qc_eyes/resource.h @@ -1,78 +1,78 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -//{{NO_DEPENDENCIES}} -// Microsoft Developer Studio generated include file. -// Used by QC_Eyes.rc -// -#define IDD_QC_EYES_DIALOG 102 -#define IDR_MAINFRAME 128 -#define IDB_EYE_DEFAULT 150 -#define IDB_EYE_LOWER_HI 151 -#define IDB_EYE_LOWER_LO 152 -#define IDB_EYE_LOWER_MID 153 -#define IDB_EYE_UPPER_HI 154 -#define IDB_EYE_UPPER_LO 155 -#define IDB_EYE_UPPER_MID 156 -#define IDB_EYE_XY_L 157 -#define IDB_EYE_XY_R 158 -#define IDB_EYE_Z_L 159 -#define IDB_EYE_Z_R 160 -#define IDC_REFERENCE_FILENAME 1000 -#define IDC_EXPRESSIONS_FILENAME 1001 -#define IDC_MODEL_FILENAME 1002 -#define IDC_RIGHT_EYE_X 1003 -#define IDC_RIGHT_EYE_Y 1004 -#define IDC_RIGHT_EYE_Z 1005 -#define IDC_LEFT_EYE_X 1006 -#define IDC_LEFT_EYE_Y 1007 -#define IDC_LEFT_EYE_Z 1008 -#define IDC_Y_AXIS_UP 1009 -#define IDC_Z_AXIS_UP 1010 -#define IDC_DEFAULT_CONTROLS 1011 -#define IDC_ADVANCED_CONTROLS 1012 -#define IDC_UPPER_LID_RAISED 1013 -#define IDC_OUTPUT_TEXT 1014 -#define IDC_UPPER_LEFT_LID_RAISED 1015 -#define IDC_UPPER_LID_LOWERED 1016 -#define IDC_UPPER_LID_NEUTRAL 1017 -#define IDC_LOWER_LID_RAISED 1018 -#define IDC_LOWER_LID_LOWERED 1019 -#define IDC_LOWER_LID_NEUTRAL 1020 -#define IDC_IRIS_COLOR_GREEN 1021 -#define IDC_IRIS_COLOR_BLUE 1022 -#define IDC_IRIS_COLOR_BROWN 1023 -#define IDC_EYE_COLOR_LIGHT 1024 -#define IDC_EYE_COLOR_DARK 1025 -#define IDC_IRIS_SIZE 1026 -#define IDC_CREATE_QC_TEXT 1027 -#define IDC_EYEBALL_SIZE 1028 -#define IDC_COPY_TEXT_TO_CLIPBOARD 1029 -#define IDC_PICTURES 1030 -#define IDC_PICTURE_LABEL 1031 -#define IDC_LEFT_LID_CONTROL 1032 -#define IDC_UPPER_LEFT_LID_NEUTRAL 1033 -#define IDC_UPPER_LEFT_LID_LOWERED 1034 -#define IDC_LOWER_LEFT_LID_RAISED 1035 -#define IDC_LOWER_LEFT_LID_NEUTRAL 1036 -#define IDC_LOWER_LEFT_LID_LOWERED 1037 -#define IDC_EYE_DETAIL_CONTROL_FRAME 1038 -#define IDC_IRIS_SIZE_LABEL 1039 -#define IDC_EYEBALL_SIZE_LABEL 1040 -#define IDC_UPPER_LEFT_LID_PANEL 1041 -#define IDC_UPPER_LEFT_LID_RAISED_LABEL 1042 -#define IDC_UPPER_LEFT_LID_NEUTRAL_LABEL 1043 -#define IDC_UPPER_LEFT_LID_LOWERED_LABEL 1044 -#define IDC_LOWER_LEFT_LID_PANEL 1045 -#define IDC_LOWER_LEFT_LID_RAISED_LABEL 1046 -#define IDC_LOWER_LEFT_LID_NEUTRAL_LABEL 1047 -#define IDC_LOWER_LEFT_LID_LOWERED_LABEL 1048 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 130 -#define _APS_NEXT_COMMAND_VALUE 32771 -#define _APS_NEXT_CONTROL_VALUE 1049 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//========= Copyright Valve Corporation, All rights reserved. ============// +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by QC_Eyes.rc +// +#define IDD_QC_EYES_DIALOG 102 +#define IDR_MAINFRAME 128 +#define IDB_EYE_DEFAULT 150 +#define IDB_EYE_LOWER_HI 151 +#define IDB_EYE_LOWER_LO 152 +#define IDB_EYE_LOWER_MID 153 +#define IDB_EYE_UPPER_HI 154 +#define IDB_EYE_UPPER_LO 155 +#define IDB_EYE_UPPER_MID 156 +#define IDB_EYE_XY_L 157 +#define IDB_EYE_XY_R 158 +#define IDB_EYE_Z_L 159 +#define IDB_EYE_Z_R 160 +#define IDC_REFERENCE_FILENAME 1000 +#define IDC_EXPRESSIONS_FILENAME 1001 +#define IDC_MODEL_FILENAME 1002 +#define IDC_RIGHT_EYE_X 1003 +#define IDC_RIGHT_EYE_Y 1004 +#define IDC_RIGHT_EYE_Z 1005 +#define IDC_LEFT_EYE_X 1006 +#define IDC_LEFT_EYE_Y 1007 +#define IDC_LEFT_EYE_Z 1008 +#define IDC_Y_AXIS_UP 1009 +#define IDC_Z_AXIS_UP 1010 +#define IDC_DEFAULT_CONTROLS 1011 +#define IDC_ADVANCED_CONTROLS 1012 +#define IDC_UPPER_LID_RAISED 1013 +#define IDC_OUTPUT_TEXT 1014 +#define IDC_UPPER_LEFT_LID_RAISED 1015 +#define IDC_UPPER_LID_LOWERED 1016 +#define IDC_UPPER_LID_NEUTRAL 1017 +#define IDC_LOWER_LID_RAISED 1018 +#define IDC_LOWER_LID_LOWERED 1019 +#define IDC_LOWER_LID_NEUTRAL 1020 +#define IDC_IRIS_COLOR_GREEN 1021 +#define IDC_IRIS_COLOR_BLUE 1022 +#define IDC_IRIS_COLOR_BROWN 1023 +#define IDC_EYE_COLOR_LIGHT 1024 +#define IDC_EYE_COLOR_DARK 1025 +#define IDC_IRIS_SIZE 1026 +#define IDC_CREATE_QC_TEXT 1027 +#define IDC_EYEBALL_SIZE 1028 +#define IDC_COPY_TEXT_TO_CLIPBOARD 1029 +#define IDC_PICTURES 1030 +#define IDC_PICTURE_LABEL 1031 +#define IDC_LEFT_LID_CONTROL 1032 +#define IDC_UPPER_LEFT_LID_NEUTRAL 1033 +#define IDC_UPPER_LEFT_LID_LOWERED 1034 +#define IDC_LOWER_LEFT_LID_RAISED 1035 +#define IDC_LOWER_LEFT_LID_NEUTRAL 1036 +#define IDC_LOWER_LEFT_LID_LOWERED 1037 +#define IDC_EYE_DETAIL_CONTROL_FRAME 1038 +#define IDC_IRIS_SIZE_LABEL 1039 +#define IDC_EYEBALL_SIZE_LABEL 1040 +#define IDC_UPPER_LEFT_LID_PANEL 1041 +#define IDC_UPPER_LEFT_LID_RAISED_LABEL 1042 +#define IDC_UPPER_LEFT_LID_NEUTRAL_LABEL 1043 +#define IDC_UPPER_LEFT_LID_LOWERED_LABEL 1044 +#define IDC_LOWER_LEFT_LID_PANEL 1045 +#define IDC_LOWER_LEFT_LID_RAISED_LABEL 1046 +#define IDC_LOWER_LEFT_LID_NEUTRAL_LABEL 1047 +#define IDC_LOWER_LEFT_LID_LOWERED_LABEL 1048 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 130 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1049 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/mp/src/utils/serverplugin_sample/serverplugin_bot.cpp b/mp/src/utils/serverplugin_sample/serverplugin_bot.cpp index 250bf149..7d900cfd 100644 --- a/mp/src/utils/serverplugin_sample/serverplugin_bot.cpp +++ b/mp/src/utils/serverplugin_sample/serverplugin_bot.cpp @@ -1,383 +1,383 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: Basic BOT handling. -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -#include "interface.h" -#include "filesystem.h" -#undef VECTOR_NO_SLOW_OPERATIONS -#include "mathlib/vector.h" - -#include "eiface.h" -#include "edict.h" -#include "game/server/iplayerinfo.h" -#include "igameevents.h" -#include "convar.h" -#include "vstdlib/random.h" -#include "../../game/shared/in_buttons.h" -#include "../../game/shared/shareddefs.h" -//#include "../../game_shared/util_shared.h" -#include "engine/IEngineTrace.h" - -extern IBotManager *botmanager; -extern IUniformRandomStream *randomStr; -extern IPlayerInfoManager *playerinfomanager; -extern IVEngineServer *engine; -extern IEngineTrace *enginetrace; -extern IPlayerInfoManager *playerinfomanager; // game dll interface to interact with players -extern IServerPluginHelpers *helpers; // special 3rd party plugin helpers from the engine - -extern CGlobalVars *gpGlobals; - -ConVar bot_forcefireweapon( "plugin_bot_forcefireweapon", "", 0, "Force bots with the specified weapon to fire." ); -ConVar bot_forceattack2( "plugin_bot_forceattack2", "0", 0, "When firing, use attack2." ); -ConVar bot_forceattackon( "plugin_bot_forceattackon", "0", 0, "When firing, don't tap fire, hold it down." ); -ConVar bot_flipout( "plugin_bot_flipout", "0", 0, "When on, all bots fire their guns." ); -ConVar bot_changeclass( "plugin_bot_changeclass", "0", 0, "Force all bots to change to the specified class." ); -static ConVar bot_mimic( "plugin_bot_mimic", "0", 0, "Bot uses usercmd of player by index." ); -static ConVar bot_mimic_yaw_offset( "plugin_bot_mimic_yaw_offset", "0", 0, "Offsets the bot yaw." ); - -ConVar bot_sendcmd( "plugin_bot_sendcmd", "", 0, "Forces bots to send the specified command." ); -ConVar bot_crouch( "plugin_bot_crouch", "0", 0, "Bot crouches" ); - - -// This is our bot class. -class CPluginBot -{ -public: - CPluginBot() : - m_bBackwards(0), - m_flNextTurnTime(0), - m_bLastTurnToRight(0), - m_flNextStrafeTime(0), - m_flSideMove(0), - m_ForwardAngle(), - m_LastAngles() - { - } - - bool m_bBackwards; - - float m_flNextTurnTime; - bool m_bLastTurnToRight; - - float m_flNextStrafeTime; - float m_flSideMove; - - QAngle m_ForwardAngle; - QAngle m_LastAngles; - - IBotController *m_BotInterface; - IPlayerInfo *m_PlayerInfo; - edict_t *m_BotEdict; -}; - -CUtlVector s_Bots; - -void Bot_Think( CPluginBot *pBot ); - -// Handler for the "bot" command. -void BotAdd_f() -{ - if ( !botmanager ) - return; - - static int s_BotNum = 0; - char botName[64]; - Q_snprintf( botName, sizeof(botName), "Bot_%i", s_BotNum ); - s_BotNum++; - - edict_t *botEdict = botmanager->CreateBot( botName ); - if ( botEdict ) - { - int botIndex = s_Bots.AddToTail(); - CPluginBot & bot = s_Bots[ botIndex ]; - bot.m_BotInterface = botmanager->GetBotController( botEdict ); - bot.m_PlayerInfo = playerinfomanager->GetPlayerInfo( botEdict ); - bot.m_BotEdict = botEdict; - Assert( bot.m_BotInterface ); - } -} - -ConCommand cc_Bot( "plugin_bot_add", BotAdd_f, "Add a bot." ); - - -//----------------------------------------------------------------------------- -// Purpose: Run through all the Bots in the game and let them think. -//----------------------------------------------------------------------------- -void Bot_RunAll( void ) -{ - if ( !botmanager ) - return; - - for ( int i = 0; i < s_Bots.Count(); i++ ) - { - CPluginBot & bot = s_Bots[i]; - if ( bot.m_BotEdict->IsFree() || !bot.m_BotEdict->GetUnknown()|| !bot.m_PlayerInfo->IsConnected() ) - { - s_Bots.Remove(i); - --i; - } - else - { - Bot_Think( &bot ); - } - } -} - -bool Bot_RunMimicCommand( CBotCmd& cmd ) -{ - if ( bot_mimic.GetInt() <= 0 ) - return false; - - if ( bot_mimic.GetInt() > gpGlobals->maxClients ) - return false; - - IPlayerInfo *playerInfo = playerinfomanager->GetPlayerInfo( engine->PEntityOfEntIndex( bot_mimic.GetInt() ) ); - if ( !playerInfo ) - return false; - - cmd = playerInfo->GetLastUserCommand(); - cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat(); - - if( bot_crouch.GetInt() ) - cmd.buttons |= IN_DUCK; - - return true; -} - -void Bot_UpdateStrafing( CPluginBot *pBot, CBotCmd &cmd ) -{ - if ( gpGlobals->curtime >= pBot->m_flNextStrafeTime ) - { - pBot->m_flNextStrafeTime = gpGlobals->curtime + 1.0f; - - if ( randomStr->RandomInt( 0, 5 ) == 0 ) - { - pBot->m_flSideMove = -600.0f + 1200.0f * randomStr->RandomFloat( 0, 2 ); - } - else - { - pBot->m_flSideMove = 0; - } - cmd.sidemove = pBot->m_flSideMove; - - if ( randomStr->RandomInt( 0, 20 ) == 0 ) - { - pBot->m_bBackwards = true; - } - else - { - pBot->m_bBackwards = false; - } - } -} - -void Bot_UpdateDirection( CPluginBot *pBot ) -{ - float angledelta = 15.0; - - int maxtries = (int)360.0/angledelta; - - if ( pBot->m_bLastTurnToRight ) - { - angledelta = -angledelta; - } - - QAngle angle( pBot->m_BotInterface->GetLocalAngles() ); - - trace_t trace; - Vector vecSrc, vecEnd, forward; - while ( --maxtries >= 0 ) - { - AngleVectors( angle, &forward ); - - vecSrc = pBot->m_BotInterface->GetLocalOrigin() + Vector( 0, 0, 36 ); - vecEnd = vecSrc + forward * 10; - - Ray_t ray; - ray.Init( vecSrc, vecEnd, Vector(-16, -16, 0 ), Vector( 16, 16, 72 ) ); - CTraceFilterWorldAndPropsOnly traceFilter; - enginetrace->TraceRay( ray, MASK_PLAYERSOLID, &traceFilter, &trace ); - - if ( trace.fraction == 1.0 ) - { - if ( gpGlobals->curtime < pBot->m_flNextTurnTime ) - { - break; - } - } - - angle.y += angledelta; - - if ( angle.y > 180 ) - angle.y -= 360; - else if ( angle.y < -180 ) - angle.y += 360; - - pBot->m_flNextTurnTime = gpGlobals->curtime + 2.0; - pBot->m_bLastTurnToRight = randomStr->RandomInt( 0, 1 ) == 0 ? true : false; - - pBot->m_ForwardAngle = angle; - pBot->m_LastAngles = angle; - } - - pBot->m_BotInterface->SetLocalAngles( angle ); -} - - -void Bot_FlipOut( CPluginBot *pBot, CBotCmd &cmd ) -{ - if ( bot_flipout.GetInt() > 0 && !pBot->m_PlayerInfo->IsDead() ) - { - if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) ) - { - cmd.buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK; - } - - if ( bot_flipout.GetInt() >= 2 ) - { - QAngle angOffset = RandomAngle( -1, 1 ); - - pBot->m_LastAngles += angOffset; - - for ( int i = 0 ; i < 2; i++ ) - { - if ( fabs( pBot->m_LastAngles[ i ] - pBot->m_ForwardAngle[ i ] ) > 15.0f ) - { - if ( pBot->m_LastAngles[ i ] > pBot->m_ForwardAngle[ i ] ) - { - pBot->m_LastAngles[ i ] = pBot->m_ForwardAngle[ i ] + 15; - } - else - { - pBot->m_LastAngles[ i ] = pBot->m_ForwardAngle[ i ] - 15; - } - } - } - - pBot->m_LastAngles[ 2 ] = 0; - - pBot->m_BotInterface->SetLocalAngles( pBot->m_LastAngles ); - } - } -} - - -void Bot_HandleSendCmd( CPluginBot *pBot ) -{ - if ( strlen( bot_sendcmd.GetString() ) > 0 ) - { - //send the cmd from this bot - helpers->ClientCommand( pBot->m_BotEdict, bot_sendcmd.GetString() ); - - bot_sendcmd.SetValue(""); - } -} - - -// If bots are being forced to fire a weapon, see if I have it -void Bot_ForceFireWeapon( CPluginBot *pBot, CBotCmd &cmd ) -{ - if ( Q_strlen( bot_forcefireweapon.GetString() ) > 0 ) - { - pBot->m_BotInterface->SetActiveWeapon( bot_forcefireweapon.GetString() ); - bot_forcefireweapon.SetValue( "" ); - // Start firing - // Some weapons require releases, so randomise firing - if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) ) - { - cmd.buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK; - } - } -} - - -void Bot_SetForwardMovement( CPluginBot *pBot, CBotCmd &cmd ) -{ - if ( !pBot->m_BotInterface->IsEFlagSet(EFL_BOT_FROZEN) ) - { - if ( pBot->m_PlayerInfo->GetHealth() == 100 ) - { - cmd.forwardmove = 600 * ( pBot->m_bBackwards ? -1 : 1 ); - if ( pBot->m_flSideMove != 0.0f ) - { - cmd.forwardmove *= randomStr->RandomFloat( 0.1, 1.0f ); - } - } - else - { - // Stop when shot - cmd.forwardmove = 0; - } - } -} - - -void Bot_HandleRespawn( CPluginBot *pBot, CBotCmd &cmd ) -{ - // Wait for Reinforcement wave - if ( pBot->m_PlayerInfo->IsDead() ) - { - if ( pBot->m_PlayerInfo->GetTeamIndex() == 0 ) - { - helpers->ClientCommand( pBot->m_BotEdict, "joingame" ); - helpers->ClientCommand( pBot->m_BotEdict, "jointeam 3" ); - helpers->ClientCommand( pBot->m_BotEdict, "joinclass 0" ); - } - } -} - - -//----------------------------------------------------------------------------- -// Run this Bot's AI for one frame. -//----------------------------------------------------------------------------- -void Bot_Think( CPluginBot *pBot ) -{ - CBotCmd cmd; - Q_memset( &cmd, 0, sizeof( cmd ) ); - - // Finally, override all this stuff if the bot is being forced to mimic a player. - if ( !Bot_RunMimicCommand( cmd ) ) - { - cmd.sidemove = pBot->m_flSideMove; - - if ( !pBot->m_PlayerInfo->IsDead() ) - { - Bot_SetForwardMovement( pBot, cmd ); - - // Only turn if I haven't been hurt - if ( !pBot->m_BotInterface->IsEFlagSet(EFL_BOT_FROZEN) && pBot->m_PlayerInfo->GetHealth() == 100 ) - { - Bot_UpdateDirection( pBot ); - Bot_UpdateStrafing( pBot, cmd ); - } - - // Handle console settings. - Bot_ForceFireWeapon( pBot, cmd ); - Bot_HandleSendCmd( pBot ); - } - else - { - Bot_HandleRespawn( pBot, cmd ); - } - - Bot_FlipOut( pBot, cmd ); - - cmd.viewangles = pBot->m_BotInterface->GetLocalAngles(); - cmd.upmove = 0; - cmd.impulse = 0; - } - - pBot->m_BotInterface->RunPlayerMove( &cmd ); -} - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Basic BOT handling. +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#include "interface.h" +#include "filesystem.h" +#undef VECTOR_NO_SLOW_OPERATIONS +#include "mathlib/vector.h" + +#include "eiface.h" +#include "edict.h" +#include "game/server/iplayerinfo.h" +#include "igameevents.h" +#include "convar.h" +#include "vstdlib/random.h" +#include "../../game/shared/in_buttons.h" +#include "../../game/shared/shareddefs.h" +//#include "../../game_shared/util_shared.h" +#include "engine/IEngineTrace.h" + +extern IBotManager *botmanager; +extern IUniformRandomStream *randomStr; +extern IPlayerInfoManager *playerinfomanager; +extern IVEngineServer *engine; +extern IEngineTrace *enginetrace; +extern IPlayerInfoManager *playerinfomanager; // game dll interface to interact with players +extern IServerPluginHelpers *helpers; // special 3rd party plugin helpers from the engine + +extern CGlobalVars *gpGlobals; + +ConVar bot_forcefireweapon( "plugin_bot_forcefireweapon", "", 0, "Force bots with the specified weapon to fire." ); +ConVar bot_forceattack2( "plugin_bot_forceattack2", "0", 0, "When firing, use attack2." ); +ConVar bot_forceattackon( "plugin_bot_forceattackon", "0", 0, "When firing, don't tap fire, hold it down." ); +ConVar bot_flipout( "plugin_bot_flipout", "0", 0, "When on, all bots fire their guns." ); +ConVar bot_changeclass( "plugin_bot_changeclass", "0", 0, "Force all bots to change to the specified class." ); +static ConVar bot_mimic( "plugin_bot_mimic", "0", 0, "Bot uses usercmd of player by index." ); +static ConVar bot_mimic_yaw_offset( "plugin_bot_mimic_yaw_offset", "0", 0, "Offsets the bot yaw." ); + +ConVar bot_sendcmd( "plugin_bot_sendcmd", "", 0, "Forces bots to send the specified command." ); +ConVar bot_crouch( "plugin_bot_crouch", "0", 0, "Bot crouches" ); + + +// This is our bot class. +class CPluginBot +{ +public: + CPluginBot() : + m_bBackwards(0), + m_flNextTurnTime(0), + m_bLastTurnToRight(0), + m_flNextStrafeTime(0), + m_flSideMove(0), + m_ForwardAngle(), + m_LastAngles() + { + } + + bool m_bBackwards; + + float m_flNextTurnTime; + bool m_bLastTurnToRight; + + float m_flNextStrafeTime; + float m_flSideMove; + + QAngle m_ForwardAngle; + QAngle m_LastAngles; + + IBotController *m_BotInterface; + IPlayerInfo *m_PlayerInfo; + edict_t *m_BotEdict; +}; + +CUtlVector s_Bots; + +void Bot_Think( CPluginBot *pBot ); + +// Handler for the "bot" command. +void BotAdd_f() +{ + if ( !botmanager ) + return; + + static int s_BotNum = 0; + char botName[64]; + Q_snprintf( botName, sizeof(botName), "Bot_%i", s_BotNum ); + s_BotNum++; + + edict_t *botEdict = botmanager->CreateBot( botName ); + if ( botEdict ) + { + int botIndex = s_Bots.AddToTail(); + CPluginBot & bot = s_Bots[ botIndex ]; + bot.m_BotInterface = botmanager->GetBotController( botEdict ); + bot.m_PlayerInfo = playerinfomanager->GetPlayerInfo( botEdict ); + bot.m_BotEdict = botEdict; + Assert( bot.m_BotInterface ); + } +} + +ConCommand cc_Bot( "plugin_bot_add", BotAdd_f, "Add a bot." ); + + +//----------------------------------------------------------------------------- +// Purpose: Run through all the Bots in the game and let them think. +//----------------------------------------------------------------------------- +void Bot_RunAll( void ) +{ + if ( !botmanager ) + return; + + for ( int i = 0; i < s_Bots.Count(); i++ ) + { + CPluginBot & bot = s_Bots[i]; + if ( bot.m_BotEdict->IsFree() || !bot.m_BotEdict->GetUnknown()|| !bot.m_PlayerInfo->IsConnected() ) + { + s_Bots.Remove(i); + --i; + } + else + { + Bot_Think( &bot ); + } + } +} + +bool Bot_RunMimicCommand( CBotCmd& cmd ) +{ + if ( bot_mimic.GetInt() <= 0 ) + return false; + + if ( bot_mimic.GetInt() > gpGlobals->maxClients ) + return false; + + IPlayerInfo *playerInfo = playerinfomanager->GetPlayerInfo( engine->PEntityOfEntIndex( bot_mimic.GetInt() ) ); + if ( !playerInfo ) + return false; + + cmd = playerInfo->GetLastUserCommand(); + cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat(); + + if( bot_crouch.GetInt() ) + cmd.buttons |= IN_DUCK; + + return true; +} + +void Bot_UpdateStrafing( CPluginBot *pBot, CBotCmd &cmd ) +{ + if ( gpGlobals->curtime >= pBot->m_flNextStrafeTime ) + { + pBot->m_flNextStrafeTime = gpGlobals->curtime + 1.0f; + + if ( randomStr->RandomInt( 0, 5 ) == 0 ) + { + pBot->m_flSideMove = -600.0f + 1200.0f * randomStr->RandomFloat( 0, 2 ); + } + else + { + pBot->m_flSideMove = 0; + } + cmd.sidemove = pBot->m_flSideMove; + + if ( randomStr->RandomInt( 0, 20 ) == 0 ) + { + pBot->m_bBackwards = true; + } + else + { + pBot->m_bBackwards = false; + } + } +} + +void Bot_UpdateDirection( CPluginBot *pBot ) +{ + float angledelta = 15.0; + + int maxtries = (int)360.0/angledelta; + + if ( pBot->m_bLastTurnToRight ) + { + angledelta = -angledelta; + } + + QAngle angle( pBot->m_BotInterface->GetLocalAngles() ); + + trace_t trace; + Vector vecSrc, vecEnd, forward; + while ( --maxtries >= 0 ) + { + AngleVectors( angle, &forward ); + + vecSrc = pBot->m_BotInterface->GetLocalOrigin() + Vector( 0, 0, 36 ); + vecEnd = vecSrc + forward * 10; + + Ray_t ray; + ray.Init( vecSrc, vecEnd, Vector(-16, -16, 0 ), Vector( 16, 16, 72 ) ); + CTraceFilterWorldAndPropsOnly traceFilter; + enginetrace->TraceRay( ray, MASK_PLAYERSOLID, &traceFilter, &trace ); + + if ( trace.fraction == 1.0 ) + { + if ( gpGlobals->curtime < pBot->m_flNextTurnTime ) + { + break; + } + } + + angle.y += angledelta; + + if ( angle.y > 180 ) + angle.y -= 360; + else if ( angle.y < -180 ) + angle.y += 360; + + pBot->m_flNextTurnTime = gpGlobals->curtime + 2.0; + pBot->m_bLastTurnToRight = randomStr->RandomInt( 0, 1 ) == 0 ? true : false; + + pBot->m_ForwardAngle = angle; + pBot->m_LastAngles = angle; + } + + pBot->m_BotInterface->SetLocalAngles( angle ); +} + + +void Bot_FlipOut( CPluginBot *pBot, CBotCmd &cmd ) +{ + if ( bot_flipout.GetInt() > 0 && !pBot->m_PlayerInfo->IsDead() ) + { + if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) ) + { + cmd.buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK; + } + + if ( bot_flipout.GetInt() >= 2 ) + { + QAngle angOffset = RandomAngle( -1, 1 ); + + pBot->m_LastAngles += angOffset; + + for ( int i = 0 ; i < 2; i++ ) + { + if ( fabs( pBot->m_LastAngles[ i ] - pBot->m_ForwardAngle[ i ] ) > 15.0f ) + { + if ( pBot->m_LastAngles[ i ] > pBot->m_ForwardAngle[ i ] ) + { + pBot->m_LastAngles[ i ] = pBot->m_ForwardAngle[ i ] + 15; + } + else + { + pBot->m_LastAngles[ i ] = pBot->m_ForwardAngle[ i ] - 15; + } + } + } + + pBot->m_LastAngles[ 2 ] = 0; + + pBot->m_BotInterface->SetLocalAngles( pBot->m_LastAngles ); + } + } +} + + +void Bot_HandleSendCmd( CPluginBot *pBot ) +{ + if ( strlen( bot_sendcmd.GetString() ) > 0 ) + { + //send the cmd from this bot + helpers->ClientCommand( pBot->m_BotEdict, bot_sendcmd.GetString() ); + + bot_sendcmd.SetValue(""); + } +} + + +// If bots are being forced to fire a weapon, see if I have it +void Bot_ForceFireWeapon( CPluginBot *pBot, CBotCmd &cmd ) +{ + if ( Q_strlen( bot_forcefireweapon.GetString() ) > 0 ) + { + pBot->m_BotInterface->SetActiveWeapon( bot_forcefireweapon.GetString() ); + bot_forcefireweapon.SetValue( "" ); + // Start firing + // Some weapons require releases, so randomise firing + if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) ) + { + cmd.buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK; + } + } +} + + +void Bot_SetForwardMovement( CPluginBot *pBot, CBotCmd &cmd ) +{ + if ( !pBot->m_BotInterface->IsEFlagSet(EFL_BOT_FROZEN) ) + { + if ( pBot->m_PlayerInfo->GetHealth() == 100 ) + { + cmd.forwardmove = 600 * ( pBot->m_bBackwards ? -1 : 1 ); + if ( pBot->m_flSideMove != 0.0f ) + { + cmd.forwardmove *= randomStr->RandomFloat( 0.1, 1.0f ); + } + } + else + { + // Stop when shot + cmd.forwardmove = 0; + } + } +} + + +void Bot_HandleRespawn( CPluginBot *pBot, CBotCmd &cmd ) +{ + // Wait for Reinforcement wave + if ( pBot->m_PlayerInfo->IsDead() ) + { + if ( pBot->m_PlayerInfo->GetTeamIndex() == 0 ) + { + helpers->ClientCommand( pBot->m_BotEdict, "joingame" ); + helpers->ClientCommand( pBot->m_BotEdict, "jointeam 3" ); + helpers->ClientCommand( pBot->m_BotEdict, "joinclass 0" ); + } + } +} + + +//----------------------------------------------------------------------------- +// Run this Bot's AI for one frame. +//----------------------------------------------------------------------------- +void Bot_Think( CPluginBot *pBot ) +{ + CBotCmd cmd; + Q_memset( &cmd, 0, sizeof( cmd ) ); + + // Finally, override all this stuff if the bot is being forced to mimic a player. + if ( !Bot_RunMimicCommand( cmd ) ) + { + cmd.sidemove = pBot->m_flSideMove; + + if ( !pBot->m_PlayerInfo->IsDead() ) + { + Bot_SetForwardMovement( pBot, cmd ); + + // Only turn if I haven't been hurt + if ( !pBot->m_BotInterface->IsEFlagSet(EFL_BOT_FROZEN) && pBot->m_PlayerInfo->GetHealth() == 100 ) + { + Bot_UpdateDirection( pBot ); + Bot_UpdateStrafing( pBot, cmd ); + } + + // Handle console settings. + Bot_ForceFireWeapon( pBot, cmd ); + Bot_HandleSendCmd( pBot ); + } + else + { + Bot_HandleRespawn( pBot, cmd ); + } + + Bot_FlipOut( pBot, cmd ); + + cmd.viewangles = pBot->m_BotInterface->GetLocalAngles(); + cmd.upmove = 0; + cmd.impulse = 0; + } + + pBot->m_BotInterface->RunPlayerMove( &cmd ); +} + + diff --git a/mp/src/utils/serverplugin_sample/serverplugin_empty.cpp b/mp/src/utils/serverplugin_sample/serverplugin_empty.cpp index ff82b5f6..42e4c1d3 100644 --- a/mp/src/utils/serverplugin_sample/serverplugin_empty.cpp +++ b/mp/src/utils/serverplugin_sample/serverplugin_empty.cpp @@ -1,922 +1,922 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//===========================================================================// - -#include - -//#define GAME_DLL -#ifdef GAME_DLL -#include "cbase.h" -#endif - -#include -#include "interface.h" -#include "filesystem.h" -#include "engine/iserverplugin.h" -#include "eiface.h" -#include "igameevents.h" -#include "convar.h" -#include "Color.h" -#include "vstdlib/random.h" -#include "engine/IEngineTrace.h" -#include "tier2/tier2.h" -#include "game/server/pluginvariant.h" -#include "game/server/iplayerinfo.h" -#include "game/server/ientityinfo.h" -#include "game/server/igameinfo.h" - -//#define SAMPLE_TF2_PLUGIN -#ifdef SAMPLE_TF2_PLUGIN -#include "tf/tf_shareddefs.h" -#endif -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -// Interfaces from the engine -IVEngineServer *engine = NULL; // helper functions (messaging clients, loading content, making entities, running commands, etc) -IGameEventManager *gameeventmanager_ = NULL; // game events interface -#ifndef GAME_DLL -#define gameeventmanager gameeventmanager_ -#endif -IPlayerInfoManager *playerinfomanager = NULL; // game dll interface to interact with players -IEntityInfoManager *entityinfomanager = NULL; // game dll interface to interact with all entities (like IPlayerInfo) -IGameInfoManager *gameinfomanager = NULL; // game dll interface to get data from game rules directly -IBotManager *botmanager = NULL; // game dll interface to interact with bots -IServerPluginHelpers *helpers = NULL; // special 3rd party plugin helpers from the engine -IUniformRandomStream *randomStr = NULL; -IEngineTrace *enginetrace = NULL; - - -CGlobalVars *gpGlobals = NULL; - -// function to initialize any cvars/command in this plugin -void Bot_RunAll( void ); - -// useful helper func -#ifndef GAME_DLL -inline bool FStrEq(const char *sz1, const char *sz2) -{ - return(Q_stricmp(sz1, sz2) == 0); -} -#endif -//--------------------------------------------------------------------------------- -// Purpose: a sample 3rd party plugin class -//--------------------------------------------------------------------------------- -class CEmptyServerPlugin: public IServerPluginCallbacks, public IGameEventListener -{ -public: - CEmptyServerPlugin(); - ~CEmptyServerPlugin(); - - // IServerPluginCallbacks methods - virtual bool Load( CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory ); - virtual void Unload( void ); - virtual void Pause( void ); - virtual void UnPause( void ); - virtual const char *GetPluginDescription( void ); - virtual void LevelInit( char const *pMapName ); - virtual void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ); - virtual void GameFrame( bool simulating ); - virtual void LevelShutdown( void ); - virtual void ClientActive( edict_t *pEntity ); - virtual void ClientDisconnect( edict_t *pEntity ); - virtual void ClientPutInServer( edict_t *pEntity, char const *playername ); - virtual void SetCommandClient( int index ); - virtual void ClientSettingsChanged( edict_t *pEdict ); - virtual PLUGIN_RESULT ClientConnect( bool *bAllowConnect, edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ); - virtual PLUGIN_RESULT ClientCommand( edict_t *pEntity, const CCommand &args ); - virtual PLUGIN_RESULT NetworkIDValidated( const char *pszUserName, const char *pszNetworkID ); - virtual void OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue ); - virtual void OnEdictAllocated( edict_t *edict ); - virtual void OnEdictFreed( const edict_t *edict ); - - // IGameEventListener Interface - virtual void FireGameEvent( KeyValues * event ); - - virtual int GetCommandIndex() { return m_iClientCommandIndex; } -private: - int m_iClientCommandIndex; -}; - - -// -// The plugin is a static singleton that is exported as an interface -// -CEmptyServerPlugin g_EmtpyServerPlugin; -EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CEmptyServerPlugin, IServerPluginCallbacks, INTERFACEVERSION_ISERVERPLUGINCALLBACKS, g_EmtpyServerPlugin ); - -//--------------------------------------------------------------------------------- -// Purpose: constructor/destructor -//--------------------------------------------------------------------------------- -CEmptyServerPlugin::CEmptyServerPlugin() -{ - m_iClientCommandIndex = 0; -} - -CEmptyServerPlugin::~CEmptyServerPlugin() -{ -} - -//--------------------------------------------------------------------------------- -// Purpose: called when the plugin is loaded, load the interface we need from the engine -//--------------------------------------------------------------------------------- -bool CEmptyServerPlugin::Load( CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory ) -{ - ConnectTier1Libraries( &interfaceFactory, 1 ); - ConnectTier2Libraries( &interfaceFactory, 1 ); - - entityinfomanager = (IEntityInfoManager *)gameServerFactory(INTERFACEVERSION_ENTITYINFOMANAGER,NULL); - if ( !entityinfomanager ) - { - Warning( "Unable to load entityinfomanager, ignoring\n" ); // this isn't fatal, we just won't be able to access entity data - } - - playerinfomanager = (IPlayerInfoManager *)gameServerFactory(INTERFACEVERSION_PLAYERINFOMANAGER,NULL); - if ( !playerinfomanager ) - { - Warning( "Unable to load playerinfomanager, ignoring\n" ); // this isn't fatal, we just won't be able to access specific player data - } - - botmanager = (IBotManager *)gameServerFactory(INTERFACEVERSION_PLAYERBOTMANAGER, NULL); - if ( !botmanager ) - { - Warning( "Unable to load botcontroller, ignoring\n" ); // this isn't fatal, we just won't be able to access specific bot functions - } - gameinfomanager = (IGameInfoManager *)gameServerFactory(INTERFACEVERSION_GAMEINFOMANAGER, NULL); - if (!gameinfomanager) - { - Warning( "Unable to load gameinfomanager, ignoring\n" ); - } - - engine = (IVEngineServer*)interfaceFactory(INTERFACEVERSION_VENGINESERVER, NULL); - gameeventmanager = (IGameEventManager *)interfaceFactory(INTERFACEVERSION_GAMEEVENTSMANAGER,NULL); - helpers = (IServerPluginHelpers*)interfaceFactory(INTERFACEVERSION_ISERVERPLUGINHELPERS, NULL); - enginetrace = (IEngineTrace *)interfaceFactory(INTERFACEVERSION_ENGINETRACE_SERVER,NULL); - randomStr = (IUniformRandomStream *)interfaceFactory(VENGINE_SERVER_RANDOM_INTERFACE_VERSION, NULL); - - // get the interfaces we want to use - if( ! ( engine && gameeventmanager && g_pFullFileSystem && helpers && enginetrace && randomStr ) ) - { - return false; // we require all these interface to function - } - - if ( playerinfomanager ) - { - gpGlobals = playerinfomanager->GetGlobalVars(); - } - - MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); - ConVar_Register( 0 ); - return true; -} - -//--------------------------------------------------------------------------------- -// Purpose: called when the plugin is unloaded (turned off) -//--------------------------------------------------------------------------------- -void CEmptyServerPlugin::Unload( void ) -{ - gameeventmanager->RemoveListener( this ); // make sure we are unloaded from the event system - - ConVar_Unregister( ); - DisconnectTier2Libraries( ); - DisconnectTier1Libraries( ); -} - -//--------------------------------------------------------------------------------- -// Purpose: called when the plugin is paused (i.e should stop running but isn't unloaded) -//--------------------------------------------------------------------------------- -void CEmptyServerPlugin::Pause( void ) -{ -} - -//--------------------------------------------------------------------------------- -// Purpose: called when the plugin is unpaused (i.e should start executing again) -//--------------------------------------------------------------------------------- -void CEmptyServerPlugin::UnPause( void ) -{ -} - -//--------------------------------------------------------------------------------- -// Purpose: the name of this plugin, returned in "plugin_print" command -//--------------------------------------------------------------------------------- -const char *CEmptyServerPlugin::GetPluginDescription( void ) -{ - return "Emtpy-Plugin V2, Valve"; -} - -//--------------------------------------------------------------------------------- -// Purpose: called on level start -//--------------------------------------------------------------------------------- -void CEmptyServerPlugin::LevelInit( char const *pMapName ) -{ - Msg( "Level \"%s\" has been loaded\n", pMapName ); - gameeventmanager->AddListener( this, true ); -} - -//--------------------------------------------------------------------------------- -// Purpose: called on level start, when the server is ready to accept client connections -// edictCount is the number of entities in the level, clientMax is the max client count -//--------------------------------------------------------------------------------- -void CEmptyServerPlugin::ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) -{ -} - -//--------------------------------------------------------------------------------- -// Purpose: called once per server frame, do recurring work here (like checking for timeouts) -//--------------------------------------------------------------------------------- -void CEmptyServerPlugin::GameFrame( bool simulating ) -{ - if ( simulating ) - { - Bot_RunAll(); - } -} - -//--------------------------------------------------------------------------------- -// Purpose: called on level end (as the server is shutting down or going to a new map) -//--------------------------------------------------------------------------------- -void CEmptyServerPlugin::LevelShutdown( void ) // !!!!this can get called multiple times per map change -{ - gameeventmanager->RemoveListener( this ); -} - -//--------------------------------------------------------------------------------- -// Purpose: called when a client spawns into a server (i.e as they begin to play) -//--------------------------------------------------------------------------------- -void CEmptyServerPlugin::ClientActive( edict_t *pEntity ) -{ -} - -//--------------------------------------------------------------------------------- -// Purpose: called when a client leaves a server (or is timed out) -//--------------------------------------------------------------------------------- -void CEmptyServerPlugin::ClientDisconnect( edict_t *pEntity ) -{ -} - -//--------------------------------------------------------------------------------- -// Purpose: called on -//--------------------------------------------------------------------------------- -void CEmptyServerPlugin::ClientPutInServer( edict_t *pEntity, char const *playername ) -{ - KeyValues *kv = new KeyValues( "msg" ); - kv->SetString( "title", "Hello" ); - kv->SetString( "msg", "Hello there" ); - kv->SetColor( "color", Color( 255, 0, 0, 255 )); - kv->SetInt( "level", 5); - kv->SetInt( "time", 10); - helpers->CreateMessage( pEntity, DIALOG_MSG, kv, this ); - kv->deleteThis(); -} - -//--------------------------------------------------------------------------------- -// Purpose: called on level start -//--------------------------------------------------------------------------------- -void CEmptyServerPlugin::SetCommandClient( int index ) -{ - m_iClientCommandIndex = index; -} - -void ClientPrint( edict_t *pEdict, char *format, ... ) -{ - va_list argptr; - static char string[1024]; - - va_start (argptr, format); - Q_vsnprintf(string, sizeof(string), format,argptr); - va_end (argptr); - - engine->ClientPrintf( pEdict, string ); -} -//--------------------------------------------------------------------------------- -// Purpose: called on level start -//--------------------------------------------------------------------------------- -void CEmptyServerPlugin::ClientSettingsChanged( edict_t *pEdict ) -{ - if ( playerinfomanager ) - { - IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEdict ); - - const char * name = engine->GetClientConVarValue( engine->IndexOfEdict(pEdict), "name" ); - - // CAN'T use Q_stricmp here, this dll is made by 3rd parties and may not link to tier0/vstdlib - if ( playerinfo && name && playerinfo->GetName() && - stricmp( name, playerinfo->GetName()) ) // playerinfo may be NULL if the MOD doesn't support access to player data - // OR if you are accessing the player before they are fully connected - { - ClientPrint( pEdict, "Your name changed to \"%s\" (from \"%s\"\n", name, playerinfo->GetName() ); - // this is the bad way to check this, the better option it to listen for the "player_changename" event in FireGameEvent() - // this is here to give a real example of how to use the playerinfo interface - } - } -} - -//--------------------------------------------------------------------------------- -// Purpose: called when a client joins a server -//--------------------------------------------------------------------------------- -PLUGIN_RESULT CEmptyServerPlugin::ClientConnect( bool *bAllowConnect, edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ) -{ - return PLUGIN_CONTINUE; -} - -CON_COMMAND( DoAskConnect, "Server plugin example of using the ask connect dialog" ) -{ - if ( args.ArgC() < 2 ) - { - Warning ( "DoAskConnect \n" ); - } - else - { - const char *pServerIP = args.Arg( 1 ); - - KeyValues *kv = new KeyValues( "menu" ); - kv->SetString( "title", pServerIP ); // The IP address of the server to connect to goes in the "title" field. - kv->SetInt( "time", 3 ); - - for ( int i=1; i < gpGlobals->maxClients; i++ ) - { - edict_t *pEdict = engine->PEntityOfEntIndex( i ); - if ( pEdict ) - { - helpers->CreateMessage( pEdict, DIALOG_ASKCONNECT, kv, &g_EmtpyServerPlugin ); - } - } - - kv->deleteThis(); - } -} - -#ifdef SAMPLE_TF2_PLUGIN -const char *classNames[] = -{ - "unknown", - "scout", - "sniper", - "soldier", - "demoman", - "medic", - "heavy weapons guy", - "pyro", - "spy", - "engineer", -}; - -bool TFPlayerHasCondition( int inBits, int condition ) -{ - Assert( condition >= 0 && condition < TF_COND_LAST ); - - return ( ( inBits & (1<GetPlayerInfo( pEntity ); - if (!playerinfo) - { - Msg("couldn't get playerinfo\n"); - return; - } - - Msg("Sentry Status:\n"); - pluginvariant value; - pluginvariant emptyVariant; - edict_t *pSentry = NULL; - if (playerinfo->GetCustomInfo(TFPLAYERINFO_ENTINDEX_SENTRY, value, emptyVariant)) - { - pSentry = engine->PEntityOfEntIndex( value.Int() ); - if (!pSentry) - { - Warning("couldn't attain sentry gun entity\n"); - return; - } - } - else - { - Msg("No Sentrygun built.\n"); - return; - - } - IEntityInfo *entinfo = entityinfomanager->GetEntityInfo( pSentry ); - if (!entinfo) - { - Warning("couldn't get entinfo for sentry gun\n"); - return; - } - - if (playerinfo->GetCustomInfo(TFPLAYERINFO_BUILDING_SENTRY, value, emptyVariant)) - { - if (value.Bool()) - Msg("Sentry Under Construction...\n"); - } - if (playerinfo->GetCustomInfo(TFPLAYERINFO_UPGRADING_SENTRY, value, emptyVariant)) - { - if (value.Bool()) - Msg("Sentry Upgrading...\n"); - } - - int sentryLevel = 0; - if (playerinfo->GetCustomInfo(TFPLAYERINFO_SENTRY_LEVEL, value, emptyVariant)) - { - sentryLevel = value.Int(); - Msg("Sentry Level: %i\n", sentryLevel ); - } - else - Msg("Unable to retrive sentry level\n"); - - if (playerinfo->GetCustomInfo(TFPLAYERINFO_SENTRY_PROGRESS, value, emptyVariant)) - { - if (sentryLevel < 3) - { - int iMetal, iRequiredMetal; - iRequiredMetal = value.Int() & 0xFF; - iMetal = (value.Int()>>8) & 0xFF; - Msg("%i / %i Metal Required for Sentry Level %i\n", iMetal, iRequiredMetal, sentryLevel+1); - } - else - Msg("Sentry cannot be upgraded further.\n"); - } - - Msg("Health: %i\n", entinfo->GetHealth() ); - - if (playerinfo->GetCustomInfo(TFPLAYERINFO_SENTRY_KILLS, value, emptyVariant)) - Msg("Kills: %i\n", value.Int() ); - else - Msg("Unable to retrieve sentry kills\n"); - - if (playerinfo->GetCustomInfo(TFPLAYERINFO_SENTRY_AMMO_SHELLS, value, emptyVariant)) - { - int iShells, iMaxShells; - iMaxShells = value.Int() & 0xFF; - iShells = (value.Int()>>8) & 0xFF; - Msg("Shells: %i / %i\n", iShells, iMaxShells); - } - if (sentryLevel > 2) - { - if (playerinfo->GetCustomInfo(TFPLAYERINFO_SENTRY_AMMO_ROCKETS, value, emptyVariant)) - { - int iRockets, iMaxRockets; - iMaxRockets = value.Int() & 0xFF; - iRockets = (value.Int()>>8) & 0xFF; - Msg("Rockets: %i / %i\n", iRockets, iMaxRockets); - } - } - -} -void DispenserStatus( edict_t *pEntity ) -{ - IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity ); - if (!playerinfo) - { - Msg("couldn't get playerinfo\n"); - return; - } - - Msg("Dispenser Status:\n"); - pluginvariant value; - pluginvariant emptyVariant; - edict_t *pDispenser = NULL; - if (playerinfo->GetCustomInfo(TFPLAYERINFO_ENTINDEX_DISPENSER, value, emptyVariant)) - { - pDispenser = engine->PEntityOfEntIndex( value.Int() ); - if (!pDispenser) - { - Warning("couldn't attain dispenser entity\n"); - return; - } - } - else - { - Msg("No dispenser built.\n"); - return; - } - IEntityInfo *entinfo = entityinfomanager->GetEntityInfo( pDispenser ); - if (!entinfo) - { - Warning("couldn't get entinfo for dispenser\n"); - return; - } - if (playerinfo->GetCustomInfo(TFPLAYERINFO_BUILDING_DISPENSER, value, emptyVariant)) - { - if (value.Bool()) - Msg("Dispenser Under Construction...\n"); - } - Msg("Health: %i\n", entinfo->GetHealth() ); - if (playerinfo->GetCustomInfo(TFPLAYERINFO_DISPENSER_METAL, value, emptyVariant)) - Msg("Metal: %i\n", value.Int() ); -} -void TeleporterStatus( edict_t *pEntity ) -{ - IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity ); - if (!playerinfo) - { - Msg("couldn't get playerinfo\n"); - return; - } - - Msg("Teleporter Status:\n"); - - pluginvariant value; - pluginvariant emptyVariant; - edict_t *pEntrance = NULL; - edict_t *pExit = NULL; - if (playerinfo->GetCustomInfo(TFPLAYERINFO_ENTINDEX_TELEPORTER_ENTRANCE, value, emptyVariant)) - { - pEntrance = engine->PEntityOfEntIndex( value.Int() ); - if (!pEntrance) - { - Warning("couldn't attain entrance entity\n"); - } - } - else - { - Msg("No Teleporter Entrance built.\n"); - } - if (playerinfo->GetCustomInfo(TFPLAYERINFO_ENTINDEX_TELEPORTER_EXIT, value, emptyVariant)) - { - pExit = engine->PEntityOfEntIndex( value.Int() ); - if (!pExit) - { - Warning("couldn't attain exit entity\n"); - } - } - else - { - Msg("No Teleporter Entrance built.\n"); - } - IEntityInfo *entranceInfo = entityinfomanager->GetEntityInfo( pEntrance ); - if (!entranceInfo) - { - Warning("couldn't get entinfo for teleporter entrance\n"); - } - IEntityInfo *exitInfo = entityinfomanager->GetEntityInfo( pExit ); - if (!exitInfo) - { - Warning("couldn't get entinfo for teleporter exit\n"); - } - - if (pEntrance && entranceInfo) - { - if (playerinfo->GetCustomInfo(TFPLAYERINFO_BUILDING_TELEPORTER_ENTRANCE, value, emptyVariant)) - { - if (value.Bool()) - Msg("Entrance Under Construction...\n"); - } - Msg("Entrance Health: %i\n", entranceInfo->GetHealth() ); - if (playerinfo->GetCustomInfo(TFPLAYERINFO_TELEPORTER_USES, value, emptyVariant)) - Msg("Entrance Used %i Times.\n", value.Int() ); - - } - if (pExit && exitInfo) - { - if (playerinfo->GetCustomInfo(TFPLAYERINFO_BUILDING_TELEPORTER_EXIT, value, emptyVariant)) - { - if (value.Bool()) - Msg("Exit Under Construction...\n"); - } - Msg("Exit Health: %i\n", exitInfo->GetHealth() ); - } -} -void ClassStatus( edict_t *pEntity ) -{ - IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity ); - if (!playerinfo) - { - Msg("couldn't get playerinfo\n"); - return; - } - int playerClassId = playerinfo->GetPlayerClassId(); - - Msg("Player Class: %s\n", playerinfo->GetPlayerClassName()); - pluginvariant conditionValue; - pluginvariant emptyVariant; - if (!playerinfo->GetCustomInfo(TFPLAYERINFO_CONDITIONS, conditionValue, emptyVariant)) - { - Warning("unable to retrieve conditions!\n"); - } - if (TFPlayerHasCondition(conditionValue.Int(), TF_COND_INVULNERABLE )) - Msg("You are Invulnerable!\n"); - if (TFPlayerHasCondition(conditionValue.Int(), TF_COND_SELECTED_TO_TELEPORT )) - Msg("You are about to Teleport.\n"); - if (TFPlayerHasCondition(conditionValue.Int(), TF_COND_TELEPORTED )) - Msg("You have recently been teleported.\n"); - - switch(playerClassId) - { - default: - case TF_CLASS_MEDIC: - break; - case TF_CLASS_ENGINEER: - Msg("Building Information:\n"); - SentryStatus( pEntity ); - DispenserStatus( pEntity ); - TeleporterStatus( pEntity ); - break; - case TF_CLASS_SPY: - { - int disguiseClass = 0; - pluginvariant value; - - if (playerinfo->GetCustomInfo(TFPLAYERINFO_SPY_DISGUISEDAS, value, emptyVariant)) - disguiseClass = value.Int(); - - if ( TFPlayerHasCondition(conditionValue.Int(), TF_COND_DISGUISING ) ) - Msg("Disguising..\n"); - else if (TFPlayerHasCondition(conditionValue.Int(), TF_COND_DISGUISED ) ) - Msg("Disguised as: %s\n", classNames[disguiseClass] ); - - if (TFPlayerHasCondition(conditionValue.Int(), TF_COND_STEALTHED )) - Msg("Cloaked!\n"); - if (playerinfo->GetCustomInfo(TFPLAYERINFO_SPY_CLOAKCHARGELEVEL, value, emptyVariant)) - Msg("Cloak Charge Percent: %d\n", value.Float() ); - - break; - } - case TF_CLASS_DEMOMAN: - break; - } -} -const char *ctf_flagtype[] = -{ - "ctf", //TF_FLAGTYPE_CTF = 0, - "attack / defend", //TF_FLAGTYPE_ATTACK_DEFEND, - "territory control", //TF_FLAGTYPE_TERRITORY_CONTROL, - "invade", //TF_FLAGTYPE_INVADE, - "king of the hill", //TF_FLAGTYPE_KINGOFTHEHILL, -}; -const char *ctf_flagstatus[] = -{ - "unknown", - "At Home", - "Dropped", - "Stolen", -}; -void FlagStatus( edict_t *pPlayer ) -{ - IPlayerInfo *pInfo = playerinfomanager->GetPlayerInfo( pPlayer ); - if (!pInfo) - { - Msg( "couldn't get playerinfo\n" ); - return; - } - IGameInfo *gameInfo = gameinfomanager->GetGameInfo(); - if (!gameInfo) - { - Msg( "couldn't get gameinfo\n" ); - } - - int gameType = gameInfo->GetInfo_GameType(); - - if (gameType != 1) - { - Msg( "Game is not CTF.\n" ); - return; - } - Msg( "===============================\n" ); - Msg( "Capture The Flag -- Flag Status\n" ); - Msg( "===============================\n" ); - pluginvariant value, options; - - edict_t *pFlag = NULL; - while ( (pFlag = entityinfomanager->FindEntityByClassname(pFlag, "item_teamflag")) != NULL ) - { - IEntityInfo *pFlagInfo = entityinfomanager->GetEntityInfo( pFlag ); - if (!pFlagInfo) - continue; - - Msg( "\nTeam %s's Flag\n", gameInfo->GetInfo_GetTeamName( pFlagInfo->GetTeamIndex() ) ); - options.SetInt(engine->IndexOfEdict(pFlag)); - if ( gameInfo->GetInfo_Custom( TFGAMEINFO_CTF_FLAG_TYPE, value, options) ) - Msg( "Type: %s\n", ctf_flagtype[value.Int()] ); - if ( gameInfo->GetInfo_Custom( TFGAMEINFO_CTF_FLAG_STATUS, value, options) ) - { - Msg( "Status: %s\n", ctf_flagstatus[value.Int()] ); - //Tony; if we're carried, find out who has us. - if (value.Int() == 3) - { - edict_t *pPlayer = pFlagInfo->GetOwner(); - if (pPlayer) - { - IPlayerInfo *pPlayerInfo = playerinfomanager->GetPlayerInfo( pPlayer ); - if (pPlayerInfo) - Msg( "Carried by: %s\n", pPlayerInfo->GetName() ); - } - } - } - } - - - Msg( "===============================\n" ); -} -#endif - -//--------------------------------------------------------------------------------- -// Purpose: called when a client types in a command (only a subset of commands however, not CON_COMMAND's) -//--------------------------------------------------------------------------------- -PLUGIN_RESULT CEmptyServerPlugin::ClientCommand( edict_t *pEntity, const CCommand &args ) -{ - const char *pcmd = args[0]; - - if ( !pEntity || pEntity->IsFree() ) - { - return PLUGIN_CONTINUE; - } - - if ( FStrEq( pcmd, "menu" ) ) - { - KeyValues *kv = new KeyValues( "menu" ); - kv->SetString( "title", "You've got options, hit ESC" ); - kv->SetInt( "level", 1 ); - kv->SetColor( "color", Color( 255, 0, 0, 255 )); - kv->SetInt( "time", 20 ); - kv->SetString( "msg", "Pick an option\nOr don't." ); - - for( int i = 1; i < 9; i++ ) - { - char num[10], msg[10], cmd[10]; - Q_snprintf( num, sizeof(num), "%i", i ); - Q_snprintf( msg, sizeof(msg), "Option %i", i ); - Q_snprintf( cmd, sizeof(cmd), "option%i", i ); - - KeyValues *item1 = kv->FindKey( num, true ); - item1->SetString( "msg", msg ); - item1->SetString( "command", cmd ); - } - - helpers->CreateMessage( pEntity, DIALOG_MENU, kv, this ); - kv->deleteThis(); - return PLUGIN_STOP; // we handled this function - } - else if ( FStrEq( pcmd, "rich" ) ) - { - KeyValues *kv = new KeyValues( "menu" ); - kv->SetString( "title", "A rich message" ); - kv->SetInt( "level", 1 ); - kv->SetInt( "time", 20 ); - kv->SetString( "msg", "This is a long long long text string.\n\nIt also has line breaks." ); - - helpers->CreateMessage( pEntity, DIALOG_TEXT, kv, this ); - kv->deleteThis(); - return PLUGIN_STOP; // we handled this function - } - else if ( FStrEq( pcmd, "msg" ) ) - { - KeyValues *kv = new KeyValues( "menu" ); - kv->SetString( "title", "Just a simple hello" ); - kv->SetInt( "level", 1 ); - kv->SetInt( "time", 20 ); - - helpers->CreateMessage( pEntity, DIALOG_MSG, kv, this ); - kv->deleteThis(); - return PLUGIN_STOP; // we handled this function - } - else if ( FStrEq( pcmd, "entry" ) ) - { - KeyValues *kv = new KeyValues( "entry" ); - kv->SetString( "title", "Stuff" ); - kv->SetString( "msg", "Enter something" ); - kv->SetString( "command", "say" ); // anything they enter into the dialog turns into a say command - kv->SetInt( "level", 1 ); - kv->SetInt( "time", 20 ); - - helpers->CreateMessage( pEntity, DIALOG_ENTRY, kv, this ); - kv->deleteThis(); - return PLUGIN_STOP; // we handled this function - } -#ifdef SAMPLE_TF2_PLUGIN - else if ( FStrEq( pcmd, "gameinfo" ) ) - { - IGameInfo *gameInfo = gameinfomanager->GetGameInfo(); - if (!gameInfo) - return PLUGIN_STOP; - - Msg("=== Game Information ===\n"); - Msg("Game Type: %i / %s\n", gameInfo->GetInfo_GameType(), gameInfo->GetInfo_GameTypeName() ); - int teamCount = gameInfo->GetInfo_GetTeamCount(); - Msg("Num Teams: %i\n", teamCount ); - - Msg("Player Counts:\n"); - for (int i = 0;iGetInfo_GetTeamName(i) ) - continue; - Msg("Team: %s, Players: %i\n", gameInfo->GetInfo_GetTeamName(i), gameInfo->GetInfo_NumPlayersOnTeam(i) ); - } - return PLUGIN_STOP; - - } - // Sample to use the new CustomInfo added to TF2 for plugins - else if ( FStrEq( pcmd, "tfcond" ) ) - { - IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity ); - if (!playerinfo) - return PLUGIN_STOP; - - pluginvariant conditionValue; - pluginvariant emptyVariant; - if (!playerinfo->GetCustomInfo(TFPLAYERINFO_CONDITIONS, conditionValue, emptyVariant)) - { - Msg("unable to retrieve conditions!\n"); - return PLUGIN_STOP; - } - - Msg("Disguising?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_DISGUISING ) ? "yes" : "no" ); - Msg("Disguised?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_DISGUISED ) ? "yes" : "no" ); - Msg("Stealthed?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_STEALTHED ) ? "yes" : "no" ); - Msg("Invulnerable?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_INVULNERABLE ) ? "yes" : "no" ); - Msg("Teleported Recently?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_TELEPORTED ) ? "yes" : "no" ); - Msg("Selected for Teleportation?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_SELECTED_TO_TELEPORT ) ? "yes" : "no" ); - Msg("On Fire?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_BURNING ) ? "yes" : "no" ); - - return PLUGIN_STOP; - } - else if ( FStrEq( pcmd, "sentry_status" ) ) - { - SentryStatus(pEntity); - return PLUGIN_STOP; - } - else if ( FStrEq( pcmd, "class_status" ) ) - { - ClassStatus(pEntity); - return PLUGIN_STOP; - } - else if ( FStrEq( pcmd, "flag_status" ) ) - { - FlagStatus(pEntity); - return PLUGIN_STOP; - } - #ifdef GAME_DLL - else if ( FStrEq( pcmd, "cbe_test" ) ) - { - IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity ); - if (!playerinfo) - return PLUGIN_STOP; - - CBaseEntity *pEnt = static_cast< CBaseEntity* >(entityinfomanager->GetEntity( pEntity )); - if (pEnt) - Msg("got a pointer to CBaseEntity..\n"); - Msg("attempting to print this entities modelname directly..\n"); - - Msg("ModelName: %s\n", STRING(pEnt->GetModelName()) ); - - return PLUGIN_STOP; - } - #endif -#endif - - - return PLUGIN_CONTINUE; -} - -//--------------------------------------------------------------------------------- -// Purpose: called when a client is authenticated -//--------------------------------------------------------------------------------- -PLUGIN_RESULT CEmptyServerPlugin::NetworkIDValidated( const char *pszUserName, const char *pszNetworkID ) -{ - return PLUGIN_CONTINUE; -} - -//--------------------------------------------------------------------------------- -// Purpose: called when a cvar value query is finished -//--------------------------------------------------------------------------------- -void CEmptyServerPlugin::OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue ) -{ - Msg( "Cvar query (cookie: %d, status: %d) - name: %s, value: %s\n", iCookie, eStatus, pCvarName, pCvarValue ); -} -void CEmptyServerPlugin::OnEdictAllocated( edict_t *edict ) -{ -} -void CEmptyServerPlugin::OnEdictFreed( const edict_t *edict ) -{ -} - -//--------------------------------------------------------------------------------- -// Purpose: called when an event is fired -//--------------------------------------------------------------------------------- -void CEmptyServerPlugin::FireGameEvent( KeyValues * event ) -{ - const char * name = event->GetName(); - Msg( "CEmptyServerPlugin::FireGameEvent: Got event \"%s\"\n", name ); -} - -//--------------------------------------------------------------------------------- -// Purpose: an example of how to implement a new command -//--------------------------------------------------------------------------------- -CON_COMMAND( empty_version, "prints the version of the empty plugin" ) -{ - Msg( "Version:2.0.0.0\n" ); -} - -CON_COMMAND( empty_log, "logs the version of the empty plugin" ) -{ - engine->LogPrint( "Version:2.0.0.0\n" ); -} - -//--------------------------------------------------------------------------------- -// Purpose: an example cvar -//--------------------------------------------------------------------------------- -static ConVar empty_cvar("plugin_empty", "0", FCVAR_NOTIFY, "Example plugin cvar"); +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// + +#include + +//#define GAME_DLL +#ifdef GAME_DLL +#include "cbase.h" +#endif + +#include +#include "interface.h" +#include "filesystem.h" +#include "engine/iserverplugin.h" +#include "eiface.h" +#include "igameevents.h" +#include "convar.h" +#include "Color.h" +#include "vstdlib/random.h" +#include "engine/IEngineTrace.h" +#include "tier2/tier2.h" +#include "game/server/pluginvariant.h" +#include "game/server/iplayerinfo.h" +#include "game/server/ientityinfo.h" +#include "game/server/igameinfo.h" + +//#define SAMPLE_TF2_PLUGIN +#ifdef SAMPLE_TF2_PLUGIN +#include "tf/tf_shareddefs.h" +#endif +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Interfaces from the engine +IVEngineServer *engine = NULL; // helper functions (messaging clients, loading content, making entities, running commands, etc) +IGameEventManager *gameeventmanager_ = NULL; // game events interface +#ifndef GAME_DLL +#define gameeventmanager gameeventmanager_ +#endif +IPlayerInfoManager *playerinfomanager = NULL; // game dll interface to interact with players +IEntityInfoManager *entityinfomanager = NULL; // game dll interface to interact with all entities (like IPlayerInfo) +IGameInfoManager *gameinfomanager = NULL; // game dll interface to get data from game rules directly +IBotManager *botmanager = NULL; // game dll interface to interact with bots +IServerPluginHelpers *helpers = NULL; // special 3rd party plugin helpers from the engine +IUniformRandomStream *randomStr = NULL; +IEngineTrace *enginetrace = NULL; + + +CGlobalVars *gpGlobals = NULL; + +// function to initialize any cvars/command in this plugin +void Bot_RunAll( void ); + +// useful helper func +#ifndef GAME_DLL +inline bool FStrEq(const char *sz1, const char *sz2) +{ + return(Q_stricmp(sz1, sz2) == 0); +} +#endif +//--------------------------------------------------------------------------------- +// Purpose: a sample 3rd party plugin class +//--------------------------------------------------------------------------------- +class CEmptyServerPlugin: public IServerPluginCallbacks, public IGameEventListener +{ +public: + CEmptyServerPlugin(); + ~CEmptyServerPlugin(); + + // IServerPluginCallbacks methods + virtual bool Load( CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory ); + virtual void Unload( void ); + virtual void Pause( void ); + virtual void UnPause( void ); + virtual const char *GetPluginDescription( void ); + virtual void LevelInit( char const *pMapName ); + virtual void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ); + virtual void GameFrame( bool simulating ); + virtual void LevelShutdown( void ); + virtual void ClientActive( edict_t *pEntity ); + virtual void ClientDisconnect( edict_t *pEntity ); + virtual void ClientPutInServer( edict_t *pEntity, char const *playername ); + virtual void SetCommandClient( int index ); + virtual void ClientSettingsChanged( edict_t *pEdict ); + virtual PLUGIN_RESULT ClientConnect( bool *bAllowConnect, edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ); + virtual PLUGIN_RESULT ClientCommand( edict_t *pEntity, const CCommand &args ); + virtual PLUGIN_RESULT NetworkIDValidated( const char *pszUserName, const char *pszNetworkID ); + virtual void OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue ); + virtual void OnEdictAllocated( edict_t *edict ); + virtual void OnEdictFreed( const edict_t *edict ); + + // IGameEventListener Interface + virtual void FireGameEvent( KeyValues * event ); + + virtual int GetCommandIndex() { return m_iClientCommandIndex; } +private: + int m_iClientCommandIndex; +}; + + +// +// The plugin is a static singleton that is exported as an interface +// +CEmptyServerPlugin g_EmtpyServerPlugin; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CEmptyServerPlugin, IServerPluginCallbacks, INTERFACEVERSION_ISERVERPLUGINCALLBACKS, g_EmtpyServerPlugin ); + +//--------------------------------------------------------------------------------- +// Purpose: constructor/destructor +//--------------------------------------------------------------------------------- +CEmptyServerPlugin::CEmptyServerPlugin() +{ + m_iClientCommandIndex = 0; +} + +CEmptyServerPlugin::~CEmptyServerPlugin() +{ +} + +//--------------------------------------------------------------------------------- +// Purpose: called when the plugin is loaded, load the interface we need from the engine +//--------------------------------------------------------------------------------- +bool CEmptyServerPlugin::Load( CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory ) +{ + ConnectTier1Libraries( &interfaceFactory, 1 ); + ConnectTier2Libraries( &interfaceFactory, 1 ); + + entityinfomanager = (IEntityInfoManager *)gameServerFactory(INTERFACEVERSION_ENTITYINFOMANAGER,NULL); + if ( !entityinfomanager ) + { + Warning( "Unable to load entityinfomanager, ignoring\n" ); // this isn't fatal, we just won't be able to access entity data + } + + playerinfomanager = (IPlayerInfoManager *)gameServerFactory(INTERFACEVERSION_PLAYERINFOMANAGER,NULL); + if ( !playerinfomanager ) + { + Warning( "Unable to load playerinfomanager, ignoring\n" ); // this isn't fatal, we just won't be able to access specific player data + } + + botmanager = (IBotManager *)gameServerFactory(INTERFACEVERSION_PLAYERBOTMANAGER, NULL); + if ( !botmanager ) + { + Warning( "Unable to load botcontroller, ignoring\n" ); // this isn't fatal, we just won't be able to access specific bot functions + } + gameinfomanager = (IGameInfoManager *)gameServerFactory(INTERFACEVERSION_GAMEINFOMANAGER, NULL); + if (!gameinfomanager) + { + Warning( "Unable to load gameinfomanager, ignoring\n" ); + } + + engine = (IVEngineServer*)interfaceFactory(INTERFACEVERSION_VENGINESERVER, NULL); + gameeventmanager = (IGameEventManager *)interfaceFactory(INTERFACEVERSION_GAMEEVENTSMANAGER,NULL); + helpers = (IServerPluginHelpers*)interfaceFactory(INTERFACEVERSION_ISERVERPLUGINHELPERS, NULL); + enginetrace = (IEngineTrace *)interfaceFactory(INTERFACEVERSION_ENGINETRACE_SERVER,NULL); + randomStr = (IUniformRandomStream *)interfaceFactory(VENGINE_SERVER_RANDOM_INTERFACE_VERSION, NULL); + + // get the interfaces we want to use + if( ! ( engine && gameeventmanager && g_pFullFileSystem && helpers && enginetrace && randomStr ) ) + { + return false; // we require all these interface to function + } + + if ( playerinfomanager ) + { + gpGlobals = playerinfomanager->GetGlobalVars(); + } + + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); + ConVar_Register( 0 ); + return true; +} + +//--------------------------------------------------------------------------------- +// Purpose: called when the plugin is unloaded (turned off) +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::Unload( void ) +{ + gameeventmanager->RemoveListener( this ); // make sure we are unloaded from the event system + + ConVar_Unregister( ); + DisconnectTier2Libraries( ); + DisconnectTier1Libraries( ); +} + +//--------------------------------------------------------------------------------- +// Purpose: called when the plugin is paused (i.e should stop running but isn't unloaded) +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::Pause( void ) +{ +} + +//--------------------------------------------------------------------------------- +// Purpose: called when the plugin is unpaused (i.e should start executing again) +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::UnPause( void ) +{ +} + +//--------------------------------------------------------------------------------- +// Purpose: the name of this plugin, returned in "plugin_print" command +//--------------------------------------------------------------------------------- +const char *CEmptyServerPlugin::GetPluginDescription( void ) +{ + return "Emtpy-Plugin V2, Valve"; +} + +//--------------------------------------------------------------------------------- +// Purpose: called on level start +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::LevelInit( char const *pMapName ) +{ + Msg( "Level \"%s\" has been loaded\n", pMapName ); + gameeventmanager->AddListener( this, true ); +} + +//--------------------------------------------------------------------------------- +// Purpose: called on level start, when the server is ready to accept client connections +// edictCount is the number of entities in the level, clientMax is the max client count +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) +{ +} + +//--------------------------------------------------------------------------------- +// Purpose: called once per server frame, do recurring work here (like checking for timeouts) +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::GameFrame( bool simulating ) +{ + if ( simulating ) + { + Bot_RunAll(); + } +} + +//--------------------------------------------------------------------------------- +// Purpose: called on level end (as the server is shutting down or going to a new map) +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::LevelShutdown( void ) // !!!!this can get called multiple times per map change +{ + gameeventmanager->RemoveListener( this ); +} + +//--------------------------------------------------------------------------------- +// Purpose: called when a client spawns into a server (i.e as they begin to play) +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::ClientActive( edict_t *pEntity ) +{ +} + +//--------------------------------------------------------------------------------- +// Purpose: called when a client leaves a server (or is timed out) +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::ClientDisconnect( edict_t *pEntity ) +{ +} + +//--------------------------------------------------------------------------------- +// Purpose: called on +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::ClientPutInServer( edict_t *pEntity, char const *playername ) +{ + KeyValues *kv = new KeyValues( "msg" ); + kv->SetString( "title", "Hello" ); + kv->SetString( "msg", "Hello there" ); + kv->SetColor( "color", Color( 255, 0, 0, 255 )); + kv->SetInt( "level", 5); + kv->SetInt( "time", 10); + helpers->CreateMessage( pEntity, DIALOG_MSG, kv, this ); + kv->deleteThis(); +} + +//--------------------------------------------------------------------------------- +// Purpose: called on level start +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::SetCommandClient( int index ) +{ + m_iClientCommandIndex = index; +} + +void ClientPrint( edict_t *pEdict, char *format, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + Q_vsnprintf(string, sizeof(string), format,argptr); + va_end (argptr); + + engine->ClientPrintf( pEdict, string ); +} +//--------------------------------------------------------------------------------- +// Purpose: called on level start +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::ClientSettingsChanged( edict_t *pEdict ) +{ + if ( playerinfomanager ) + { + IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEdict ); + + const char * name = engine->GetClientConVarValue( engine->IndexOfEdict(pEdict), "name" ); + + // CAN'T use Q_stricmp here, this dll is made by 3rd parties and may not link to tier0/vstdlib + if ( playerinfo && name && playerinfo->GetName() && + stricmp( name, playerinfo->GetName()) ) // playerinfo may be NULL if the MOD doesn't support access to player data + // OR if you are accessing the player before they are fully connected + { + ClientPrint( pEdict, "Your name changed to \"%s\" (from \"%s\"\n", name, playerinfo->GetName() ); + // this is the bad way to check this, the better option it to listen for the "player_changename" event in FireGameEvent() + // this is here to give a real example of how to use the playerinfo interface + } + } +} + +//--------------------------------------------------------------------------------- +// Purpose: called when a client joins a server +//--------------------------------------------------------------------------------- +PLUGIN_RESULT CEmptyServerPlugin::ClientConnect( bool *bAllowConnect, edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ) +{ + return PLUGIN_CONTINUE; +} + +CON_COMMAND( DoAskConnect, "Server plugin example of using the ask connect dialog" ) +{ + if ( args.ArgC() < 2 ) + { + Warning ( "DoAskConnect \n" ); + } + else + { + const char *pServerIP = args.Arg( 1 ); + + KeyValues *kv = new KeyValues( "menu" ); + kv->SetString( "title", pServerIP ); // The IP address of the server to connect to goes in the "title" field. + kv->SetInt( "time", 3 ); + + for ( int i=1; i < gpGlobals->maxClients; i++ ) + { + edict_t *pEdict = engine->PEntityOfEntIndex( i ); + if ( pEdict ) + { + helpers->CreateMessage( pEdict, DIALOG_ASKCONNECT, kv, &g_EmtpyServerPlugin ); + } + } + + kv->deleteThis(); + } +} + +#ifdef SAMPLE_TF2_PLUGIN +const char *classNames[] = +{ + "unknown", + "scout", + "sniper", + "soldier", + "demoman", + "medic", + "heavy weapons guy", + "pyro", + "spy", + "engineer", +}; + +bool TFPlayerHasCondition( int inBits, int condition ) +{ + Assert( condition >= 0 && condition < TF_COND_LAST ); + + return ( ( inBits & (1<GetPlayerInfo( pEntity ); + if (!playerinfo) + { + Msg("couldn't get playerinfo\n"); + return; + } + + Msg("Sentry Status:\n"); + pluginvariant value; + pluginvariant emptyVariant; + edict_t *pSentry = NULL; + if (playerinfo->GetCustomInfo(TFPLAYERINFO_ENTINDEX_SENTRY, value, emptyVariant)) + { + pSentry = engine->PEntityOfEntIndex( value.Int() ); + if (!pSentry) + { + Warning("couldn't attain sentry gun entity\n"); + return; + } + } + else + { + Msg("No Sentrygun built.\n"); + return; + + } + IEntityInfo *entinfo = entityinfomanager->GetEntityInfo( pSentry ); + if (!entinfo) + { + Warning("couldn't get entinfo for sentry gun\n"); + return; + } + + if (playerinfo->GetCustomInfo(TFPLAYERINFO_BUILDING_SENTRY, value, emptyVariant)) + { + if (value.Bool()) + Msg("Sentry Under Construction...\n"); + } + if (playerinfo->GetCustomInfo(TFPLAYERINFO_UPGRADING_SENTRY, value, emptyVariant)) + { + if (value.Bool()) + Msg("Sentry Upgrading...\n"); + } + + int sentryLevel = 0; + if (playerinfo->GetCustomInfo(TFPLAYERINFO_SENTRY_LEVEL, value, emptyVariant)) + { + sentryLevel = value.Int(); + Msg("Sentry Level: %i\n", sentryLevel ); + } + else + Msg("Unable to retrive sentry level\n"); + + if (playerinfo->GetCustomInfo(TFPLAYERINFO_SENTRY_PROGRESS, value, emptyVariant)) + { + if (sentryLevel < 3) + { + int iMetal, iRequiredMetal; + iRequiredMetal = value.Int() & 0xFF; + iMetal = (value.Int()>>8) & 0xFF; + Msg("%i / %i Metal Required for Sentry Level %i\n", iMetal, iRequiredMetal, sentryLevel+1); + } + else + Msg("Sentry cannot be upgraded further.\n"); + } + + Msg("Health: %i\n", entinfo->GetHealth() ); + + if (playerinfo->GetCustomInfo(TFPLAYERINFO_SENTRY_KILLS, value, emptyVariant)) + Msg("Kills: %i\n", value.Int() ); + else + Msg("Unable to retrieve sentry kills\n"); + + if (playerinfo->GetCustomInfo(TFPLAYERINFO_SENTRY_AMMO_SHELLS, value, emptyVariant)) + { + int iShells, iMaxShells; + iMaxShells = value.Int() & 0xFF; + iShells = (value.Int()>>8) & 0xFF; + Msg("Shells: %i / %i\n", iShells, iMaxShells); + } + if (sentryLevel > 2) + { + if (playerinfo->GetCustomInfo(TFPLAYERINFO_SENTRY_AMMO_ROCKETS, value, emptyVariant)) + { + int iRockets, iMaxRockets; + iMaxRockets = value.Int() & 0xFF; + iRockets = (value.Int()>>8) & 0xFF; + Msg("Rockets: %i / %i\n", iRockets, iMaxRockets); + } + } + +} +void DispenserStatus( edict_t *pEntity ) +{ + IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity ); + if (!playerinfo) + { + Msg("couldn't get playerinfo\n"); + return; + } + + Msg("Dispenser Status:\n"); + pluginvariant value; + pluginvariant emptyVariant; + edict_t *pDispenser = NULL; + if (playerinfo->GetCustomInfo(TFPLAYERINFO_ENTINDEX_DISPENSER, value, emptyVariant)) + { + pDispenser = engine->PEntityOfEntIndex( value.Int() ); + if (!pDispenser) + { + Warning("couldn't attain dispenser entity\n"); + return; + } + } + else + { + Msg("No dispenser built.\n"); + return; + } + IEntityInfo *entinfo = entityinfomanager->GetEntityInfo( pDispenser ); + if (!entinfo) + { + Warning("couldn't get entinfo for dispenser\n"); + return; + } + if (playerinfo->GetCustomInfo(TFPLAYERINFO_BUILDING_DISPENSER, value, emptyVariant)) + { + if (value.Bool()) + Msg("Dispenser Under Construction...\n"); + } + Msg("Health: %i\n", entinfo->GetHealth() ); + if (playerinfo->GetCustomInfo(TFPLAYERINFO_DISPENSER_METAL, value, emptyVariant)) + Msg("Metal: %i\n", value.Int() ); +} +void TeleporterStatus( edict_t *pEntity ) +{ + IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity ); + if (!playerinfo) + { + Msg("couldn't get playerinfo\n"); + return; + } + + Msg("Teleporter Status:\n"); + + pluginvariant value; + pluginvariant emptyVariant; + edict_t *pEntrance = NULL; + edict_t *pExit = NULL; + if (playerinfo->GetCustomInfo(TFPLAYERINFO_ENTINDEX_TELEPORTER_ENTRANCE, value, emptyVariant)) + { + pEntrance = engine->PEntityOfEntIndex( value.Int() ); + if (!pEntrance) + { + Warning("couldn't attain entrance entity\n"); + } + } + else + { + Msg("No Teleporter Entrance built.\n"); + } + if (playerinfo->GetCustomInfo(TFPLAYERINFO_ENTINDEX_TELEPORTER_EXIT, value, emptyVariant)) + { + pExit = engine->PEntityOfEntIndex( value.Int() ); + if (!pExit) + { + Warning("couldn't attain exit entity\n"); + } + } + else + { + Msg("No Teleporter Entrance built.\n"); + } + IEntityInfo *entranceInfo = entityinfomanager->GetEntityInfo( pEntrance ); + if (!entranceInfo) + { + Warning("couldn't get entinfo for teleporter entrance\n"); + } + IEntityInfo *exitInfo = entityinfomanager->GetEntityInfo( pExit ); + if (!exitInfo) + { + Warning("couldn't get entinfo for teleporter exit\n"); + } + + if (pEntrance && entranceInfo) + { + if (playerinfo->GetCustomInfo(TFPLAYERINFO_BUILDING_TELEPORTER_ENTRANCE, value, emptyVariant)) + { + if (value.Bool()) + Msg("Entrance Under Construction...\n"); + } + Msg("Entrance Health: %i\n", entranceInfo->GetHealth() ); + if (playerinfo->GetCustomInfo(TFPLAYERINFO_TELEPORTER_USES, value, emptyVariant)) + Msg("Entrance Used %i Times.\n", value.Int() ); + + } + if (pExit && exitInfo) + { + if (playerinfo->GetCustomInfo(TFPLAYERINFO_BUILDING_TELEPORTER_EXIT, value, emptyVariant)) + { + if (value.Bool()) + Msg("Exit Under Construction...\n"); + } + Msg("Exit Health: %i\n", exitInfo->GetHealth() ); + } +} +void ClassStatus( edict_t *pEntity ) +{ + IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity ); + if (!playerinfo) + { + Msg("couldn't get playerinfo\n"); + return; + } + int playerClassId = playerinfo->GetPlayerClassId(); + + Msg("Player Class: %s\n", playerinfo->GetPlayerClassName()); + pluginvariant conditionValue; + pluginvariant emptyVariant; + if (!playerinfo->GetCustomInfo(TFPLAYERINFO_CONDITIONS, conditionValue, emptyVariant)) + { + Warning("unable to retrieve conditions!\n"); + } + if (TFPlayerHasCondition(conditionValue.Int(), TF_COND_INVULNERABLE )) + Msg("You are Invulnerable!\n"); + if (TFPlayerHasCondition(conditionValue.Int(), TF_COND_SELECTED_TO_TELEPORT )) + Msg("You are about to Teleport.\n"); + if (TFPlayerHasCondition(conditionValue.Int(), TF_COND_TELEPORTED )) + Msg("You have recently been teleported.\n"); + + switch(playerClassId) + { + default: + case TF_CLASS_MEDIC: + break; + case TF_CLASS_ENGINEER: + Msg("Building Information:\n"); + SentryStatus( pEntity ); + DispenserStatus( pEntity ); + TeleporterStatus( pEntity ); + break; + case TF_CLASS_SPY: + { + int disguiseClass = 0; + pluginvariant value; + + if (playerinfo->GetCustomInfo(TFPLAYERINFO_SPY_DISGUISEDAS, value, emptyVariant)) + disguiseClass = value.Int(); + + if ( TFPlayerHasCondition(conditionValue.Int(), TF_COND_DISGUISING ) ) + Msg("Disguising..\n"); + else if (TFPlayerHasCondition(conditionValue.Int(), TF_COND_DISGUISED ) ) + Msg("Disguised as: %s\n", classNames[disguiseClass] ); + + if (TFPlayerHasCondition(conditionValue.Int(), TF_COND_STEALTHED )) + Msg("Cloaked!\n"); + if (playerinfo->GetCustomInfo(TFPLAYERINFO_SPY_CLOAKCHARGELEVEL, value, emptyVariant)) + Msg("Cloak Charge Percent: %d\n", value.Float() ); + + break; + } + case TF_CLASS_DEMOMAN: + break; + } +} +const char *ctf_flagtype[] = +{ + "ctf", //TF_FLAGTYPE_CTF = 0, + "attack / defend", //TF_FLAGTYPE_ATTACK_DEFEND, + "territory control", //TF_FLAGTYPE_TERRITORY_CONTROL, + "invade", //TF_FLAGTYPE_INVADE, + "king of the hill", //TF_FLAGTYPE_KINGOFTHEHILL, +}; +const char *ctf_flagstatus[] = +{ + "unknown", + "At Home", + "Dropped", + "Stolen", +}; +void FlagStatus( edict_t *pPlayer ) +{ + IPlayerInfo *pInfo = playerinfomanager->GetPlayerInfo( pPlayer ); + if (!pInfo) + { + Msg( "couldn't get playerinfo\n" ); + return; + } + IGameInfo *gameInfo = gameinfomanager->GetGameInfo(); + if (!gameInfo) + { + Msg( "couldn't get gameinfo\n" ); + } + + int gameType = gameInfo->GetInfo_GameType(); + + if (gameType != 1) + { + Msg( "Game is not CTF.\n" ); + return; + } + Msg( "===============================\n" ); + Msg( "Capture The Flag -- Flag Status\n" ); + Msg( "===============================\n" ); + pluginvariant value, options; + + edict_t *pFlag = NULL; + while ( (pFlag = entityinfomanager->FindEntityByClassname(pFlag, "item_teamflag")) != NULL ) + { + IEntityInfo *pFlagInfo = entityinfomanager->GetEntityInfo( pFlag ); + if (!pFlagInfo) + continue; + + Msg( "\nTeam %s's Flag\n", gameInfo->GetInfo_GetTeamName( pFlagInfo->GetTeamIndex() ) ); + options.SetInt(engine->IndexOfEdict(pFlag)); + if ( gameInfo->GetInfo_Custom( TFGAMEINFO_CTF_FLAG_TYPE, value, options) ) + Msg( "Type: %s\n", ctf_flagtype[value.Int()] ); + if ( gameInfo->GetInfo_Custom( TFGAMEINFO_CTF_FLAG_STATUS, value, options) ) + { + Msg( "Status: %s\n", ctf_flagstatus[value.Int()] ); + //Tony; if we're carried, find out who has us. + if (value.Int() == 3) + { + edict_t *pPlayer = pFlagInfo->GetOwner(); + if (pPlayer) + { + IPlayerInfo *pPlayerInfo = playerinfomanager->GetPlayerInfo( pPlayer ); + if (pPlayerInfo) + Msg( "Carried by: %s\n", pPlayerInfo->GetName() ); + } + } + } + } + + + Msg( "===============================\n" ); +} +#endif + +//--------------------------------------------------------------------------------- +// Purpose: called when a client types in a command (only a subset of commands however, not CON_COMMAND's) +//--------------------------------------------------------------------------------- +PLUGIN_RESULT CEmptyServerPlugin::ClientCommand( edict_t *pEntity, const CCommand &args ) +{ + const char *pcmd = args[0]; + + if ( !pEntity || pEntity->IsFree() ) + { + return PLUGIN_CONTINUE; + } + + if ( FStrEq( pcmd, "menu" ) ) + { + KeyValues *kv = new KeyValues( "menu" ); + kv->SetString( "title", "You've got options, hit ESC" ); + kv->SetInt( "level", 1 ); + kv->SetColor( "color", Color( 255, 0, 0, 255 )); + kv->SetInt( "time", 20 ); + kv->SetString( "msg", "Pick an option\nOr don't." ); + + for( int i = 1; i < 9; i++ ) + { + char num[10], msg[10], cmd[10]; + Q_snprintf( num, sizeof(num), "%i", i ); + Q_snprintf( msg, sizeof(msg), "Option %i", i ); + Q_snprintf( cmd, sizeof(cmd), "option%i", i ); + + KeyValues *item1 = kv->FindKey( num, true ); + item1->SetString( "msg", msg ); + item1->SetString( "command", cmd ); + } + + helpers->CreateMessage( pEntity, DIALOG_MENU, kv, this ); + kv->deleteThis(); + return PLUGIN_STOP; // we handled this function + } + else if ( FStrEq( pcmd, "rich" ) ) + { + KeyValues *kv = new KeyValues( "menu" ); + kv->SetString( "title", "A rich message" ); + kv->SetInt( "level", 1 ); + kv->SetInt( "time", 20 ); + kv->SetString( "msg", "This is a long long long text string.\n\nIt also has line breaks." ); + + helpers->CreateMessage( pEntity, DIALOG_TEXT, kv, this ); + kv->deleteThis(); + return PLUGIN_STOP; // we handled this function + } + else if ( FStrEq( pcmd, "msg" ) ) + { + KeyValues *kv = new KeyValues( "menu" ); + kv->SetString( "title", "Just a simple hello" ); + kv->SetInt( "level", 1 ); + kv->SetInt( "time", 20 ); + + helpers->CreateMessage( pEntity, DIALOG_MSG, kv, this ); + kv->deleteThis(); + return PLUGIN_STOP; // we handled this function + } + else if ( FStrEq( pcmd, "entry" ) ) + { + KeyValues *kv = new KeyValues( "entry" ); + kv->SetString( "title", "Stuff" ); + kv->SetString( "msg", "Enter something" ); + kv->SetString( "command", "say" ); // anything they enter into the dialog turns into a say command + kv->SetInt( "level", 1 ); + kv->SetInt( "time", 20 ); + + helpers->CreateMessage( pEntity, DIALOG_ENTRY, kv, this ); + kv->deleteThis(); + return PLUGIN_STOP; // we handled this function + } +#ifdef SAMPLE_TF2_PLUGIN + else if ( FStrEq( pcmd, "gameinfo" ) ) + { + IGameInfo *gameInfo = gameinfomanager->GetGameInfo(); + if (!gameInfo) + return PLUGIN_STOP; + + Msg("=== Game Information ===\n"); + Msg("Game Type: %i / %s\n", gameInfo->GetInfo_GameType(), gameInfo->GetInfo_GameTypeName() ); + int teamCount = gameInfo->GetInfo_GetTeamCount(); + Msg("Num Teams: %i\n", teamCount ); + + Msg("Player Counts:\n"); + for (int i = 0;iGetInfo_GetTeamName(i) ) + continue; + Msg("Team: %s, Players: %i\n", gameInfo->GetInfo_GetTeamName(i), gameInfo->GetInfo_NumPlayersOnTeam(i) ); + } + return PLUGIN_STOP; + + } + // Sample to use the new CustomInfo added to TF2 for plugins + else if ( FStrEq( pcmd, "tfcond" ) ) + { + IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity ); + if (!playerinfo) + return PLUGIN_STOP; + + pluginvariant conditionValue; + pluginvariant emptyVariant; + if (!playerinfo->GetCustomInfo(TFPLAYERINFO_CONDITIONS, conditionValue, emptyVariant)) + { + Msg("unable to retrieve conditions!\n"); + return PLUGIN_STOP; + } + + Msg("Disguising?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_DISGUISING ) ? "yes" : "no" ); + Msg("Disguised?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_DISGUISED ) ? "yes" : "no" ); + Msg("Stealthed?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_STEALTHED ) ? "yes" : "no" ); + Msg("Invulnerable?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_INVULNERABLE ) ? "yes" : "no" ); + Msg("Teleported Recently?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_TELEPORTED ) ? "yes" : "no" ); + Msg("Selected for Teleportation?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_SELECTED_TO_TELEPORT ) ? "yes" : "no" ); + Msg("On Fire?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_BURNING ) ? "yes" : "no" ); + + return PLUGIN_STOP; + } + else if ( FStrEq( pcmd, "sentry_status" ) ) + { + SentryStatus(pEntity); + return PLUGIN_STOP; + } + else if ( FStrEq( pcmd, "class_status" ) ) + { + ClassStatus(pEntity); + return PLUGIN_STOP; + } + else if ( FStrEq( pcmd, "flag_status" ) ) + { + FlagStatus(pEntity); + return PLUGIN_STOP; + } + #ifdef GAME_DLL + else if ( FStrEq( pcmd, "cbe_test" ) ) + { + IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity ); + if (!playerinfo) + return PLUGIN_STOP; + + CBaseEntity *pEnt = static_cast< CBaseEntity* >(entityinfomanager->GetEntity( pEntity )); + if (pEnt) + Msg("got a pointer to CBaseEntity..\n"); + Msg("attempting to print this entities modelname directly..\n"); + + Msg("ModelName: %s\n", STRING(pEnt->GetModelName()) ); + + return PLUGIN_STOP; + } + #endif +#endif + + + return PLUGIN_CONTINUE; +} + +//--------------------------------------------------------------------------------- +// Purpose: called when a client is authenticated +//--------------------------------------------------------------------------------- +PLUGIN_RESULT CEmptyServerPlugin::NetworkIDValidated( const char *pszUserName, const char *pszNetworkID ) +{ + return PLUGIN_CONTINUE; +} + +//--------------------------------------------------------------------------------- +// Purpose: called when a cvar value query is finished +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue ) +{ + Msg( "Cvar query (cookie: %d, status: %d) - name: %s, value: %s\n", iCookie, eStatus, pCvarName, pCvarValue ); +} +void CEmptyServerPlugin::OnEdictAllocated( edict_t *edict ) +{ +} +void CEmptyServerPlugin::OnEdictFreed( const edict_t *edict ) +{ +} + +//--------------------------------------------------------------------------------- +// Purpose: called when an event is fired +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::FireGameEvent( KeyValues * event ) +{ + const char * name = event->GetName(); + Msg( "CEmptyServerPlugin::FireGameEvent: Got event \"%s\"\n", name ); +} + +//--------------------------------------------------------------------------------- +// Purpose: an example of how to implement a new command +//--------------------------------------------------------------------------------- +CON_COMMAND( empty_version, "prints the version of the empty plugin" ) +{ + Msg( "Version:2.0.0.0\n" ); +} + +CON_COMMAND( empty_log, "logs the version of the empty plugin" ) +{ + engine->LogPrint( "Version:2.0.0.0\n" ); +} + +//--------------------------------------------------------------------------------- +// Purpose: an example cvar +//--------------------------------------------------------------------------------- +static ConVar empty_cvar("plugin_empty", "0", FCVAR_NOTIFY, "Example plugin cvar"); diff --git a/mp/src/utils/serverplugin_sample/serverplugin_empty.vpc b/mp/src/utils/serverplugin_sample/serverplugin_empty.vpc index a4bc5380..0d584af5 100644 --- a/mp/src/utils/serverplugin_sample/serverplugin_empty.vpc +++ b/mp/src/utils/serverplugin_sample/serverplugin_empty.vpc @@ -1,62 +1,62 @@ -//----------------------------------------------------------------------------- -// SERVERPLUGIN_EMPTY.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" - -$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" - -$Configuration -{ - $Compiler - { - $AdditionalIncludeDirectories "$BASE,$SRCDIR\game\server,$SRCDIR\game\shared" - $PreprocessorDefinitions "$BASE;serverplugin_emptyONLY;_MBCS" - } - - $Linker - { - $AdditionalDependencies "$BASE odbc32.lib odbccp32.lib" - } -} - -$Project "Serverplugin_empty" -{ - $Folder "Source Files" - { - $File "serverplugin_bot.cpp" - $File "serverplugin_empty.cpp" - } - - $Folder "Header Files" - { - $File "$SRCDIR\public\tier0\basetypes.h" - $File "$SRCDIR\public\Color.h" - $File "$SRCDIR\public\tier0\dbg.h" - $File "$SRCDIR\public\eiface.h" - $File "$SRCDIR\public\filesystem.h" - $File "$SRCDIR\public\tier0\icommandline.h" - $File "$SRCDIR\public\igameevents.h" - $File "$SRCDIR\public\tier1\interface.h" - $File "$SRCDIR\public\game\server\iplayerinfo.h" - $File "$SRCDIR\public\engine\iserverplugin.h" - $File "$SRCDIR\public\tier1\KeyValues.h" - $File "$SRCDIR\public\tier0\mem.h" - $File "$SRCDIR\public\tier0\memalloc.h" - $File "$SRCDIR\public\tier0\memdbgon.h" - $File "$SRCDIR\public\tier1\strtools.h" - $File "$SRCDIR\public\tier1\utlbuffer.h" - $File "$SRCDIR\public\tier1\utlmemory.h" - $File "$SRCDIR\public\tier1\utlvector.h" - $File "$SRCDIR\public\vstdlib\vstdlib.h" - } - - $Folder "Link Libraries" - { - $Lib mathlib - $Lib tier2 - } -} +//----------------------------------------------------------------------------- +// SERVERPLUGIN_EMPTY.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,$SRCDIR\game\server,$SRCDIR\game\shared" + $PreprocessorDefinitions "$BASE;serverplugin_emptyONLY;_MBCS" + } + + $Linker + { + $AdditionalDependencies "$BASE odbc32.lib odbccp32.lib" + } +} + +$Project "Serverplugin_empty" +{ + $Folder "Source Files" + { + $File "serverplugin_bot.cpp" + $File "serverplugin_empty.cpp" + } + + $Folder "Header Files" + { + $File "$SRCDIR\public\tier0\basetypes.h" + $File "$SRCDIR\public\Color.h" + $File "$SRCDIR\public\tier0\dbg.h" + $File "$SRCDIR\public\eiface.h" + $File "$SRCDIR\public\filesystem.h" + $File "$SRCDIR\public\tier0\icommandline.h" + $File "$SRCDIR\public\igameevents.h" + $File "$SRCDIR\public\tier1\interface.h" + $File "$SRCDIR\public\game\server\iplayerinfo.h" + $File "$SRCDIR\public\engine\iserverplugin.h" + $File "$SRCDIR\public\tier1\KeyValues.h" + $File "$SRCDIR\public\tier0\mem.h" + $File "$SRCDIR\public\tier0\memalloc.h" + $File "$SRCDIR\public\tier0\memdbgon.h" + $File "$SRCDIR\public\tier1\strtools.h" + $File "$SRCDIR\public\tier1\utlbuffer.h" + $File "$SRCDIR\public\tier1\utlmemory.h" + $File "$SRCDIR\public\tier1\utlvector.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + } + + $Folder "Link Libraries" + { + $Lib mathlib + $Lib tier2 + } +} diff --git a/mp/src/utils/smdlexp/smdlexp.cpp b/mp/src/utils/smdlexp/smdlexp.cpp index fafb5da6..4955dd98 100644 --- a/mp/src/utils/smdlexp/smdlexp.cpp +++ b/mp/src/utils/smdlexp/smdlexp.cpp @@ -1,1096 +1,1096 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "MAX.H" -#include "DECOMP.H" -#include "STDMAT.H" -#include "ANIMTBL.H" -#include "istdplug.h" -#include "phyexp.h" -#include "BonesPro.h" - -#include "smexprc.h" -#include "smedefs.h" - -//=================================================================== -// Prototype declarations -// -int GetIndexOfINode(INode *pnode,BOOL fAssertPropExists = TRUE); -void SetIndexOfINode(INode *pnode, int inode); -BOOL FUndesirableNode(INode *pnode); -BOOL FNodeMarkedToSkip(INode *pnode); -float FlReduceRotation(float fl); - - -//=================================================================== -// Global variable definitions -// - -// Save for use with dialogs -static HINSTANCE hInstance; - -// We just need one of these to hand off to 3DSMAX. -static SmdExportClassDesc SmdExportCD; - -// For OutputDebugString and misc sprintf's -static char st_szDBG[300]; - -// INode mapping table -static int g_inmMac = 0; - -//=================================================================== -// Utility functions -// - -static int AssertFailedFunc(char *sz) -{ - MessageBox(GetActiveWindow(), sz, "Assert failure", MB_OK); - int Set_Your_Breakpoint_Here = 1; - return 1; -} -#define ASSERT_MBOX(f, sz) ((f) ? 1 : AssertFailedFunc(sz)) - - -//=================================================================== -// Required plug-in export functions -// -BOOL WINAPI DllMain( HINSTANCE hinstDLL, ULONG fdwReason, LPVOID lpvReserved) -{ - static int fFirstTimeHere = TRUE; - if (fFirstTimeHere) - { - fFirstTimeHere = FALSE; - hInstance = hinstDLL; - } - return TRUE; -} - - -EXPORT_THIS int LibNumberClasses(void) -{ - return 1; -} - - -EXPORT_THIS ClassDesc *LibClassDesc(int iWhichClass) -{ - switch(iWhichClass) - { - case 0: return &SmdExportCD; - default: return 0; - } -} - - -EXPORT_THIS const TCHAR *LibDescription() -{ - return _T("Valve SMD Plug-in."); -} - - -EXPORT_THIS ULONG LibVersion() -{ - return VERSION_3DSMAX; -} - - -//===================================================================== -// Methods for SmdExportClass -// - -CONSTRUCTOR SmdExportClass::SmdExportClass(void) -{ - m_rgmaxnode = NULL; -} - - -DESTRUCTOR SmdExportClass::~SmdExportClass(void) -{ - if (m_rgmaxnode) - delete[] m_rgmaxnode; -} - - -int SmdExportClass::DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts, DWORD options) -{ - ExpInterface *pexpiface = ei; // Hungarian - Interface *piface = i; // Hungarian - - // Reset the name-map property manager - g_inmMac = 0; - - if (!suppressPrompts) - { - // Present the user with the Export Options dialog - if (DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_EXPORTOPTIONS), GetActiveWindow(), - ExportOptionsDlgProc, (LPARAM)this) <= 0) - return 0; // error or cancel - } - else - { - m_fReferenceFrame = 0; - } - - // Break up filename, re-assemble longer versions - TSTR strPath, strFile, strExt; - TCHAR szFile[MAX_PATH]; - SplitFilename(TSTR(name), &strPath, &strFile, &strExt); - sprintf(szFile, "%s\\%s.%s", (char*)strPath, (char*)strFile, DEFAULT_EXT); - - /* - if (m_fReferenceFrame) - sprintf(szFile, "%s\\%s_model.%s", (char*)strPath, (char*)strFile, DEFAULT_EXT); - */ - - FILE *pFile; - if ((pFile = fopen(szFile, "w")) == NULL) - return FALSE/*failure*/; - - fprintf( pFile, "version %d\n", 1 ); - - // Get animation metrics - m_intervalOfAnimation = piface->GetAnimRange(); - m_tvStart = m_intervalOfAnimation.Start(); - m_tvEnd = m_intervalOfAnimation.End(); - m_tpf = ::GetTicksPerFrame(); - - // Count nodes, label them, collect into array - if (!CollectNodes(pexpiface)) - return 0; /*fail*/ - - // Output nodes - if (!DumpBones(pFile, pexpiface)) - { - fclose( pFile ); - return 0; /*fail*/ - } - - // Output bone rotations, for each frame. Do only first frame if this is the reference frame MAX file - DumpRotations(pFile, pexpiface); - - // Output triangle meshes (first frame/all frames), if this is the reference frame MAX file - if (m_fReferenceFrame) - { - DumpModel(pFile, pexpiface); - } - - if (!suppressPrompts) - { - // Tell user that exporting is finished (it can take a while with no feedback) - char szExportComplete[300]; - sprintf(szExportComplete, "Exported %s.", szFile); - MessageBox(GetActiveWindow(), szExportComplete, "Status", MB_OK); - } - - fclose( pFile ); - - return 1/*success*/; -} - - -BOOL SmdExportClass::CollectNodes( ExpInterface *pexpiface) -{ - // Count total nodes in the model, so I can alloc array - // Also "brands" each node with node index, or with "skip me" marker. - CountNodesTEP procCountNodes; - procCountNodes.m_cNodes = 0; - (void) pexpiface->theScene->EnumTree(&procCountNodes); - ASSERT_MBOX(procCountNodes.m_cNodes > 0, "No nodes!"); - - // Alloc and fill array - m_imaxnodeMac = procCountNodes.m_cNodes; - m_rgmaxnode = new MaxNode[m_imaxnodeMac]; - ASSERT_MBOX(m_rgmaxnode != NULL, "new failed"); - - - CollectNodesTEP procCollectNodes; - procCollectNodes.m_phec = this; - (void) pexpiface->theScene->EnumTree(&procCollectNodes); - - return TRUE; -} - - -BOOL SmdExportClass::DumpBones(FILE *pFile, ExpInterface *pexpiface) -{ - // Dump bone names - DumpNodesTEP procDumpNodes; - procDumpNodes.m_pfile = pFile; - procDumpNodes.m_phec = this; - fprintf(pFile, "nodes\n" ); - (void) pexpiface->theScene->EnumTree(&procDumpNodes); - fprintf(pFile, "end\n" ); - - return TRUE; -} - - -BOOL SmdExportClass::DumpRotations(FILE *pFile, ExpInterface *pexpiface) -{ - // Dump bone-rotation info, for each frame - // Also dumps root-node translation info (the model's world-position at each frame) - DumpFrameRotationsTEP procDumpFrameRotations; - procDumpFrameRotations.m_pfile = pFile; - procDumpFrameRotations.m_phec = this; - - TimeValue m_tvTill = (m_fReferenceFrame) ? m_tvStart : m_tvEnd; - - fprintf(pFile, "skeleton\n" ); - for (TimeValue tv = m_tvStart; tv <= m_tvTill; tv += m_tpf) - { - fprintf(pFile, "time %d\n", tv / GetTicksPerFrame() ); - procDumpFrameRotations.m_tvToDump = tv; - (void) pexpiface->theScene->EnumTree(&procDumpFrameRotations); - } - fprintf(pFile, "end\n" ); - - return TRUE; -} - - -BOOL SmdExportClass::DumpModel( FILE *pFile, ExpInterface *pexpiface) -{ - // Dump mesh info: vertices, normals, UV texture map coords, bone assignments - DumpModelTEP procDumpModel; - procDumpModel.m_pfile = pFile; - procDumpModel.m_phec = this; - fprintf(pFile, "triangles\n" ); - procDumpModel.m_tvToDump = m_tvStart; - (void) pexpiface->theScene->EnumTree(&procDumpModel); - fprintf(pFile, "end\n" ); - return TRUE; -} - - - - -//============================================================================= -// TREE-ENUMERATION PROCEDURES -//============================================================================= - -#define ASSERT_AND_ABORT(f, sz) \ - if (!(f)) \ - { \ - ASSERT_MBOX(FALSE, sz); \ - cleanup( ); \ - return TREE_ABORT; \ - } - - -//================================================================= -// Methods for CountNodesTEP -// -int CountNodesTEP::callback( INode *node) -{ - INode *pnode = node; // Hungarian - - ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); - - if (::FUndesirableNode(pnode)) - { - // Mark as skippable - ::SetIndexOfINode(pnode, SmdExportClass::UNDESIRABLE_NODE_MARKER); - return TREE_CONTINUE; - } - - // Establish "node index"--just ascending ints - ::SetIndexOfINode(pnode, m_cNodes); - - m_cNodes++; - - return TREE_CONTINUE; -} - - -//================================================================= -// Methods for CollectNodesTEP -// -int CollectNodesTEP::callback(INode *node) -{ - INode *pnode = node; // Hungarian - - ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); - - if (::FNodeMarkedToSkip(pnode)) - return TREE_CONTINUE; - - // Get pre-stored "index" - int iNode = ::GetIndexOfINode(pnode); - ASSERT_MBOX(iNode >= 0 && iNode <= m_phec->m_imaxnodeMac-1, "Bogus iNode"); - - // Get name, store name in array - TSTR strNodeName(pnode->GetName()); - strcpy(m_phec->m_rgmaxnode[iNode].szNodeName, (char*)strNodeName); - - // Get Node's time-zero Transformation Matrices - m_phec->m_rgmaxnode[iNode].mat3NodeTM = pnode->GetNodeTM(0/*TimeValue*/); - m_phec->m_rgmaxnode[iNode].mat3ObjectTM = pnode->GetObjectTM(0/*TimeValue*/); - - // I'll calculate this later - m_phec->m_rgmaxnode[iNode].imaxnodeParent = SmdExportClass::UNDESIRABLE_NODE_MARKER; - - return TREE_CONTINUE; -} - - - - - - -//================================================================= -// Methods for DumpNodesTEP -// -int DumpNodesTEP::callback(INode *pnode) -{ - ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); - - if (::FNodeMarkedToSkip(pnode)) - return TREE_CONTINUE; - - // Get node's parent - INode *pnodeParent; - pnodeParent = pnode->GetParentNode(); - - // The model's root is a child of the real "scene root" - TSTR strNodeName(pnode->GetName()); - BOOL fNodeIsRoot = pnodeParent->IsRootNode( ); - - int iNode = ::GetIndexOfINode(pnode); - int iNodeParent = ::GetIndexOfINode(pnodeParent, !fNodeIsRoot/*fAssertPropExists*/); - - // Convenient time to cache this - m_phec->m_rgmaxnode[iNode].imaxnodeParent = fNodeIsRoot ? SmdExportClass::UNDESIRABLE_NODE_MARKER : iNodeParent; - - // Root node has no parent, thus no translation - if (fNodeIsRoot) - iNodeParent = -1; - - // check to see if the matrix isn't right handed - m_phec->m_rgmaxnode[iNode].isMirrored = DotProd( CrossProd( m_phec->m_rgmaxnode[iNode].mat3ObjectTM.GetRow(0).Normalize(), m_phec->m_rgmaxnode[iNode].mat3ObjectTM.GetRow(1).Normalize() ).Normalize(), m_phec->m_rgmaxnode[iNode].mat3ObjectTM.GetRow(2).Normalize() ) < 0; - - // Dump node description - fprintf(m_pfile, "%3d \"%s\" %3d\n", - iNode, - strNodeName, - iNodeParent ); - - return TREE_CONTINUE; -} - - - -//================================================================= -// Methods for DumpFrameRotationsTEP -// -int DumpFrameRotationsTEP::callback(INode *pnode) -{ - ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); - - if (::FNodeMarkedToSkip(pnode)) - return TREE_CONTINUE; - - int iNode = ::GetIndexOfINode(pnode); - - TSTR strNodeName(pnode->GetName()); - - // The model's root is a child of the real "scene root" - INode *pnodeParent = pnode->GetParentNode(); - BOOL fNodeIsRoot = pnodeParent->IsRootNode( ); - - // Get Node's "Local" Transformation Matrix - Matrix3 mat3NodeTM = pnode->GetNodeTM(m_tvToDump); - Matrix3 mat3ParentTM = pnodeParent->GetNodeTM(m_tvToDump); - mat3NodeTM.NoScale(); // Clear these out because they apparently - mat3ParentTM.NoScale(); // screw up the following calculation. - Matrix3 mat3NodeLocalTM = mat3NodeTM * Inverse(mat3ParentTM); - Point3 rowTrans = mat3NodeLocalTM.GetTrans(); - - // check to see if the parent bone was mirrored. If so, mirror invert this bones position - if (m_phec->m_rgmaxnode[iNode].imaxnodeParent >= 0 && m_phec->m_rgmaxnode[m_phec->m_rgmaxnode[iNode].imaxnodeParent].isMirrored) - { - rowTrans = rowTrans * -1.0f; - } - - // Get the rotation (via decomposition into "affine parts", then quaternion-to-Euler) - // Apparently the order of rotations returned by QuatToEuler() is X, then Y, then Z. - AffineParts affparts; - float rgflXYZRotations[3]; - - decomp_affine(mat3NodeLocalTM, &affparts); - QuatToEuler(affparts.q, rgflXYZRotations); - - float xRot = rgflXYZRotations[0]; // in radians - float yRot = rgflXYZRotations[1]; // in radians - float zRot = rgflXYZRotations[2]; // in radians - - // Get rotations in the -2pi...2pi range - xRot = ::FlReduceRotation(xRot); - yRot = ::FlReduceRotation(yRot); - zRot = ::FlReduceRotation(zRot); - - // Print rotations - fprintf(m_pfile, "%3d %f %f %f %f %f %f\n", - // Node:%-15s Rotation (x,y,z)\n", - iNode, rowTrans.x, rowTrans.y, rowTrans.z, xRot, yRot, zRot); - - return TREE_CONTINUE; -} - - - -//================================================================= -// Methods for DumpModelTEP -// -Modifier *FindPhysiqueModifier (INode *nodePtr) -{ - // Get object from node. Abort if no object. - Object *ObjectPtr = nodePtr->GetObjectRef(); - if (!ObjectPtr) return NULL; - - // Is derived object ? - if (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID) - { - // Yes -> Cast. - IDerivedObject *DerivedObjectPtr = static_cast(ObjectPtr); - - // Iterate over all entries of the modifier stack. - int ModStackIndex = 0; - while (ModStackIndex < DerivedObjectPtr->NumModifiers()) - { - // Get current modifier. - Modifier *ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex); - - // Is this Physique ? - if (ModifierPtr->ClassID() == Class_ID( PHYSIQUE_CLASS_ID_A, PHYSIQUE_CLASS_ID_B) ) - { - // Yes -> Exit. - return ModifierPtr; - } - // Next modifier stack entry. - ModStackIndex++; - } - } - // Not found. - return NULL; -} - -Modifier *FindBonesProModifier (INode *nodePtr) -{ - // Get object from node. Abort if no object. - Object *ObjectPtr = nodePtr->GetObjectRef(); - if (!ObjectPtr) return NULL; - - // Is derived object ? - if (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID) - { - // Yes -> Cast. - IDerivedObject *DerivedObjectPtr = static_cast(ObjectPtr); - - // Iterate over all entries of the modifier stack. - int ModStackIndex = 0; - while (ModStackIndex < DerivedObjectPtr->NumModifiers()) - { - // Get current modifier. - Modifier *ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex); - - // Is this Bones Pro OSM? - if (ModifierPtr->ClassID() == BP_CLASS_ID_OSM ) - { - // Yes -> Exit. - return ModifierPtr; - } - // Is this Bones Pro WSM? - if (ModifierPtr->ClassID() == BP_CLASS_ID_WSM ) - { - // Yes -> Exit. - return ModifierPtr; - } - // Next modifier stack entry. - ModStackIndex++; - } - } - // Not found. - return NULL; -} - -// #define DEBUG_MESH_DUMP - -//================================================================= -// Methods for DumpModelTEP -// -int DumpModelTEP::callback(INode *pnode) -{ - Object* pobj; - int fHasMat = TRUE; - - // clear physique export parameters - m_mcExport = NULL; - m_phyExport = NULL; - m_phyMod = NULL; - m_bonesProMod = NULL; - - ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); - - if (::FNodeMarkedToSkip(pnode)) - return TREE_CONTINUE; - - // Actually, if it's not selected, skip it! - //if (!pnode->Selected()) - // return TRUE; - - int iNode = ::GetIndexOfINode(pnode); - TSTR strNodeName(pnode->GetName()); - - // The Footsteps node apparently MUST have a dummy mesh attached! Ignore it explicitly. - if (FStrEq((char*)strNodeName, "Bip01 Footsteps")) - return TREE_CONTINUE; - - // Helper nodes don't have meshes - pobj = pnode->GetObjectRef(); - if (pobj->SuperClassID() == HELPER_CLASS_ID) - return TREE_CONTINUE; - - // The model's root is a child of the real "scene root" - INode *pnodeParent = pnode->GetParentNode(); - BOOL fNodeIsRoot = pnodeParent->IsRootNode( ); - - // Get node's material: should be a multi/sub (if it has a material at all) - Mtl *pmtlNode = pnode->GetMtl(); - if (pmtlNode == NULL) - { - return TREE_CONTINUE; - fHasMat = FALSE; - } - else if (!(pmtlNode->ClassID() == Class_ID(MULTI_CLASS_ID, 0) && pmtlNode->IsMultiMtl())) - { - // sprintf(st_szDBG, "ERROR--Material on node %s isn't a Multi/Sub-Object", (char*)strNodeName); - // ASSERT_AND_ABORT(FALSE, st_szDBG); - // fHasMat = FALSE; - } - - // Get Node's object, convert to a triangle-mesh object, so I can access the Faces - ObjectState os = pnode->EvalWorldState(m_tvToDump); - pobj = os.obj; - TriObject *ptriobj; - BOOL fConvertedToTriObject = - pobj->CanConvertToType(triObjectClassID) && - (ptriobj = (TriObject*)pobj->ConvertToType(m_tvToDump, triObjectClassID)) != NULL; - if (!fConvertedToTriObject) - return TREE_CONTINUE; - Mesh *pmesh = &ptriobj->mesh; - - // Shouldn't have gotten this far if it's a helper object - if (pobj->SuperClassID() == HELPER_CLASS_ID) - { - sprintf(st_szDBG, "ERROR--Helper node %s has an attached mesh, and it shouldn't.", (char*)strNodeName); - ASSERT_AND_ABORT(FALSE, st_szDBG); - } - - // Ensure that the vertex normals are up-to-date - pmesh->buildNormals(); - - // We want the vertex coordinates in World-space, not object-space - Matrix3 mat3ObjectTM = pnode->GetObjectTM(m_tvToDump); - - - // initialize physique export parameters - m_phyMod = FindPhysiqueModifier(pnode); - if (m_phyMod) - { - // Physique Modifier exists for given Node - m_phyExport = (IPhysiqueExport *)m_phyMod->GetInterface(I_PHYINTERFACE); - - if (m_phyExport) - { - // create a ModContext Export Interface for the specific node of the Physique Modifier - m_mcExport = (IPhyContextExport *)m_phyExport->GetContextInterface(pnode); - - if (m_mcExport) - { - // convert all vertices to Rigid - m_mcExport->ConvertToRigid(TRUE); - } - } - } - - // initialize bones pro export parameters - m_wa = NULL; - m_bonesProMod = FindBonesProModifier(pnode); - if (m_bonesProMod) - { - m_bonesProMod->SetProperty( BP_PROPID_GET_WEIGHTS, &m_wa ); - } - - // Dump the triangle face info - int cFaces = pmesh->getNumFaces(); - for (int iFace = 0; iFace < cFaces; iFace++) - { - Face* pface = &pmesh->faces[iFace]; - TVFace* ptvface = (pmesh->tvFace) ? &pmesh->tvFace[iFace] : NULL; - DWORD smGroupFace = pface->getSmGroup(); - - // Get face's 3 indexes into the Mesh's vertex array(s). - DWORD iVertex0 = pface->getVert(0); - DWORD iVertex1 = pface->getVert(1); - DWORD iVertex2 = pface->getVert(2); - ASSERT_AND_ABORT((int)iVertex0 < pmesh->getNumVerts(), "Bogus Vertex 0 index"); - ASSERT_AND_ABORT((int)iVertex1 < pmesh->getNumVerts(), "Bogus Vertex 1 index"); - ASSERT_AND_ABORT((int)iVertex2 < pmesh->getNumVerts(), "Bogus Vertex 2 index"); - - // Get the 3 Vertex's for this face - Point3 pt3Vertex0 = pmesh->getVert(iVertex0); - Point3 pt3Vertex1 = pmesh->getVert(iVertex1); - Point3 pt3Vertex2 = pmesh->getVert(iVertex2); - - // Get the 3 RVertex's for this face - // NOTE: I'm using getRVertPtr instead of getRVert to work around a 3DSMax bug - RVertex *prvertex0 = pmesh->getRVertPtr(iVertex0); - RVertex *prvertex1 = pmesh->getRVertPtr(iVertex1); - RVertex *prvertex2 = pmesh->getRVertPtr(iVertex2); - - // Find appropriate normals for each RVertex - // A vertex can be part of multiple faces, so the "smoothing group" - // is used to locate the normal for this face's use of the vertex. - Point3 pt3Vertex0Normal; - Point3 pt3Vertex1Normal; - Point3 pt3Vertex2Normal; - if (smGroupFace) - { - pt3Vertex0Normal = Pt3GetRVertexNormal(prvertex0, smGroupFace); - pt3Vertex1Normal = Pt3GetRVertexNormal(prvertex1, smGroupFace); - pt3Vertex2Normal = Pt3GetRVertexNormal(prvertex2, smGroupFace); - } - else - { - pt3Vertex0Normal = pmesh->getFaceNormal( iFace ); - pt3Vertex1Normal = pmesh->getFaceNormal( iFace ); - pt3Vertex2Normal = pmesh->getFaceNormal( iFace ); - } - ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus orig normal 0" ); - ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus orig normal 1" ); - ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus orig normal 2" ); - - // Get Face's sub-material from node's material, to get the bitmap name. - // And no, there isn't a simpler way to get the bitmap name, you have to - // dig down through all these levels. - TCHAR szBitmapName[256] = "null.bmp"; - if (fHasMat) - { - Texmap *ptexmap = NULL; - MtlID mtlidFace = pface->getMatID(); - if (pmtlNode->IsMultiMtl()) - { - if (mtlidFace >= pmtlNode->NumSubMtls()) - { - sprintf(st_szDBG, "ERROR--Bogus sub-material index %d in node %s; highest valid index is %d", - mtlidFace, (char*)strNodeName, pmtlNode->NumSubMtls()-1); - // ASSERT_AND_ABORT(FALSE, st_szDBG); - mtlidFace = 0; - } - Mtl *pmtlFace = pmtlNode->GetSubMtl(mtlidFace); - ASSERT_AND_ABORT(pmtlFace != NULL, "NULL Sub-material returned"); - - /* - if ((pmtlFace->ClassID() == Class_ID(MULTI_CLASS_ID, 0) && pmtlFace->IsMultiMtl())) - { - // it's a sub-sub material. Gads. - pmtlFace = pmtlFace->GetSubMtl(mtlidFace); - ASSERT_AND_ABORT(pmtlFace != NULL, "NULL Sub-material returned"); - } - */ - - if (!(pmtlFace->ClassID() == Class_ID(DMTL_CLASS_ID, 0))) - { - - sprintf(st_szDBG, - "ERROR--Sub-material with index %d (used in node %s) isn't a 'default/standard' material [%x].", - mtlidFace, (char*)strNodeName, pmtlFace->ClassID()); - ASSERT_AND_ABORT(FALSE, st_szDBG); - } - StdMat *pstdmtlFace = (StdMat*)pmtlFace; - ptexmap = pstdmtlFace->GetSubTexmap(ID_DI); - } - else - { - ptexmap = pmtlNode->GetActiveTexmap(); - } - - // ASSERT_AND_ABORT(ptexmap != NULL, "NULL diffuse texture") - if (ptexmap != NULL) - { - if (!(ptexmap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0))) - { - sprintf(st_szDBG, - "ERROR--Sub-material with index %d (used in node %s) doesn't have a bitmap as its diffuse texture.", - mtlidFace, (char*)strNodeName); - ASSERT_AND_ABORT(FALSE, st_szDBG); - } - BitmapTex *pbmptex = (BitmapTex*)ptexmap; - strcpy(szBitmapName, pbmptex->GetMapName()); - TSTR strPath, strFile; - SplitPathFile(TSTR(szBitmapName), &strPath, &strFile); - strcpy(szBitmapName,strFile); - } - } - - UVVert UVvertex0( 0, 0, 0 ); - UVVert UVvertex1( 1, 0, 0 ); - UVVert UVvertex2( 0, 1, 0 ); - - // All faces must have textures assigned to them - if (ptvface && (pface->flags & HAS_TVERTS)) - { - // Get TVface's 3 indexes into the Mesh's TVertex array(s). - DWORD iTVertex0 = ptvface->getTVert(0); - DWORD iTVertex1 = ptvface->getTVert(1); - DWORD iTVertex2 = ptvface->getTVert(2); - ASSERT_AND_ABORT((int)iTVertex0 < pmesh->getNumTVerts(), "Bogus TVertex 0 index"); - ASSERT_AND_ABORT((int)iTVertex1 < pmesh->getNumTVerts(), "Bogus TVertex 1 index"); - ASSERT_AND_ABORT((int)iTVertex2 < pmesh->getNumTVerts(), "Bogus TVertex 2 index"); - - // Get the 3 TVertex's for this TVFace - // NOTE: I'm using getRVertPtr instead of getRVert to work around a 3DSMax bug - UVvertex0 = pmesh->getTVert(iTVertex0); - UVvertex1 = pmesh->getTVert(iTVertex1); - UVvertex2 = pmesh->getTVert(iTVertex2); - } - else - { - //sprintf(st_szDBG, "ERROR--Node %s has a textureless face. All faces must have an applied texture.", (char*)strNodeName); - //ASSERT_AND_ABORT(FALSE, st_szDBG); - } - - /* - const char *szExpectedExtension = ".bmp"; - if (stricmp(szBitmapName+strlen(szBitmapName)-strlen(szExpectedExtension), szExpectedExtension) != 0) - { - sprintf(st_szDBG, "Node %s uses %s, which is not a %s file", (char*)strNodeName, szBitmapName, szExpectedExtension); - ASSERT_AND_ABORT(FALSE, st_szDBG); - } - */ - - // Determine owning bones for the vertices. - int iNodeV0, iNodeV1, iNodeV2; - // Simple 3dsMax model: the vertices are owned by the object, and hence the node - iNodeV0 = iNode; - iNodeV1 = iNode; - iNodeV2 = iNode; - - // Rotate the face vertices out of object-space, and into world-space space - Point3 v0 = pt3Vertex0 * mat3ObjectTM; - Point3 v1 = pt3Vertex1 * mat3ObjectTM; - Point3 v2 = pt3Vertex2 * mat3ObjectTM; - - - Matrix3 mat3ObjectNTM = mat3ObjectTM; - mat3ObjectNTM.NoScale( ); - ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus pre normal 0" ); - pt3Vertex0Normal = VectorTransform(mat3ObjectNTM, pt3Vertex0Normal); - ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus post normal 0" ); - ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus pre normal 1" ); - pt3Vertex1Normal = VectorTransform(mat3ObjectNTM, pt3Vertex1Normal); - ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus post normal 1" ); - ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus pre normal 2" ); - pt3Vertex2Normal = VectorTransform(mat3ObjectNTM, pt3Vertex2Normal); - ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus post normal 2" ); - - // Finally dump the bitmap name and 3 lines of face info - fprintf(m_pfile, "%s\n", szBitmapName); - fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f", - iNodeV0, v0.x, v0.y, v0.z, - pt3Vertex0Normal.x, pt3Vertex0Normal.y, pt3Vertex0Normal.z, - UVvertex0.x, UVvertex0.y); - DumpWeights( iVertex0 ); - fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f", - iNodeV1, v1.x, v1.y, v1.z, - pt3Vertex1Normal.x, pt3Vertex1Normal.y, pt3Vertex1Normal.z, - UVvertex1.x, UVvertex1.y); - DumpWeights( iVertex1 ); - fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f", - iNodeV2, v2.x, v2.y, v2.z, - pt3Vertex2Normal.x, pt3Vertex2Normal.y, pt3Vertex2Normal.z, - UVvertex2.x, UVvertex2.y); - DumpWeights( iVertex2 ); - } - - cleanup( ); - return TREE_CONTINUE; -} - - -#define MAX_BLEND_WEIGHTS 8 - -static struct { - int iNode; - float flWeight; -} aWeights[MAX_BLEND_WEIGHTS+1]; - -int AddWeight( int iCount, int iNode, float flWeight ) -{ - if (flWeight < 0.001) - return iCount; - - for (int i = 0; i < iCount; i++) - { - if (aWeights[i].flWeight < flWeight) - { - for (int j = iCount; j > i; j--) - { - aWeights[j] = aWeights[j-1]; - } - break; - } - } - aWeights[i].iNode = iNode; - aWeights[i].flWeight = flWeight; - - iCount++; - if (iCount > MAX_BLEND_WEIGHTS) - iCount = MAX_BLEND_WEIGHTS; - - return iCount; -} - - -void DumpModelTEP::DumpWeights(int iVertex) -{ - if (m_mcExport) - { - IPhyVertexExport *vtxExport = m_mcExport->GetVertexInterface(iVertex); - - if (vtxExport) - { - if (vtxExport->GetVertexType() & BLENDED_TYPE) - { - IPhyBlendedRigidVertex *pBlend = ((IPhyBlendedRigidVertex *)vtxExport); - int iCount = 0; - - for (int i = 0; i < pBlend->GetNumberNodes(); i++) - { - iCount = AddWeight( iCount, GetIndexOfINode( pBlend->GetNode( i ) ), pBlend->GetWeight( i ) ); - } - - fprintf(m_pfile, " %2d ", iCount ); - for (i = 0; i < iCount; i++) - { - fprintf(m_pfile, " %2d %5.3f ", aWeights[i].iNode, aWeights[i].flWeight ); - } - } - else - { - INode *Bone = ((IPhyRigidVertex *)vtxExport)->GetNode(); - - fprintf(m_pfile, " 1 %2d 1.000", GetIndexOfINode(Bone) ); - } - m_mcExport->ReleaseVertexInterface(vtxExport); - } - } - else if (m_wa != NULL) - { - int iCount = 0; - - for ( int iBone = 0; iBone < m_wa->nb; iBone++) - { - if (m_wa->w[iVertex * m_wa->nb + iBone] > 0.0) - { - BonesPro_Bone bone; - bone.t = BP_TIME_ATTACHED; - bone.index = iBone; - m_bonesProMod->SetProperty( BP_PROPID_GET_BONE_STAT, &bone ); - if (bone.node != NULL) - { - iCount = AddWeight( iCount, GetIndexOfINode( bone.node ), m_wa->w[iVertex * m_wa->nb + iBone] ); - } - } - } - - fprintf(m_pfile, " %2d ", iCount ); - for (int i = 0; i < iCount; i++) - { - fprintf(m_pfile, " %2d %5.3f ", aWeights[i].iNode, aWeights[i].flWeight ); - } - } - - fprintf(m_pfile, "\n" ); - fflush( m_pfile ); -} - -void DumpModelTEP::cleanup(void) -{ - if (m_phyMod && m_phyExport) - { - if (m_mcExport) - { - m_phyExport->ReleaseContextInterface(m_mcExport); - m_mcExport = NULL; - } - m_phyMod->ReleaseInterface(I_PHYINTERFACE, m_phyExport); - m_phyExport = NULL; - m_phyMod = NULL; - } -} - - -Point3 DumpModelTEP::Pt3GetRVertexNormal(RVertex *prvertex, DWORD smGroupFace) -{ - // Lookup the appropriate vertex normal, based on smoothing group. - int cNormals = prvertex->rFlags & NORCT_MASK; - - ASSERT_MBOX((cNormals == 1 && prvertex->ern == NULL) || - (cNormals > 1 && prvertex->ern != NULL), "BOGUS RVERTEX"); - - if (cNormals == 1) - return prvertex->rn.getNormal(); - else - { - for (int irn = 0; irn < cNormals; irn++) - if (prvertex->ern[irn].getSmGroup() & smGroupFace) - break; - - if (irn >= cNormals) - { - irn = 0; - // ASSERT_MBOX(irn < cNormals, "unknown smoothing group\n"); - } - return prvertex->ern[irn].getNormal(); - } -} - - - - - -//=========================================================== -// Dialog proc for export options -// -static BOOL CALLBACK ExportOptionsDlgProc( - HWND hDlg, - UINT message, - WPARAM wParam, - LPARAM lParam) -{ - static SmdExportClass *pexp; - switch (message) - { - case WM_INITDIALOG: - pexp = (SmdExportClass*) lParam; - CheckRadioButton(hDlg, IDC_CHECK_SKELETAL, IDC_CHECK_REFFRAME, IDC_CHECK_SKELETAL); - SetFocus(GetDlgItem(hDlg,IDOK)); - return FALSE; - case WM_DESTROY: - return FALSE; - case WM_COMMAND: - switch (LOWORD(wParam)) - { - case IDOK: - pexp->m_fReferenceFrame = IsDlgButtonChecked(hDlg, IDC_CHECK_REFFRAME); - EndDialog(hDlg, 1); // 1 indicates "ok to export" - return TRUE; - case IDCANCEL: // 0 indicates "cancel export" - EndDialog(hDlg, 0); - return TRUE; - case IDC_CHECK_SKELETAL: - case IDC_CHECK_REFFRAME: - CheckRadioButton(hDlg, IDC_CHECK_SKELETAL, IDC_CHECK_REFFRAME, LOWORD(wParam)); - break; - } - } - return FALSE; -} - - - -//======================================================================== -// Utility functions for getting/setting the personal "node index" property. -// NOTE: I'm storing a string-property because I hit a 3DSMax bug in v1.2 when I -// NOTE: tried using an integer property. -// FURTHER NOTE: those properties seem to change randomly sometimes, so I'm -// implementing my own. - -typedef struct -{ - char szNodeName[SmdExportClass::MAX_NAME_CHARS]; - int iNode; -} NAMEMAP; -const int MAX_NAMEMAP = 512; -static NAMEMAP g_rgnm[MAX_NAMEMAP]; - -int GetIndexOfINode(INode *pnode, BOOL fAssertPropExists) -{ - TSTR strNodeName(pnode->GetName()); - for (int inm = 0; inm < g_inmMac; inm++) - { - if (FStrEq(g_rgnm[inm].szNodeName, (char*)strNodeName)) - { - return g_rgnm[inm].iNode; - } - } - - if (fAssertPropExists) - ASSERT_MBOX(FALSE, "No NODEINDEXSTR property"); - return -7777; -} - - -void SetIndexOfINode(INode *pnode, int inode) -{ - TSTR strNodeName(pnode->GetName()); - NAMEMAP *pnm; - for (int inm = 0; inm < g_inmMac; inm++) - if (FStrEq(g_rgnm[inm].szNodeName, (char*)strNodeName)) - break; - if (inm < g_inmMac) - pnm = &g_rgnm[inm]; - else - { - ASSERT_MBOX(g_inmMac < MAX_NAMEMAP, "NAMEMAP is full"); - pnm = &g_rgnm[g_inmMac++]; - strcpy(pnm->szNodeName, (char*)strNodeName); - } - pnm->iNode = inode; -} - - -//============================================================= -// Returns TRUE if a node should be ignored during tree traversal. -// -BOOL FUndesirableNode(INode *pnode) -{ - // Get Node's underlying object, and object class name - Object *pobj = pnode->GetObjectRef(); - - // Don't care about lights, dummies, and cameras - if (pobj->SuperClassID() == CAMERA_CLASS_ID) - return TRUE; - if (pobj->SuperClassID() == LIGHT_CLASS_ID) - return TRUE; - - return FALSE; -} - - -//============================================================= -// Returns TRUE if a node has been marked as skippable -// -BOOL FNodeMarkedToSkip(INode *pnode) -{ - return (::GetIndexOfINode(pnode) == SmdExportClass::UNDESIRABLE_NODE_MARKER); -} - - -//============================================================= -// Reduces a rotation to within the -2PI..2PI range. -// -static float FlReduceRotation(float fl) -{ - while (fl >= TWOPI) - fl -= TWOPI; - while (fl <= -TWOPI) - fl += TWOPI; - return fl; -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "MAX.H" +#include "DECOMP.H" +#include "STDMAT.H" +#include "ANIMTBL.H" +#include "istdplug.h" +#include "phyexp.h" +#include "BonesPro.h" + +#include "smexprc.h" +#include "smedefs.h" + +//=================================================================== +// Prototype declarations +// +int GetIndexOfINode(INode *pnode,BOOL fAssertPropExists = TRUE); +void SetIndexOfINode(INode *pnode, int inode); +BOOL FUndesirableNode(INode *pnode); +BOOL FNodeMarkedToSkip(INode *pnode); +float FlReduceRotation(float fl); + + +//=================================================================== +// Global variable definitions +// + +// Save for use with dialogs +static HINSTANCE hInstance; + +// We just need one of these to hand off to 3DSMAX. +static SmdExportClassDesc SmdExportCD; + +// For OutputDebugString and misc sprintf's +static char st_szDBG[300]; + +// INode mapping table +static int g_inmMac = 0; + +//=================================================================== +// Utility functions +// + +static int AssertFailedFunc(char *sz) +{ + MessageBox(GetActiveWindow(), sz, "Assert failure", MB_OK); + int Set_Your_Breakpoint_Here = 1; + return 1; +} +#define ASSERT_MBOX(f, sz) ((f) ? 1 : AssertFailedFunc(sz)) + + +//=================================================================== +// Required plug-in export functions +// +BOOL WINAPI DllMain( HINSTANCE hinstDLL, ULONG fdwReason, LPVOID lpvReserved) +{ + static int fFirstTimeHere = TRUE; + if (fFirstTimeHere) + { + fFirstTimeHere = FALSE; + hInstance = hinstDLL; + } + return TRUE; +} + + +EXPORT_THIS int LibNumberClasses(void) +{ + return 1; +} + + +EXPORT_THIS ClassDesc *LibClassDesc(int iWhichClass) +{ + switch(iWhichClass) + { + case 0: return &SmdExportCD; + default: return 0; + } +} + + +EXPORT_THIS const TCHAR *LibDescription() +{ + return _T("Valve SMD Plug-in."); +} + + +EXPORT_THIS ULONG LibVersion() +{ + return VERSION_3DSMAX; +} + + +//===================================================================== +// Methods for SmdExportClass +// + +CONSTRUCTOR SmdExportClass::SmdExportClass(void) +{ + m_rgmaxnode = NULL; +} + + +DESTRUCTOR SmdExportClass::~SmdExportClass(void) +{ + if (m_rgmaxnode) + delete[] m_rgmaxnode; +} + + +int SmdExportClass::DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts, DWORD options) +{ + ExpInterface *pexpiface = ei; // Hungarian + Interface *piface = i; // Hungarian + + // Reset the name-map property manager + g_inmMac = 0; + + if (!suppressPrompts) + { + // Present the user with the Export Options dialog + if (DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_EXPORTOPTIONS), GetActiveWindow(), + ExportOptionsDlgProc, (LPARAM)this) <= 0) + return 0; // error or cancel + } + else + { + m_fReferenceFrame = 0; + } + + // Break up filename, re-assemble longer versions + TSTR strPath, strFile, strExt; + TCHAR szFile[MAX_PATH]; + SplitFilename(TSTR(name), &strPath, &strFile, &strExt); + sprintf(szFile, "%s\\%s.%s", (char*)strPath, (char*)strFile, DEFAULT_EXT); + + /* + if (m_fReferenceFrame) + sprintf(szFile, "%s\\%s_model.%s", (char*)strPath, (char*)strFile, DEFAULT_EXT); + */ + + FILE *pFile; + if ((pFile = fopen(szFile, "w")) == NULL) + return FALSE/*failure*/; + + fprintf( pFile, "version %d\n", 1 ); + + // Get animation metrics + m_intervalOfAnimation = piface->GetAnimRange(); + m_tvStart = m_intervalOfAnimation.Start(); + m_tvEnd = m_intervalOfAnimation.End(); + m_tpf = ::GetTicksPerFrame(); + + // Count nodes, label them, collect into array + if (!CollectNodes(pexpiface)) + return 0; /*fail*/ + + // Output nodes + if (!DumpBones(pFile, pexpiface)) + { + fclose( pFile ); + return 0; /*fail*/ + } + + // Output bone rotations, for each frame. Do only first frame if this is the reference frame MAX file + DumpRotations(pFile, pexpiface); + + // Output triangle meshes (first frame/all frames), if this is the reference frame MAX file + if (m_fReferenceFrame) + { + DumpModel(pFile, pexpiface); + } + + if (!suppressPrompts) + { + // Tell user that exporting is finished (it can take a while with no feedback) + char szExportComplete[300]; + sprintf(szExportComplete, "Exported %s.", szFile); + MessageBox(GetActiveWindow(), szExportComplete, "Status", MB_OK); + } + + fclose( pFile ); + + return 1/*success*/; +} + + +BOOL SmdExportClass::CollectNodes( ExpInterface *pexpiface) +{ + // Count total nodes in the model, so I can alloc array + // Also "brands" each node with node index, or with "skip me" marker. + CountNodesTEP procCountNodes; + procCountNodes.m_cNodes = 0; + (void) pexpiface->theScene->EnumTree(&procCountNodes); + ASSERT_MBOX(procCountNodes.m_cNodes > 0, "No nodes!"); + + // Alloc and fill array + m_imaxnodeMac = procCountNodes.m_cNodes; + m_rgmaxnode = new MaxNode[m_imaxnodeMac]; + ASSERT_MBOX(m_rgmaxnode != NULL, "new failed"); + + + CollectNodesTEP procCollectNodes; + procCollectNodes.m_phec = this; + (void) pexpiface->theScene->EnumTree(&procCollectNodes); + + return TRUE; +} + + +BOOL SmdExportClass::DumpBones(FILE *pFile, ExpInterface *pexpiface) +{ + // Dump bone names + DumpNodesTEP procDumpNodes; + procDumpNodes.m_pfile = pFile; + procDumpNodes.m_phec = this; + fprintf(pFile, "nodes\n" ); + (void) pexpiface->theScene->EnumTree(&procDumpNodes); + fprintf(pFile, "end\n" ); + + return TRUE; +} + + +BOOL SmdExportClass::DumpRotations(FILE *pFile, ExpInterface *pexpiface) +{ + // Dump bone-rotation info, for each frame + // Also dumps root-node translation info (the model's world-position at each frame) + DumpFrameRotationsTEP procDumpFrameRotations; + procDumpFrameRotations.m_pfile = pFile; + procDumpFrameRotations.m_phec = this; + + TimeValue m_tvTill = (m_fReferenceFrame) ? m_tvStart : m_tvEnd; + + fprintf(pFile, "skeleton\n" ); + for (TimeValue tv = m_tvStart; tv <= m_tvTill; tv += m_tpf) + { + fprintf(pFile, "time %d\n", tv / GetTicksPerFrame() ); + procDumpFrameRotations.m_tvToDump = tv; + (void) pexpiface->theScene->EnumTree(&procDumpFrameRotations); + } + fprintf(pFile, "end\n" ); + + return TRUE; +} + + +BOOL SmdExportClass::DumpModel( FILE *pFile, ExpInterface *pexpiface) +{ + // Dump mesh info: vertices, normals, UV texture map coords, bone assignments + DumpModelTEP procDumpModel; + procDumpModel.m_pfile = pFile; + procDumpModel.m_phec = this; + fprintf(pFile, "triangles\n" ); + procDumpModel.m_tvToDump = m_tvStart; + (void) pexpiface->theScene->EnumTree(&procDumpModel); + fprintf(pFile, "end\n" ); + return TRUE; +} + + + + +//============================================================================= +// TREE-ENUMERATION PROCEDURES +//============================================================================= + +#define ASSERT_AND_ABORT(f, sz) \ + if (!(f)) \ + { \ + ASSERT_MBOX(FALSE, sz); \ + cleanup( ); \ + return TREE_ABORT; \ + } + + +//================================================================= +// Methods for CountNodesTEP +// +int CountNodesTEP::callback( INode *node) +{ + INode *pnode = node; // Hungarian + + ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); + + if (::FUndesirableNode(pnode)) + { + // Mark as skippable + ::SetIndexOfINode(pnode, SmdExportClass::UNDESIRABLE_NODE_MARKER); + return TREE_CONTINUE; + } + + // Establish "node index"--just ascending ints + ::SetIndexOfINode(pnode, m_cNodes); + + m_cNodes++; + + return TREE_CONTINUE; +} + + +//================================================================= +// Methods for CollectNodesTEP +// +int CollectNodesTEP::callback(INode *node) +{ + INode *pnode = node; // Hungarian + + ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); + + if (::FNodeMarkedToSkip(pnode)) + return TREE_CONTINUE; + + // Get pre-stored "index" + int iNode = ::GetIndexOfINode(pnode); + ASSERT_MBOX(iNode >= 0 && iNode <= m_phec->m_imaxnodeMac-1, "Bogus iNode"); + + // Get name, store name in array + TSTR strNodeName(pnode->GetName()); + strcpy(m_phec->m_rgmaxnode[iNode].szNodeName, (char*)strNodeName); + + // Get Node's time-zero Transformation Matrices + m_phec->m_rgmaxnode[iNode].mat3NodeTM = pnode->GetNodeTM(0/*TimeValue*/); + m_phec->m_rgmaxnode[iNode].mat3ObjectTM = pnode->GetObjectTM(0/*TimeValue*/); + + // I'll calculate this later + m_phec->m_rgmaxnode[iNode].imaxnodeParent = SmdExportClass::UNDESIRABLE_NODE_MARKER; + + return TREE_CONTINUE; +} + + + + + + +//================================================================= +// Methods for DumpNodesTEP +// +int DumpNodesTEP::callback(INode *pnode) +{ + ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); + + if (::FNodeMarkedToSkip(pnode)) + return TREE_CONTINUE; + + // Get node's parent + INode *pnodeParent; + pnodeParent = pnode->GetParentNode(); + + // The model's root is a child of the real "scene root" + TSTR strNodeName(pnode->GetName()); + BOOL fNodeIsRoot = pnodeParent->IsRootNode( ); + + int iNode = ::GetIndexOfINode(pnode); + int iNodeParent = ::GetIndexOfINode(pnodeParent, !fNodeIsRoot/*fAssertPropExists*/); + + // Convenient time to cache this + m_phec->m_rgmaxnode[iNode].imaxnodeParent = fNodeIsRoot ? SmdExportClass::UNDESIRABLE_NODE_MARKER : iNodeParent; + + // Root node has no parent, thus no translation + if (fNodeIsRoot) + iNodeParent = -1; + + // check to see if the matrix isn't right handed + m_phec->m_rgmaxnode[iNode].isMirrored = DotProd( CrossProd( m_phec->m_rgmaxnode[iNode].mat3ObjectTM.GetRow(0).Normalize(), m_phec->m_rgmaxnode[iNode].mat3ObjectTM.GetRow(1).Normalize() ).Normalize(), m_phec->m_rgmaxnode[iNode].mat3ObjectTM.GetRow(2).Normalize() ) < 0; + + // Dump node description + fprintf(m_pfile, "%3d \"%s\" %3d\n", + iNode, + strNodeName, + iNodeParent ); + + return TREE_CONTINUE; +} + + + +//================================================================= +// Methods for DumpFrameRotationsTEP +// +int DumpFrameRotationsTEP::callback(INode *pnode) +{ + ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); + + if (::FNodeMarkedToSkip(pnode)) + return TREE_CONTINUE; + + int iNode = ::GetIndexOfINode(pnode); + + TSTR strNodeName(pnode->GetName()); + + // The model's root is a child of the real "scene root" + INode *pnodeParent = pnode->GetParentNode(); + BOOL fNodeIsRoot = pnodeParent->IsRootNode( ); + + // Get Node's "Local" Transformation Matrix + Matrix3 mat3NodeTM = pnode->GetNodeTM(m_tvToDump); + Matrix3 mat3ParentTM = pnodeParent->GetNodeTM(m_tvToDump); + mat3NodeTM.NoScale(); // Clear these out because they apparently + mat3ParentTM.NoScale(); // screw up the following calculation. + Matrix3 mat3NodeLocalTM = mat3NodeTM * Inverse(mat3ParentTM); + Point3 rowTrans = mat3NodeLocalTM.GetTrans(); + + // check to see if the parent bone was mirrored. If so, mirror invert this bones position + if (m_phec->m_rgmaxnode[iNode].imaxnodeParent >= 0 && m_phec->m_rgmaxnode[m_phec->m_rgmaxnode[iNode].imaxnodeParent].isMirrored) + { + rowTrans = rowTrans * -1.0f; + } + + // Get the rotation (via decomposition into "affine parts", then quaternion-to-Euler) + // Apparently the order of rotations returned by QuatToEuler() is X, then Y, then Z. + AffineParts affparts; + float rgflXYZRotations[3]; + + decomp_affine(mat3NodeLocalTM, &affparts); + QuatToEuler(affparts.q, rgflXYZRotations); + + float xRot = rgflXYZRotations[0]; // in radians + float yRot = rgflXYZRotations[1]; // in radians + float zRot = rgflXYZRotations[2]; // in radians + + // Get rotations in the -2pi...2pi range + xRot = ::FlReduceRotation(xRot); + yRot = ::FlReduceRotation(yRot); + zRot = ::FlReduceRotation(zRot); + + // Print rotations + fprintf(m_pfile, "%3d %f %f %f %f %f %f\n", + // Node:%-15s Rotation (x,y,z)\n", + iNode, rowTrans.x, rowTrans.y, rowTrans.z, xRot, yRot, zRot); + + return TREE_CONTINUE; +} + + + +//================================================================= +// Methods for DumpModelTEP +// +Modifier *FindPhysiqueModifier (INode *nodePtr) +{ + // Get object from node. Abort if no object. + Object *ObjectPtr = nodePtr->GetObjectRef(); + if (!ObjectPtr) return NULL; + + // Is derived object ? + if (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID) + { + // Yes -> Cast. + IDerivedObject *DerivedObjectPtr = static_cast(ObjectPtr); + + // Iterate over all entries of the modifier stack. + int ModStackIndex = 0; + while (ModStackIndex < DerivedObjectPtr->NumModifiers()) + { + // Get current modifier. + Modifier *ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex); + + // Is this Physique ? + if (ModifierPtr->ClassID() == Class_ID( PHYSIQUE_CLASS_ID_A, PHYSIQUE_CLASS_ID_B) ) + { + // Yes -> Exit. + return ModifierPtr; + } + // Next modifier stack entry. + ModStackIndex++; + } + } + // Not found. + return NULL; +} + +Modifier *FindBonesProModifier (INode *nodePtr) +{ + // Get object from node. Abort if no object. + Object *ObjectPtr = nodePtr->GetObjectRef(); + if (!ObjectPtr) return NULL; + + // Is derived object ? + if (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID) + { + // Yes -> Cast. + IDerivedObject *DerivedObjectPtr = static_cast(ObjectPtr); + + // Iterate over all entries of the modifier stack. + int ModStackIndex = 0; + while (ModStackIndex < DerivedObjectPtr->NumModifiers()) + { + // Get current modifier. + Modifier *ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex); + + // Is this Bones Pro OSM? + if (ModifierPtr->ClassID() == BP_CLASS_ID_OSM ) + { + // Yes -> Exit. + return ModifierPtr; + } + // Is this Bones Pro WSM? + if (ModifierPtr->ClassID() == BP_CLASS_ID_WSM ) + { + // Yes -> Exit. + return ModifierPtr; + } + // Next modifier stack entry. + ModStackIndex++; + } + } + // Not found. + return NULL; +} + +// #define DEBUG_MESH_DUMP + +//================================================================= +// Methods for DumpModelTEP +// +int DumpModelTEP::callback(INode *pnode) +{ + Object* pobj; + int fHasMat = TRUE; + + // clear physique export parameters + m_mcExport = NULL; + m_phyExport = NULL; + m_phyMod = NULL; + m_bonesProMod = NULL; + + ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); + + if (::FNodeMarkedToSkip(pnode)) + return TREE_CONTINUE; + + // Actually, if it's not selected, skip it! + //if (!pnode->Selected()) + // return TRUE; + + int iNode = ::GetIndexOfINode(pnode); + TSTR strNodeName(pnode->GetName()); + + // The Footsteps node apparently MUST have a dummy mesh attached! Ignore it explicitly. + if (FStrEq((char*)strNodeName, "Bip01 Footsteps")) + return TREE_CONTINUE; + + // Helper nodes don't have meshes + pobj = pnode->GetObjectRef(); + if (pobj->SuperClassID() == HELPER_CLASS_ID) + return TREE_CONTINUE; + + // The model's root is a child of the real "scene root" + INode *pnodeParent = pnode->GetParentNode(); + BOOL fNodeIsRoot = pnodeParent->IsRootNode( ); + + // Get node's material: should be a multi/sub (if it has a material at all) + Mtl *pmtlNode = pnode->GetMtl(); + if (pmtlNode == NULL) + { + return TREE_CONTINUE; + fHasMat = FALSE; + } + else if (!(pmtlNode->ClassID() == Class_ID(MULTI_CLASS_ID, 0) && pmtlNode->IsMultiMtl())) + { + // sprintf(st_szDBG, "ERROR--Material on node %s isn't a Multi/Sub-Object", (char*)strNodeName); + // ASSERT_AND_ABORT(FALSE, st_szDBG); + // fHasMat = FALSE; + } + + // Get Node's object, convert to a triangle-mesh object, so I can access the Faces + ObjectState os = pnode->EvalWorldState(m_tvToDump); + pobj = os.obj; + TriObject *ptriobj; + BOOL fConvertedToTriObject = + pobj->CanConvertToType(triObjectClassID) && + (ptriobj = (TriObject*)pobj->ConvertToType(m_tvToDump, triObjectClassID)) != NULL; + if (!fConvertedToTriObject) + return TREE_CONTINUE; + Mesh *pmesh = &ptriobj->mesh; + + // Shouldn't have gotten this far if it's a helper object + if (pobj->SuperClassID() == HELPER_CLASS_ID) + { + sprintf(st_szDBG, "ERROR--Helper node %s has an attached mesh, and it shouldn't.", (char*)strNodeName); + ASSERT_AND_ABORT(FALSE, st_szDBG); + } + + // Ensure that the vertex normals are up-to-date + pmesh->buildNormals(); + + // We want the vertex coordinates in World-space, not object-space + Matrix3 mat3ObjectTM = pnode->GetObjectTM(m_tvToDump); + + + // initialize physique export parameters + m_phyMod = FindPhysiqueModifier(pnode); + if (m_phyMod) + { + // Physique Modifier exists for given Node + m_phyExport = (IPhysiqueExport *)m_phyMod->GetInterface(I_PHYINTERFACE); + + if (m_phyExport) + { + // create a ModContext Export Interface for the specific node of the Physique Modifier + m_mcExport = (IPhyContextExport *)m_phyExport->GetContextInterface(pnode); + + if (m_mcExport) + { + // convert all vertices to Rigid + m_mcExport->ConvertToRigid(TRUE); + } + } + } + + // initialize bones pro export parameters + m_wa = NULL; + m_bonesProMod = FindBonesProModifier(pnode); + if (m_bonesProMod) + { + m_bonesProMod->SetProperty( BP_PROPID_GET_WEIGHTS, &m_wa ); + } + + // Dump the triangle face info + int cFaces = pmesh->getNumFaces(); + for (int iFace = 0; iFace < cFaces; iFace++) + { + Face* pface = &pmesh->faces[iFace]; + TVFace* ptvface = (pmesh->tvFace) ? &pmesh->tvFace[iFace] : NULL; + DWORD smGroupFace = pface->getSmGroup(); + + // Get face's 3 indexes into the Mesh's vertex array(s). + DWORD iVertex0 = pface->getVert(0); + DWORD iVertex1 = pface->getVert(1); + DWORD iVertex2 = pface->getVert(2); + ASSERT_AND_ABORT((int)iVertex0 < pmesh->getNumVerts(), "Bogus Vertex 0 index"); + ASSERT_AND_ABORT((int)iVertex1 < pmesh->getNumVerts(), "Bogus Vertex 1 index"); + ASSERT_AND_ABORT((int)iVertex2 < pmesh->getNumVerts(), "Bogus Vertex 2 index"); + + // Get the 3 Vertex's for this face + Point3 pt3Vertex0 = pmesh->getVert(iVertex0); + Point3 pt3Vertex1 = pmesh->getVert(iVertex1); + Point3 pt3Vertex2 = pmesh->getVert(iVertex2); + + // Get the 3 RVertex's for this face + // NOTE: I'm using getRVertPtr instead of getRVert to work around a 3DSMax bug + RVertex *prvertex0 = pmesh->getRVertPtr(iVertex0); + RVertex *prvertex1 = pmesh->getRVertPtr(iVertex1); + RVertex *prvertex2 = pmesh->getRVertPtr(iVertex2); + + // Find appropriate normals for each RVertex + // A vertex can be part of multiple faces, so the "smoothing group" + // is used to locate the normal for this face's use of the vertex. + Point3 pt3Vertex0Normal; + Point3 pt3Vertex1Normal; + Point3 pt3Vertex2Normal; + if (smGroupFace) + { + pt3Vertex0Normal = Pt3GetRVertexNormal(prvertex0, smGroupFace); + pt3Vertex1Normal = Pt3GetRVertexNormal(prvertex1, smGroupFace); + pt3Vertex2Normal = Pt3GetRVertexNormal(prvertex2, smGroupFace); + } + else + { + pt3Vertex0Normal = pmesh->getFaceNormal( iFace ); + pt3Vertex1Normal = pmesh->getFaceNormal( iFace ); + pt3Vertex2Normal = pmesh->getFaceNormal( iFace ); + } + ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus orig normal 0" ); + ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus orig normal 1" ); + ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus orig normal 2" ); + + // Get Face's sub-material from node's material, to get the bitmap name. + // And no, there isn't a simpler way to get the bitmap name, you have to + // dig down through all these levels. + TCHAR szBitmapName[256] = "null.bmp"; + if (fHasMat) + { + Texmap *ptexmap = NULL; + MtlID mtlidFace = pface->getMatID(); + if (pmtlNode->IsMultiMtl()) + { + if (mtlidFace >= pmtlNode->NumSubMtls()) + { + sprintf(st_szDBG, "ERROR--Bogus sub-material index %d in node %s; highest valid index is %d", + mtlidFace, (char*)strNodeName, pmtlNode->NumSubMtls()-1); + // ASSERT_AND_ABORT(FALSE, st_szDBG); + mtlidFace = 0; + } + Mtl *pmtlFace = pmtlNode->GetSubMtl(mtlidFace); + ASSERT_AND_ABORT(pmtlFace != NULL, "NULL Sub-material returned"); + + /* + if ((pmtlFace->ClassID() == Class_ID(MULTI_CLASS_ID, 0) && pmtlFace->IsMultiMtl())) + { + // it's a sub-sub material. Gads. + pmtlFace = pmtlFace->GetSubMtl(mtlidFace); + ASSERT_AND_ABORT(pmtlFace != NULL, "NULL Sub-material returned"); + } + */ + + if (!(pmtlFace->ClassID() == Class_ID(DMTL_CLASS_ID, 0))) + { + + sprintf(st_szDBG, + "ERROR--Sub-material with index %d (used in node %s) isn't a 'default/standard' material [%x].", + mtlidFace, (char*)strNodeName, pmtlFace->ClassID()); + ASSERT_AND_ABORT(FALSE, st_szDBG); + } + StdMat *pstdmtlFace = (StdMat*)pmtlFace; + ptexmap = pstdmtlFace->GetSubTexmap(ID_DI); + } + else + { + ptexmap = pmtlNode->GetActiveTexmap(); + } + + // ASSERT_AND_ABORT(ptexmap != NULL, "NULL diffuse texture") + if (ptexmap != NULL) + { + if (!(ptexmap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0))) + { + sprintf(st_szDBG, + "ERROR--Sub-material with index %d (used in node %s) doesn't have a bitmap as its diffuse texture.", + mtlidFace, (char*)strNodeName); + ASSERT_AND_ABORT(FALSE, st_szDBG); + } + BitmapTex *pbmptex = (BitmapTex*)ptexmap; + strcpy(szBitmapName, pbmptex->GetMapName()); + TSTR strPath, strFile; + SplitPathFile(TSTR(szBitmapName), &strPath, &strFile); + strcpy(szBitmapName,strFile); + } + } + + UVVert UVvertex0( 0, 0, 0 ); + UVVert UVvertex1( 1, 0, 0 ); + UVVert UVvertex2( 0, 1, 0 ); + + // All faces must have textures assigned to them + if (ptvface && (pface->flags & HAS_TVERTS)) + { + // Get TVface's 3 indexes into the Mesh's TVertex array(s). + DWORD iTVertex0 = ptvface->getTVert(0); + DWORD iTVertex1 = ptvface->getTVert(1); + DWORD iTVertex2 = ptvface->getTVert(2); + ASSERT_AND_ABORT((int)iTVertex0 < pmesh->getNumTVerts(), "Bogus TVertex 0 index"); + ASSERT_AND_ABORT((int)iTVertex1 < pmesh->getNumTVerts(), "Bogus TVertex 1 index"); + ASSERT_AND_ABORT((int)iTVertex2 < pmesh->getNumTVerts(), "Bogus TVertex 2 index"); + + // Get the 3 TVertex's for this TVFace + // NOTE: I'm using getRVertPtr instead of getRVert to work around a 3DSMax bug + UVvertex0 = pmesh->getTVert(iTVertex0); + UVvertex1 = pmesh->getTVert(iTVertex1); + UVvertex2 = pmesh->getTVert(iTVertex2); + } + else + { + //sprintf(st_szDBG, "ERROR--Node %s has a textureless face. All faces must have an applied texture.", (char*)strNodeName); + //ASSERT_AND_ABORT(FALSE, st_szDBG); + } + + /* + const char *szExpectedExtension = ".bmp"; + if (stricmp(szBitmapName+strlen(szBitmapName)-strlen(szExpectedExtension), szExpectedExtension) != 0) + { + sprintf(st_szDBG, "Node %s uses %s, which is not a %s file", (char*)strNodeName, szBitmapName, szExpectedExtension); + ASSERT_AND_ABORT(FALSE, st_szDBG); + } + */ + + // Determine owning bones for the vertices. + int iNodeV0, iNodeV1, iNodeV2; + // Simple 3dsMax model: the vertices are owned by the object, and hence the node + iNodeV0 = iNode; + iNodeV1 = iNode; + iNodeV2 = iNode; + + // Rotate the face vertices out of object-space, and into world-space space + Point3 v0 = pt3Vertex0 * mat3ObjectTM; + Point3 v1 = pt3Vertex1 * mat3ObjectTM; + Point3 v2 = pt3Vertex2 * mat3ObjectTM; + + + Matrix3 mat3ObjectNTM = mat3ObjectTM; + mat3ObjectNTM.NoScale( ); + ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus pre normal 0" ); + pt3Vertex0Normal = VectorTransform(mat3ObjectNTM, pt3Vertex0Normal); + ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus post normal 0" ); + ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus pre normal 1" ); + pt3Vertex1Normal = VectorTransform(mat3ObjectNTM, pt3Vertex1Normal); + ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus post normal 1" ); + ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus pre normal 2" ); + pt3Vertex2Normal = VectorTransform(mat3ObjectNTM, pt3Vertex2Normal); + ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus post normal 2" ); + + // Finally dump the bitmap name and 3 lines of face info + fprintf(m_pfile, "%s\n", szBitmapName); + fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f", + iNodeV0, v0.x, v0.y, v0.z, + pt3Vertex0Normal.x, pt3Vertex0Normal.y, pt3Vertex0Normal.z, + UVvertex0.x, UVvertex0.y); + DumpWeights( iVertex0 ); + fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f", + iNodeV1, v1.x, v1.y, v1.z, + pt3Vertex1Normal.x, pt3Vertex1Normal.y, pt3Vertex1Normal.z, + UVvertex1.x, UVvertex1.y); + DumpWeights( iVertex1 ); + fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f", + iNodeV2, v2.x, v2.y, v2.z, + pt3Vertex2Normal.x, pt3Vertex2Normal.y, pt3Vertex2Normal.z, + UVvertex2.x, UVvertex2.y); + DumpWeights( iVertex2 ); + } + + cleanup( ); + return TREE_CONTINUE; +} + + +#define MAX_BLEND_WEIGHTS 8 + +static struct { + int iNode; + float flWeight; +} aWeights[MAX_BLEND_WEIGHTS+1]; + +int AddWeight( int iCount, int iNode, float flWeight ) +{ + if (flWeight < 0.001) + return iCount; + + for (int i = 0; i < iCount; i++) + { + if (aWeights[i].flWeight < flWeight) + { + for (int j = iCount; j > i; j--) + { + aWeights[j] = aWeights[j-1]; + } + break; + } + } + aWeights[i].iNode = iNode; + aWeights[i].flWeight = flWeight; + + iCount++; + if (iCount > MAX_BLEND_WEIGHTS) + iCount = MAX_BLEND_WEIGHTS; + + return iCount; +} + + +void DumpModelTEP::DumpWeights(int iVertex) +{ + if (m_mcExport) + { + IPhyVertexExport *vtxExport = m_mcExport->GetVertexInterface(iVertex); + + if (vtxExport) + { + if (vtxExport->GetVertexType() & BLENDED_TYPE) + { + IPhyBlendedRigidVertex *pBlend = ((IPhyBlendedRigidVertex *)vtxExport); + int iCount = 0; + + for (int i = 0; i < pBlend->GetNumberNodes(); i++) + { + iCount = AddWeight( iCount, GetIndexOfINode( pBlend->GetNode( i ) ), pBlend->GetWeight( i ) ); + } + + fprintf(m_pfile, " %2d ", iCount ); + for (i = 0; i < iCount; i++) + { + fprintf(m_pfile, " %2d %5.3f ", aWeights[i].iNode, aWeights[i].flWeight ); + } + } + else + { + INode *Bone = ((IPhyRigidVertex *)vtxExport)->GetNode(); + + fprintf(m_pfile, " 1 %2d 1.000", GetIndexOfINode(Bone) ); + } + m_mcExport->ReleaseVertexInterface(vtxExport); + } + } + else if (m_wa != NULL) + { + int iCount = 0; + + for ( int iBone = 0; iBone < m_wa->nb; iBone++) + { + if (m_wa->w[iVertex * m_wa->nb + iBone] > 0.0) + { + BonesPro_Bone bone; + bone.t = BP_TIME_ATTACHED; + bone.index = iBone; + m_bonesProMod->SetProperty( BP_PROPID_GET_BONE_STAT, &bone ); + if (bone.node != NULL) + { + iCount = AddWeight( iCount, GetIndexOfINode( bone.node ), m_wa->w[iVertex * m_wa->nb + iBone] ); + } + } + } + + fprintf(m_pfile, " %2d ", iCount ); + for (int i = 0; i < iCount; i++) + { + fprintf(m_pfile, " %2d %5.3f ", aWeights[i].iNode, aWeights[i].flWeight ); + } + } + + fprintf(m_pfile, "\n" ); + fflush( m_pfile ); +} + +void DumpModelTEP::cleanup(void) +{ + if (m_phyMod && m_phyExport) + { + if (m_mcExport) + { + m_phyExport->ReleaseContextInterface(m_mcExport); + m_mcExport = NULL; + } + m_phyMod->ReleaseInterface(I_PHYINTERFACE, m_phyExport); + m_phyExport = NULL; + m_phyMod = NULL; + } +} + + +Point3 DumpModelTEP::Pt3GetRVertexNormal(RVertex *prvertex, DWORD smGroupFace) +{ + // Lookup the appropriate vertex normal, based on smoothing group. + int cNormals = prvertex->rFlags & NORCT_MASK; + + ASSERT_MBOX((cNormals == 1 && prvertex->ern == NULL) || + (cNormals > 1 && prvertex->ern != NULL), "BOGUS RVERTEX"); + + if (cNormals == 1) + return prvertex->rn.getNormal(); + else + { + for (int irn = 0; irn < cNormals; irn++) + if (prvertex->ern[irn].getSmGroup() & smGroupFace) + break; + + if (irn >= cNormals) + { + irn = 0; + // ASSERT_MBOX(irn < cNormals, "unknown smoothing group\n"); + } + return prvertex->ern[irn].getNormal(); + } +} + + + + + +//=========================================================== +// Dialog proc for export options +// +static BOOL CALLBACK ExportOptionsDlgProc( + HWND hDlg, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + static SmdExportClass *pexp; + switch (message) + { + case WM_INITDIALOG: + pexp = (SmdExportClass*) lParam; + CheckRadioButton(hDlg, IDC_CHECK_SKELETAL, IDC_CHECK_REFFRAME, IDC_CHECK_SKELETAL); + SetFocus(GetDlgItem(hDlg,IDOK)); + return FALSE; + case WM_DESTROY: + return FALSE; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + pexp->m_fReferenceFrame = IsDlgButtonChecked(hDlg, IDC_CHECK_REFFRAME); + EndDialog(hDlg, 1); // 1 indicates "ok to export" + return TRUE; + case IDCANCEL: // 0 indicates "cancel export" + EndDialog(hDlg, 0); + return TRUE; + case IDC_CHECK_SKELETAL: + case IDC_CHECK_REFFRAME: + CheckRadioButton(hDlg, IDC_CHECK_SKELETAL, IDC_CHECK_REFFRAME, LOWORD(wParam)); + break; + } + } + return FALSE; +} + + + +//======================================================================== +// Utility functions for getting/setting the personal "node index" property. +// NOTE: I'm storing a string-property because I hit a 3DSMax bug in v1.2 when I +// NOTE: tried using an integer property. +// FURTHER NOTE: those properties seem to change randomly sometimes, so I'm +// implementing my own. + +typedef struct +{ + char szNodeName[SmdExportClass::MAX_NAME_CHARS]; + int iNode; +} NAMEMAP; +const int MAX_NAMEMAP = 512; +static NAMEMAP g_rgnm[MAX_NAMEMAP]; + +int GetIndexOfINode(INode *pnode, BOOL fAssertPropExists) +{ + TSTR strNodeName(pnode->GetName()); + for (int inm = 0; inm < g_inmMac; inm++) + { + if (FStrEq(g_rgnm[inm].szNodeName, (char*)strNodeName)) + { + return g_rgnm[inm].iNode; + } + } + + if (fAssertPropExists) + ASSERT_MBOX(FALSE, "No NODEINDEXSTR property"); + return -7777; +} + + +void SetIndexOfINode(INode *pnode, int inode) +{ + TSTR strNodeName(pnode->GetName()); + NAMEMAP *pnm; + for (int inm = 0; inm < g_inmMac; inm++) + if (FStrEq(g_rgnm[inm].szNodeName, (char*)strNodeName)) + break; + if (inm < g_inmMac) + pnm = &g_rgnm[inm]; + else + { + ASSERT_MBOX(g_inmMac < MAX_NAMEMAP, "NAMEMAP is full"); + pnm = &g_rgnm[g_inmMac++]; + strcpy(pnm->szNodeName, (char*)strNodeName); + } + pnm->iNode = inode; +} + + +//============================================================= +// Returns TRUE if a node should be ignored during tree traversal. +// +BOOL FUndesirableNode(INode *pnode) +{ + // Get Node's underlying object, and object class name + Object *pobj = pnode->GetObjectRef(); + + // Don't care about lights, dummies, and cameras + if (pobj->SuperClassID() == CAMERA_CLASS_ID) + return TRUE; + if (pobj->SuperClassID() == LIGHT_CLASS_ID) + return TRUE; + + return FALSE; +} + + +//============================================================= +// Returns TRUE if a node has been marked as skippable +// +BOOL FNodeMarkedToSkip(INode *pnode) +{ + return (::GetIndexOfINode(pnode) == SmdExportClass::UNDESIRABLE_NODE_MARKER); +} + + +//============================================================= +// Reduces a rotation to within the -2PI..2PI range. +// +static float FlReduceRotation(float fl) +{ + while (fl >= TWOPI) + fl -= TWOPI; + while (fl <= -TWOPI) + fl += TWOPI; + return fl; +} diff --git a/mp/src/utils/smdlexp/smedefs.h b/mp/src/utils/smdlexp/smedefs.h index 7211e1e1..4b09d5dc 100644 --- a/mp/src/utils/smdlexp/smedefs.h +++ b/mp/src/utils/smdlexp/smedefs.h @@ -1,178 +1,178 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -//=================================================================== -// Useful macros -// -#define CONSTRUCTOR -#define DESTRUCTOR - -#define EXPORT_THIS __declspec(dllexport) - -#define DEFAULT_EXT _T("smd") - -#define FStrEq(sz1, sz2) (strcmp((sz1), (sz2)) == 0) - - -//=================================================================== -// Class that implements the scene-export. -// -class SmdExportClass : public SceneExport -{ - friend BOOL CALLBACK ExportOptionsDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); - friend class DumpModelTEP; - friend class DumpDeformsTEP; - -public: - CONSTRUCTOR SmdExportClass (void); - DESTRUCTOR ~SmdExportClass (void); - - // Required by classes derived from SceneExport - virtual int ExtCount (void) { return 1; } - virtual const TCHAR* Ext (int i) { return DEFAULT_EXT; } - virtual const TCHAR* LongDesc (void) { return _T("Valve Skeletal Model Exporter for 3D Studio Max"); } - virtual const TCHAR* ShortDesc (void) { return _T("Valve SMD"); } - virtual const TCHAR* AuthorName (void) { return _T("Valve, LLC"); } - virtual const TCHAR* CopyrightMessage(void) { return _T("Copyright (c) 1998, Valve LLC"); } - virtual const TCHAR* OtherMessage1 (void) { return _T(""); } - virtual const TCHAR* OtherMessage2 (void) { return _T(""); } - virtual unsigned int Version (void) { return 201; } - virtual void ShowAbout (HWND hWnd) { return; } - // virtual int DoExport (const TCHAR *name, ExpInterface *ei, Interface *i); - virtual int DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts=FALSE,DWORD options=0); // Export file - - // Integer constants for this class - enum - { - MAX_NAME_CHARS = 70, - UNDESIRABLE_NODE_MARKER = -7777 - }; - - // For keeping info about each (non-ignored) 3dsMax node in the tree - typedef struct - { - char szNodeName[MAX_NAME_CHARS]; // usefull for lookups - Matrix3 mat3NodeTM; // node's transformation matrix (at time zero) - Matrix3 mat3ObjectTM; // object-offset transformation matrix (at time zero) - int imaxnodeParent; // cached index of parent node - float xRotFirstFrame; // 1st frame's X rotation - float yRotFirstFrame; // 1st frame's Y rotation - float zRotFirstFrame; // 1st frame's Z rotation - bool isMirrored; - } MaxNode; - MaxNode *m_rgmaxnode; // array of nodes - long m_imaxnodeMac; // # of nodes - - // Animation metrics (gleaned from 3dsMax and cached for convenience) - Interval m_intervalOfAnimation; - TimeValue m_tvStart; - TimeValue m_tvEnd; - int m_tpf; // ticks-per-frame - -private: - BOOL CollectNodes (ExpInterface *expiface); - BOOL DumpBones (FILE *pFile, ExpInterface *pexpiface); - BOOL DumpRotations (FILE *pFile, ExpInterface *pexpiface); - BOOL DumpModel (FILE *pFile, ExpInterface *pexpiface); - BOOL DumpDeforms (FILE *pFile, ExpInterface *pexpiface); - - // Is this MAX file just the reference frame, or an animation? - // If TRUE, the "bones" and "mesh" files will be created. - // If FALSE, the "rots" file will be created. - BOOL m_fReferenceFrame; -}; - - -//=================================================================== -// Basically just a ClassFactory for communicating with 3DSMAX. -// -class SmdExportClassDesc : public ClassDesc -{ -public: - int IsPublic (void) { return TRUE; } - void * Create (BOOL loading=FALSE) { return new SmdExportClass; } - const TCHAR * ClassName (void) { return _T("SmdExport"); } - SClass_ID SuperClassID (void) { return SCENE_EXPORT_CLASS_ID; } - Class_ID ClassID (void) { return Class_ID(0x774a43fd, 0x794d2210); } - const TCHAR * Category (void) { return _T(""); } -}; - - -//=================================================================== -// Tree Enumeration Callback -// Just counts the nodes in the node tree -// -class CountNodesTEP : public ITreeEnumProc -{ -public: - virtual int callback(INode *node); - int m_cNodes; // running count of nodes -}; - - -//=================================================================== -// Tree Enumeration Callback -// Collects the nodes in the tree into the global array -// -class CollectNodesTEP : public ITreeEnumProc -{ -public: - virtual int callback(INode *node); - SmdExportClass *m_phec; -}; - - -//=================================================================== -// Tree Enumeration Callback -// Dumps the bone offsets to a file. -// -class DumpNodesTEP : public ITreeEnumProc -{ -public: - virtual int callback(INode *node); - FILE *m_pfile; // write to this file - SmdExportClass *m_phec; -}; - - -//=================================================================== -// Tree Enumeration Callback -// Dumps the per-frame bone rotations to a file. -// -class DumpFrameRotationsTEP : public ITreeEnumProc -{ -public: - virtual int callback(INode *node); - void cleanup(void); - FILE *m_pfile; // write to this file - TimeValue m_tvToDump; // dump snapshot at this frame time - SmdExportClass *m_phec; -}; - -//=================================================================== -// Tree Enumeration Callback -// Dumps the triangle meshes to a file. -// -class DumpModelTEP : public ITreeEnumProc -{ -public: - virtual int callback(INode *node); - void cleanup(void); - FILE *m_pfile; // write to this file - TimeValue m_tvToDump; // dump snapshot at this frame time - SmdExportClass *m_phec; - IPhyContextExport *m_mcExport; - IPhysiqueExport *m_phyExport; - Modifier *m_phyMod; - Modifier *m_bonesProMod; - BonesPro_WeightArray *m_wa; -private: - Point3 Pt3GetRVertexNormal(RVertex *prvertex, DWORD smGroupFace); - void DumpWeights( int iVertex ); -}; - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +//=================================================================== +// Useful macros +// +#define CONSTRUCTOR +#define DESTRUCTOR + +#define EXPORT_THIS __declspec(dllexport) + +#define DEFAULT_EXT _T("smd") + +#define FStrEq(sz1, sz2) (strcmp((sz1), (sz2)) == 0) + + +//=================================================================== +// Class that implements the scene-export. +// +class SmdExportClass : public SceneExport +{ + friend BOOL CALLBACK ExportOptionsDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + friend class DumpModelTEP; + friend class DumpDeformsTEP; + +public: + CONSTRUCTOR SmdExportClass (void); + DESTRUCTOR ~SmdExportClass (void); + + // Required by classes derived from SceneExport + virtual int ExtCount (void) { return 1; } + virtual const TCHAR* Ext (int i) { return DEFAULT_EXT; } + virtual const TCHAR* LongDesc (void) { return _T("Valve Skeletal Model Exporter for 3D Studio Max"); } + virtual const TCHAR* ShortDesc (void) { return _T("Valve SMD"); } + virtual const TCHAR* AuthorName (void) { return _T("Valve, LLC"); } + virtual const TCHAR* CopyrightMessage(void) { return _T("Copyright (c) 1998, Valve LLC"); } + virtual const TCHAR* OtherMessage1 (void) { return _T(""); } + virtual const TCHAR* OtherMessage2 (void) { return _T(""); } + virtual unsigned int Version (void) { return 201; } + virtual void ShowAbout (HWND hWnd) { return; } + // virtual int DoExport (const TCHAR *name, ExpInterface *ei, Interface *i); + virtual int DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts=FALSE,DWORD options=0); // Export file + + // Integer constants for this class + enum + { + MAX_NAME_CHARS = 70, + UNDESIRABLE_NODE_MARKER = -7777 + }; + + // For keeping info about each (non-ignored) 3dsMax node in the tree + typedef struct + { + char szNodeName[MAX_NAME_CHARS]; // usefull for lookups + Matrix3 mat3NodeTM; // node's transformation matrix (at time zero) + Matrix3 mat3ObjectTM; // object-offset transformation matrix (at time zero) + int imaxnodeParent; // cached index of parent node + float xRotFirstFrame; // 1st frame's X rotation + float yRotFirstFrame; // 1st frame's Y rotation + float zRotFirstFrame; // 1st frame's Z rotation + bool isMirrored; + } MaxNode; + MaxNode *m_rgmaxnode; // array of nodes + long m_imaxnodeMac; // # of nodes + + // Animation metrics (gleaned from 3dsMax and cached for convenience) + Interval m_intervalOfAnimation; + TimeValue m_tvStart; + TimeValue m_tvEnd; + int m_tpf; // ticks-per-frame + +private: + BOOL CollectNodes (ExpInterface *expiface); + BOOL DumpBones (FILE *pFile, ExpInterface *pexpiface); + BOOL DumpRotations (FILE *pFile, ExpInterface *pexpiface); + BOOL DumpModel (FILE *pFile, ExpInterface *pexpiface); + BOOL DumpDeforms (FILE *pFile, ExpInterface *pexpiface); + + // Is this MAX file just the reference frame, or an animation? + // If TRUE, the "bones" and "mesh" files will be created. + // If FALSE, the "rots" file will be created. + BOOL m_fReferenceFrame; +}; + + +//=================================================================== +// Basically just a ClassFactory for communicating with 3DSMAX. +// +class SmdExportClassDesc : public ClassDesc +{ +public: + int IsPublic (void) { return TRUE; } + void * Create (BOOL loading=FALSE) { return new SmdExportClass; } + const TCHAR * ClassName (void) { return _T("SmdExport"); } + SClass_ID SuperClassID (void) { return SCENE_EXPORT_CLASS_ID; } + Class_ID ClassID (void) { return Class_ID(0x774a43fd, 0x794d2210); } + const TCHAR * Category (void) { return _T(""); } +}; + + +//=================================================================== +// Tree Enumeration Callback +// Just counts the nodes in the node tree +// +class CountNodesTEP : public ITreeEnumProc +{ +public: + virtual int callback(INode *node); + int m_cNodes; // running count of nodes +}; + + +//=================================================================== +// Tree Enumeration Callback +// Collects the nodes in the tree into the global array +// +class CollectNodesTEP : public ITreeEnumProc +{ +public: + virtual int callback(INode *node); + SmdExportClass *m_phec; +}; + + +//=================================================================== +// Tree Enumeration Callback +// Dumps the bone offsets to a file. +// +class DumpNodesTEP : public ITreeEnumProc +{ +public: + virtual int callback(INode *node); + FILE *m_pfile; // write to this file + SmdExportClass *m_phec; +}; + + +//=================================================================== +// Tree Enumeration Callback +// Dumps the per-frame bone rotations to a file. +// +class DumpFrameRotationsTEP : public ITreeEnumProc +{ +public: + virtual int callback(INode *node); + void cleanup(void); + FILE *m_pfile; // write to this file + TimeValue m_tvToDump; // dump snapshot at this frame time + SmdExportClass *m_phec; +}; + +//=================================================================== +// Tree Enumeration Callback +// Dumps the triangle meshes to a file. +// +class DumpModelTEP : public ITreeEnumProc +{ +public: + virtual int callback(INode *node); + void cleanup(void); + FILE *m_pfile; // write to this file + TimeValue m_tvToDump; // dump snapshot at this frame time + SmdExportClass *m_phec; + IPhyContextExport *m_mcExport; + IPhysiqueExport *m_phyExport; + Modifier *m_phyMod; + Modifier *m_bonesProMod; + BonesPro_WeightArray *m_wa; +private: + Point3 Pt3GetRVertexNormal(RVertex *prvertex, DWORD smGroupFace); + void DumpWeights( int iVertex ); +}; + diff --git a/mp/src/utils/smdlexp/smexprc.h b/mp/src/utils/smdlexp/smexprc.h index d783fc67..e6482bf9 100644 --- a/mp/src/utils/smdlexp/smexprc.h +++ b/mp/src/utils/smdlexp/smexprc.h @@ -1,28 +1,28 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -//{{NO_DEPENDENCIES}} -// Microsoft Developer Studio generated include file. -// Used by smdlexp.rc -// -#define IDD_SMDLEXP_UI 101 -#define IDD_EXPORTOPTIONS 101 -#define IDC_CHECK_SKELETAL 1000 -#define IDC_CHECK_DEFORM 1001 -#define IDC_CHECK_REFFRAME 1002 -#define IDC_CHECK_PHYSIQUE 1003 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1006 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by smdlexp.rc +// +#define IDD_SMDLEXP_UI 101 +#define IDD_EXPORTOPTIONS 101 +#define IDC_CHECK_SKELETAL 1000 +#define IDC_CHECK_DEFORM 1001 +#define IDC_CHECK_REFFRAME 1002 +#define IDC_CHECK_PHYSIQUE 1003 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1006 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/mp/src/utils/tgadiff/tgadiff.cpp b/mp/src/utils/tgadiff/tgadiff.cpp index 6dbc3e3a..42654568 100644 --- a/mp/src/utils/tgadiff/tgadiff.cpp +++ b/mp/src/utils/tgadiff/tgadiff.cpp @@ -1,184 +1,184 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//===========================================================================// -#include -#include -#include -#include "bitmap/tgaloader.h" -#include "bitmap/tgawriter.h" -#include "tier1/utlbuffer.h" -#include "tier2/tier2.h" -#include "mathlib/mathlib.h" -#include "filesystem.h" - -void Usage( void ) -{ - printf( "Usage: tgadiff src1.tga src2.tga diff.tga\n" ); - exit( -1 ); -} - -int main( int argc, char **argv ) -{ - if( argc != 4 ) - { - Usage(); - } - - MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); - InitDefaultFileSystem(); - - char pCurrentDirectory[MAX_PATH]; - if ( _getcwd( pCurrentDirectory, sizeof(pCurrentDirectory) ) == NULL ) - { - fprintf( stderr, "Unable to get the current directory\n" ); - return -1; - } - Q_FixSlashes( pCurrentDirectory ); - Q_StripTrailingSlash( pCurrentDirectory ); - - char pBuf[3][MAX_PATH]; - const char *pFileName[3]; - for ( int i = 0; i < 3; ++i ) - { - if ( !Q_IsAbsolutePath( argv[i+1] ) ) - { - Q_snprintf( pBuf[i], sizeof(pBuf[i]), "%s\\%s", pCurrentDirectory, argv[i+1] ); - pFileName[i] = pBuf[i]; - } - else - { - pFileName[i] = argv[i+1]; - } - } - - int width1, height1; - ImageFormat imageFormat1; - float gamma1; - - CUtlBuffer buf1; - if ( !g_pFullFileSystem->ReadFile( pFileName[0], NULL, buf1 ) ) - { - fprintf( stderr, "%s not found\n", pFileName[0] ); - return -1; - } - - if( !TGALoader::GetInfo( buf1, &width1, &height1, &imageFormat1, &gamma1 ) ) - { - printf( "error loading %s\n", pFileName[0] ); - exit( -1 ); - } - - int width2, height2; - ImageFormat imageFormat2; - float gamma2; - - CUtlBuffer buf2; - if ( !g_pFullFileSystem->ReadFile( pFileName[1], NULL, buf2 ) ) - { - fprintf( stderr, "%s not found\n", pFileName[1] ); - return -1; - } - - if( !TGALoader::GetInfo( buf2, &width2, &height2, &imageFormat2, &gamma2 ) ) - { - printf( "error loading %s\n", pFileName[1] ); - exit( -1 ); - } - - if( width1 != width2 || height1 != height2 ) - { - printf( "image dimensions different (%dx%d!=%dx%d): can't do diff for %s\n", - width1, height1, width2, height2, pFileName[2] ); - exit( -1 ); - } -#if 0 - // have to allow for different formats for now due to *.txt file screwup. - if( imageFormat1 != imageFormat2 ) - { - printf( "image format different (%s!=%s). . can't do diff for %s\n", - ImageLoader::GetName( imageFormat1 ), ImageLoader::GetName( imageFormat2 ), pFileName[2] ); - exit( -1 ); - } -#endif - if( gamma1 != gamma2 ) - { - printf( "image gamma different (%f!=%f). . can't do diff for %s\n", gamma1, gamma2, pFileName[2] ); - exit( -1 ); - } - - unsigned char *pImage1Tmp = new unsigned char[ImageLoader::GetMemRequired( width1, height1, 1, imageFormat1, false )]; - unsigned char *pImage2Tmp = new unsigned char[ImageLoader::GetMemRequired( width2, height2, 1, imageFormat2, false )]; - - buf1.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); - if( !TGALoader::Load( pImage1Tmp, buf1, width1, height1, imageFormat1, 2.2f, false ) ) - { - printf( "error loading %s\n", pFileName[0] ); - exit( -1 ); - } - - buf2.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); - if( !TGALoader::Load( pImage2Tmp, buf2, width2, height2, imageFormat2, 2.2f, false ) ) - { - printf( "error loading %s\n", pFileName[1] ); - exit( -1 ); - } - - unsigned char *pImage1 = new unsigned char[ImageLoader::GetMemRequired( width1, height1, 1, IMAGE_FORMAT_ABGR8888, false )]; - unsigned char *pImage2 = new unsigned char[ImageLoader::GetMemRequired( width2, height2, 1, IMAGE_FORMAT_ABGR8888, false )]; - unsigned char *pDiff = new unsigned char[ImageLoader::GetMemRequired( width2, height2, 1, IMAGE_FORMAT_ABGR8888, false )]; - ImageLoader::ConvertImageFormat( pImage1Tmp, imageFormat1, pImage1, IMAGE_FORMAT_ABGR8888, width1, height1, 0, 0 ); - ImageLoader::ConvertImageFormat( pImage2Tmp, imageFormat2, pImage2, IMAGE_FORMAT_ABGR8888, width2, height2, 0, 0 ); - - int sizeInBytes = ImageLoader::SizeInBytes( IMAGE_FORMAT_ABGR8888 ); - bool isDifferent = false; - for( int i = 0; i < width1 * height1 * sizeInBytes; i++ ) - { - int d; - d = pImage2[i] - pImage1[i]; - pDiff[i] = d > 0 ? d : -d; - if( d != 0 ) - { - isDifferent = true; - } - } - - if( !isDifferent ) - { - printf( "Files are the same %s %s : not generating %s\n", pFileName[0], pFileName[1], pFileName[2] ); - exit( -1 ); - } - else - { - printf( "Generating diff: %s!\n", pFileName[2] ); - } - - ImageFormat dstImageFormat; - // get rid of this until we get the formats matching -// if( sizeInBytes == 3 ) -// { -// dstImageFormat = IMAGE_FORMAT_RGB888; -// } -// else - { - dstImageFormat = IMAGE_FORMAT_RGBA8888; - } - - CUtlBuffer outBuffer; - if ( !TGAWriter::WriteToBuffer( pDiff, outBuffer, width1, height1, dstImageFormat, dstImageFormat ) ) - { - printf( "error writing %s to buffer\n", pFileName[2] ); - exit( -1 ); - } - - if ( !g_pFullFileSystem->WriteFile( pFileName[2], NULL, outBuffer ) ) - { - fprintf( stderr, "unable to write %s\n", pFileName[2] ); - return -1; - } - - return 0; -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#include +#include +#include +#include "bitmap/tgaloader.h" +#include "bitmap/tgawriter.h" +#include "tier1/utlbuffer.h" +#include "tier2/tier2.h" +#include "mathlib/mathlib.h" +#include "filesystem.h" + +void Usage( void ) +{ + printf( "Usage: tgadiff src1.tga src2.tga diff.tga\n" ); + exit( -1 ); +} + +int main( int argc, char **argv ) +{ + if( argc != 4 ) + { + Usage(); + } + + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); + InitDefaultFileSystem(); + + char pCurrentDirectory[MAX_PATH]; + if ( _getcwd( pCurrentDirectory, sizeof(pCurrentDirectory) ) == NULL ) + { + fprintf( stderr, "Unable to get the current directory\n" ); + return -1; + } + Q_FixSlashes( pCurrentDirectory ); + Q_StripTrailingSlash( pCurrentDirectory ); + + char pBuf[3][MAX_PATH]; + const char *pFileName[3]; + for ( int i = 0; i < 3; ++i ) + { + if ( !Q_IsAbsolutePath( argv[i+1] ) ) + { + Q_snprintf( pBuf[i], sizeof(pBuf[i]), "%s\\%s", pCurrentDirectory, argv[i+1] ); + pFileName[i] = pBuf[i]; + } + else + { + pFileName[i] = argv[i+1]; + } + } + + int width1, height1; + ImageFormat imageFormat1; + float gamma1; + + CUtlBuffer buf1; + if ( !g_pFullFileSystem->ReadFile( pFileName[0], NULL, buf1 ) ) + { + fprintf( stderr, "%s not found\n", pFileName[0] ); + return -1; + } + + if( !TGALoader::GetInfo( buf1, &width1, &height1, &imageFormat1, &gamma1 ) ) + { + printf( "error loading %s\n", pFileName[0] ); + exit( -1 ); + } + + int width2, height2; + ImageFormat imageFormat2; + float gamma2; + + CUtlBuffer buf2; + if ( !g_pFullFileSystem->ReadFile( pFileName[1], NULL, buf2 ) ) + { + fprintf( stderr, "%s not found\n", pFileName[1] ); + return -1; + } + + if( !TGALoader::GetInfo( buf2, &width2, &height2, &imageFormat2, &gamma2 ) ) + { + printf( "error loading %s\n", pFileName[1] ); + exit( -1 ); + } + + if( width1 != width2 || height1 != height2 ) + { + printf( "image dimensions different (%dx%d!=%dx%d): can't do diff for %s\n", + width1, height1, width2, height2, pFileName[2] ); + exit( -1 ); + } +#if 0 + // have to allow for different formats for now due to *.txt file screwup. + if( imageFormat1 != imageFormat2 ) + { + printf( "image format different (%s!=%s). . can't do diff for %s\n", + ImageLoader::GetName( imageFormat1 ), ImageLoader::GetName( imageFormat2 ), pFileName[2] ); + exit( -1 ); + } +#endif + if( gamma1 != gamma2 ) + { + printf( "image gamma different (%f!=%f). . can't do diff for %s\n", gamma1, gamma2, pFileName[2] ); + exit( -1 ); + } + + unsigned char *pImage1Tmp = new unsigned char[ImageLoader::GetMemRequired( width1, height1, 1, imageFormat1, false )]; + unsigned char *pImage2Tmp = new unsigned char[ImageLoader::GetMemRequired( width2, height2, 1, imageFormat2, false )]; + + buf1.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + if( !TGALoader::Load( pImage1Tmp, buf1, width1, height1, imageFormat1, 2.2f, false ) ) + { + printf( "error loading %s\n", pFileName[0] ); + exit( -1 ); + } + + buf2.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + if( !TGALoader::Load( pImage2Tmp, buf2, width2, height2, imageFormat2, 2.2f, false ) ) + { + printf( "error loading %s\n", pFileName[1] ); + exit( -1 ); + } + + unsigned char *pImage1 = new unsigned char[ImageLoader::GetMemRequired( width1, height1, 1, IMAGE_FORMAT_ABGR8888, false )]; + unsigned char *pImage2 = new unsigned char[ImageLoader::GetMemRequired( width2, height2, 1, IMAGE_FORMAT_ABGR8888, false )]; + unsigned char *pDiff = new unsigned char[ImageLoader::GetMemRequired( width2, height2, 1, IMAGE_FORMAT_ABGR8888, false )]; + ImageLoader::ConvertImageFormat( pImage1Tmp, imageFormat1, pImage1, IMAGE_FORMAT_ABGR8888, width1, height1, 0, 0 ); + ImageLoader::ConvertImageFormat( pImage2Tmp, imageFormat2, pImage2, IMAGE_FORMAT_ABGR8888, width2, height2, 0, 0 ); + + int sizeInBytes = ImageLoader::SizeInBytes( IMAGE_FORMAT_ABGR8888 ); + bool isDifferent = false; + for( int i = 0; i < width1 * height1 * sizeInBytes; i++ ) + { + int d; + d = pImage2[i] - pImage1[i]; + pDiff[i] = d > 0 ? d : -d; + if( d != 0 ) + { + isDifferent = true; + } + } + + if( !isDifferent ) + { + printf( "Files are the same %s %s : not generating %s\n", pFileName[0], pFileName[1], pFileName[2] ); + exit( -1 ); + } + else + { + printf( "Generating diff: %s!\n", pFileName[2] ); + } + + ImageFormat dstImageFormat; + // get rid of this until we get the formats matching +// if( sizeInBytes == 3 ) +// { +// dstImageFormat = IMAGE_FORMAT_RGB888; +// } +// else + { + dstImageFormat = IMAGE_FORMAT_RGBA8888; + } + + CUtlBuffer outBuffer; + if ( !TGAWriter::WriteToBuffer( pDiff, outBuffer, width1, height1, dstImageFormat, dstImageFormat ) ) + { + printf( "error writing %s to buffer\n", pFileName[2] ); + exit( -1 ); + } + + if ( !g_pFullFileSystem->WriteFile( pFileName[2], NULL, outBuffer ) ) + { + fprintf( stderr, "unable to write %s\n", pFileName[2] ); + return -1; + } + + return 0; +} diff --git a/mp/src/utils/tgadiff/tgadiff.vpc b/mp/src/utils/tgadiff/tgadiff.vpc index 1bfc6b1f..16c63b4a 100644 --- a/mp/src/utils/tgadiff/tgadiff.vpc +++ b/mp/src/utils/tgadiff/tgadiff.vpc @@ -1,25 +1,25 @@ -//----------------------------------------------------------------------------- -// TGADIFF.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" - -$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" - -$Project "Tgadiff" -{ - $Folder "Source Files" - { - $File "tgadiff.cpp" - } - - $Folder "Link Libraries" - { - $Lib bitmap - $Lib mathlib - $Lib tier2 - } -} +//----------------------------------------------------------------------------- +// TGADIFF.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Project "Tgadiff" +{ + $Folder "Source Files" + { + $File "tgadiff.cpp" + } + + $Folder "Link Libraries" + { + $Lib bitmap + $Lib mathlib + $Lib tier2 + } +} diff --git a/mp/src/utils/vbsp/boundbox.cpp b/mp/src/utils/vbsp/boundbox.cpp index d0366cfc..ecb8dfa0 100644 --- a/mp/src/utils/vbsp/boundbox.cpp +++ b/mp/src/utils/vbsp/boundbox.cpp @@ -1,285 +1,285 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "vbsp.h" -#include "BoundBox.h" -//#include "hammer_mathlib.h" -//#include "MapDefs.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - - -float rint(float f) -{ - if (f > 0.0f) { - return (float) floor(f + 0.5f); - } else if (f < 0.0f) { - return (float) ceil(f - 0.5f); - } else - return 0.0f; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -BoundBox::BoundBox(void) -{ - ResetBounds(); -} - -BoundBox::BoundBox(const Vector &mins, const Vector &maxs) -{ - bmins = mins; - bmaxs = maxs; -} - -//----------------------------------------------------------------------------- -// Purpose: Sets the box to an uninitialized state, so that calls to UpdateBounds -// will properly set the mins and maxs. -//----------------------------------------------------------------------------- -void BoundBox::ResetBounds(void) -{ - bmins[0] = bmins[1] = bmins[2] = COORD_NOTINIT; - bmaxs[0] = bmaxs[1] = bmaxs[2] = -COORD_NOTINIT; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pt - -//----------------------------------------------------------------------------- -void BoundBox::UpdateBounds(const Vector& pt) -{ - if(pt[0] < bmins[0]) - bmins[0] = pt[0]; - if(pt[1] < bmins[1]) - bmins[1] = pt[1]; - if(pt[2] < bmins[2]) - bmins[2] = pt[2]; - - if(pt[0] > bmaxs[0]) - bmaxs[0] = pt[0]; - if(pt[1] > bmaxs[1]) - bmaxs[1] = pt[1]; - if(pt[2] > bmaxs[2]) - bmaxs[2] = pt[2]; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : bmins - -// bmaxs - -//----------------------------------------------------------------------------- -void BoundBox::UpdateBounds(const Vector& mins, const Vector& maxs) -{ - if(mins[0] < bmins[0]) - bmins[0] = mins[0]; - if(mins[1] < bmins[1]) - bmins[1] = mins[1]; - if(mins[2] < bmins[2]) - bmins[2] = mins[2]; - - if(maxs[0] > bmaxs[0]) - bmaxs[0] = maxs[0]; - if(maxs[1] > bmaxs[1]) - bmaxs[1] = maxs[1]; - if(maxs[2] > bmaxs[2]) - bmaxs[2] = maxs[2]; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pBox - -//----------------------------------------------------------------------------- -void BoundBox::UpdateBounds(const BoundBox *pBox) -{ - UpdateBounds(pBox->bmins, pBox->bmaxs); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : ptdest - -//----------------------------------------------------------------------------- -void BoundBox::GetBoundsCenter(Vector& ptdest) -{ - ptdest = (bmins + bmaxs)/2.0f; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pt - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool BoundBox::ContainsPoint(const Vector& pt) const -{ - for (int i = 0; i < 3; i++) - { - if (pt[i] < bmins[i] || pt[i] > bmaxs[i]) - { - return(false); - } - } - return(true); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pfMins - -// pfMaxs - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool BoundBox::IsIntersectingBox(const Vector& pfMins, const Vector& pfMaxs) const -{ - if ((bmins[0] >= pfMaxs[0]) || (bmaxs[0] <= pfMins[0])) - { - return(false); - - } - if ((bmins[1] >= pfMaxs[1]) || (bmaxs[1] <= pfMins[1])) - { - return(false); - } - - if ((bmins[2] >= pfMaxs[2]) || (bmaxs[2] <= pfMins[2])) - { - return(false); - } - - return(true); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pfMins - -// pfMaxs - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool BoundBox::IsInsideBox(const Vector& pfMins, const Vector& pfMaxs) const -{ - if ((bmins[0] < pfMins[0]) || (bmaxs[0] > pfMaxs[0])) - { - return(false); - } - - if ((bmins[1] < pfMins[1]) || (bmaxs[1] > pfMaxs[1])) - { - return(false); - } - - if ((bmins[2] < pfMins[2]) || (bmaxs[2] > pfMaxs[2])) - { - return(false); - } - - return(true); -} - - -//----------------------------------------------------------------------------- -// Purpose: Returns whether this bounding box is valid, ie maxs >= mins. -//----------------------------------------------------------------------------- -bool BoundBox::IsValidBox(void) const -{ - for (int i = 0; i < 3; i++) - { - if (bmins[i] > bmaxs[i]) - { - return(false); - } - } - - return(true); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : size - -//----------------------------------------------------------------------------- -void BoundBox::GetBoundsSize(Vector& size) -{ - size[0] = bmaxs[0] - bmins[0]; - size[1] = bmaxs[1] - bmins[1]; - size[2] = bmaxs[2] - bmins[2]; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : iValue - -// iGridSize - -// Output : -//----------------------------------------------------------------------------- -static int Snap(/*int*/ float iValue, int iGridSize) -{ - return (int)(rint(iValue/iGridSize) * iGridSize); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : iGridSize - -//----------------------------------------------------------------------------- -void BoundBox::SnapToGrid(int iGridSize) -{ - // does not alter the size of the box .. snaps its minimal coordinates - // to the grid size specified in iGridSize - Vector size; - GetBoundsSize(size); - - for(int i = 0; i < 3; i++) - { - bmins[i] = (float)Snap(/* YWB (int)*/bmins[i], iGridSize); - bmaxs[i] = bmins[i] + size[i]; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : axis - -//----------------------------------------------------------------------------- -void BoundBox::Rotate90(int axis) -{ - int e1 = AXIS_X, e2 = AXIS_Y; - - // get bounds center first - Vector center; - GetBoundsCenter(center); - - switch(axis) - { - case AXIS_Z: - e1 = AXIS_X; - e2 = AXIS_Y; - break; - case AXIS_X: - e1 = AXIS_Y; - e2 = AXIS_Z; - break; - case AXIS_Y: - e1 = AXIS_X; - e2 = AXIS_Z; - break; - } - - float tmp1, tmp2; - tmp1 = bmins[e1] - center[e1] + center[e2]; - tmp2 = bmaxs[e1] - center[e1] + center[e2]; - bmins[e1] = bmins[e2] - center[e2] + center[e1]; - bmaxs[e1] = bmaxs[e2] - center[e2] + center[e1]; - bmins[e2] = tmp1; - bmaxs[e2] = tmp2; -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "BoundBox.h" +//#include "hammer_mathlib.h" +//#include "MapDefs.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +float rint(float f) +{ + if (f > 0.0f) { + return (float) floor(f + 0.5f); + } else if (f < 0.0f) { + return (float) ceil(f - 0.5f); + } else + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +BoundBox::BoundBox(void) +{ + ResetBounds(); +} + +BoundBox::BoundBox(const Vector &mins, const Vector &maxs) +{ + bmins = mins; + bmaxs = maxs; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the box to an uninitialized state, so that calls to UpdateBounds +// will properly set the mins and maxs. +//----------------------------------------------------------------------------- +void BoundBox::ResetBounds(void) +{ + bmins[0] = bmins[1] = bmins[2] = COORD_NOTINIT; + bmaxs[0] = bmaxs[1] = bmaxs[2] = -COORD_NOTINIT; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pt - +//----------------------------------------------------------------------------- +void BoundBox::UpdateBounds(const Vector& pt) +{ + if(pt[0] < bmins[0]) + bmins[0] = pt[0]; + if(pt[1] < bmins[1]) + bmins[1] = pt[1]; + if(pt[2] < bmins[2]) + bmins[2] = pt[2]; + + if(pt[0] > bmaxs[0]) + bmaxs[0] = pt[0]; + if(pt[1] > bmaxs[1]) + bmaxs[1] = pt[1]; + if(pt[2] > bmaxs[2]) + bmaxs[2] = pt[2]; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bmins - +// bmaxs - +//----------------------------------------------------------------------------- +void BoundBox::UpdateBounds(const Vector& mins, const Vector& maxs) +{ + if(mins[0] < bmins[0]) + bmins[0] = mins[0]; + if(mins[1] < bmins[1]) + bmins[1] = mins[1]; + if(mins[2] < bmins[2]) + bmins[2] = mins[2]; + + if(maxs[0] > bmaxs[0]) + bmaxs[0] = maxs[0]; + if(maxs[1] > bmaxs[1]) + bmaxs[1] = maxs[1]; + if(maxs[2] > bmaxs[2]) + bmaxs[2] = maxs[2]; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pBox - +//----------------------------------------------------------------------------- +void BoundBox::UpdateBounds(const BoundBox *pBox) +{ + UpdateBounds(pBox->bmins, pBox->bmaxs); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : ptdest - +//----------------------------------------------------------------------------- +void BoundBox::GetBoundsCenter(Vector& ptdest) +{ + ptdest = (bmins + bmaxs)/2.0f; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pt - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool BoundBox::ContainsPoint(const Vector& pt) const +{ + for (int i = 0; i < 3; i++) + { + if (pt[i] < bmins[i] || pt[i] > bmaxs[i]) + { + return(false); + } + } + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pfMins - +// pfMaxs - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool BoundBox::IsIntersectingBox(const Vector& pfMins, const Vector& pfMaxs) const +{ + if ((bmins[0] >= pfMaxs[0]) || (bmaxs[0] <= pfMins[0])) + { + return(false); + + } + if ((bmins[1] >= pfMaxs[1]) || (bmaxs[1] <= pfMins[1])) + { + return(false); + } + + if ((bmins[2] >= pfMaxs[2]) || (bmaxs[2] <= pfMins[2])) + { + return(false); + } + + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pfMins - +// pfMaxs - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool BoundBox::IsInsideBox(const Vector& pfMins, const Vector& pfMaxs) const +{ + if ((bmins[0] < pfMins[0]) || (bmaxs[0] > pfMaxs[0])) + { + return(false); + } + + if ((bmins[1] < pfMins[1]) || (bmaxs[1] > pfMaxs[1])) + { + return(false); + } + + if ((bmins[2] < pfMins[2]) || (bmaxs[2] > pfMaxs[2])) + { + return(false); + } + + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns whether this bounding box is valid, ie maxs >= mins. +//----------------------------------------------------------------------------- +bool BoundBox::IsValidBox(void) const +{ + for (int i = 0; i < 3; i++) + { + if (bmins[i] > bmaxs[i]) + { + return(false); + } + } + + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : size - +//----------------------------------------------------------------------------- +void BoundBox::GetBoundsSize(Vector& size) +{ + size[0] = bmaxs[0] - bmins[0]; + size[1] = bmaxs[1] - bmins[1]; + size[2] = bmaxs[2] - bmins[2]; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iValue - +// iGridSize - +// Output : +//----------------------------------------------------------------------------- +static int Snap(/*int*/ float iValue, int iGridSize) +{ + return (int)(rint(iValue/iGridSize) * iGridSize); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iGridSize - +//----------------------------------------------------------------------------- +void BoundBox::SnapToGrid(int iGridSize) +{ + // does not alter the size of the box .. snaps its minimal coordinates + // to the grid size specified in iGridSize + Vector size; + GetBoundsSize(size); + + for(int i = 0; i < 3; i++) + { + bmins[i] = (float)Snap(/* YWB (int)*/bmins[i], iGridSize); + bmaxs[i] = bmins[i] + size[i]; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : axis - +//----------------------------------------------------------------------------- +void BoundBox::Rotate90(int axis) +{ + int e1 = AXIS_X, e2 = AXIS_Y; + + // get bounds center first + Vector center; + GetBoundsCenter(center); + + switch(axis) + { + case AXIS_Z: + e1 = AXIS_X; + e2 = AXIS_Y; + break; + case AXIS_X: + e1 = AXIS_Y; + e2 = AXIS_Z; + break; + case AXIS_Y: + e1 = AXIS_X; + e2 = AXIS_Z; + break; + } + + float tmp1, tmp2; + tmp1 = bmins[e1] - center[e1] + center[e2]; + tmp2 = bmaxs[e1] - center[e1] + center[e2]; + bmins[e1] = bmins[e2] - center[e2] + center[e1]; + bmaxs[e1] = bmaxs[e2] - center[e2] + center[e1]; + bmins[e2] = tmp1; + bmaxs[e2] = tmp2; +} + diff --git a/mp/src/utils/vbsp/boundbox.h b/mp/src/utils/vbsp/boundbox.h index c9838fe6..4720a40e 100644 --- a/mp/src/utils/vbsp/boundbox.h +++ b/mp/src/utils/vbsp/boundbox.h @@ -1,79 +1,79 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: An axis aligned bounding box class. -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef BOUNDBOX_H -#define BOUNDBOX_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "mathlib/vector.h" - -#define COORD_NOTINIT ((float)(99999.0)) - -enum -{ - AXIS_X = 0, - AXIS_Y, - AXIS_Z -}; - -class BoundBox -{ - public: - - BoundBox(void); - BoundBox(const Vector &mins, const Vector &maxs); - - void ResetBounds(void); - inline void SetBounds(const Vector &mins, const Vector &maxs); - - void UpdateBounds(const Vector& bmins, const Vector& bmaxs); - void UpdateBounds(const Vector& pt); - void UpdateBounds(const BoundBox *pBox); - void GetBoundsCenter(Vector& ptdest); - inline void GetBounds(Vector& Mins, Vector& Maxs); - - virtual bool IsIntersectingBox(const Vector& pfMins, const Vector& pfMaxs) const; - bool IsInsideBox(const Vector& pfMins, const Vector& pfMaxs) const; - bool ContainsPoint(const Vector& pt) const; - bool IsValidBox(void) const; - void GetBoundsSize(Vector& size); - void SnapToGrid(int iGridSize); - void Rotate90(int axis); - - Vector bmins; - Vector bmaxs; -}; - - -//----------------------------------------------------------------------------- -// Purpose: Gets the bounding box as two vectors, a min and a max. -// Input : Mins - Receives the box's minima. -// Maxs - Receives the box's maxima. -//----------------------------------------------------------------------------- -void BoundBox::GetBounds(Vector &Mins, Vector &Maxs) -{ - Mins = bmins; - Maxs = bmaxs; -} - - -//----------------------------------------------------------------------------- -// Purpose: Sets the box outright, equivalent to ResetBounds + UpdateBounds. -// Input : mins - Minima to set. -// maxs - Maxima to set. -//----------------------------------------------------------------------------- -void BoundBox::SetBounds(const Vector &mins, const Vector &maxs) -{ - bmins = mins; - bmaxs = maxs; -} - - -#endif // BOUNDBOX_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: An axis aligned bounding box class. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef BOUNDBOX_H +#define BOUNDBOX_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "mathlib/vector.h" + +#define COORD_NOTINIT ((float)(99999.0)) + +enum +{ + AXIS_X = 0, + AXIS_Y, + AXIS_Z +}; + +class BoundBox +{ + public: + + BoundBox(void); + BoundBox(const Vector &mins, const Vector &maxs); + + void ResetBounds(void); + inline void SetBounds(const Vector &mins, const Vector &maxs); + + void UpdateBounds(const Vector& bmins, const Vector& bmaxs); + void UpdateBounds(const Vector& pt); + void UpdateBounds(const BoundBox *pBox); + void GetBoundsCenter(Vector& ptdest); + inline void GetBounds(Vector& Mins, Vector& Maxs); + + virtual bool IsIntersectingBox(const Vector& pfMins, const Vector& pfMaxs) const; + bool IsInsideBox(const Vector& pfMins, const Vector& pfMaxs) const; + bool ContainsPoint(const Vector& pt) const; + bool IsValidBox(void) const; + void GetBoundsSize(Vector& size); + void SnapToGrid(int iGridSize); + void Rotate90(int axis); + + Vector bmins; + Vector bmaxs; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Gets the bounding box as two vectors, a min and a max. +// Input : Mins - Receives the box's minima. +// Maxs - Receives the box's maxima. +//----------------------------------------------------------------------------- +void BoundBox::GetBounds(Vector &Mins, Vector &Maxs) +{ + Mins = bmins; + Maxs = bmaxs; +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the box outright, equivalent to ResetBounds + UpdateBounds. +// Input : mins - Minima to set. +// maxs - Maxima to set. +//----------------------------------------------------------------------------- +void BoundBox::SetBounds(const Vector &mins, const Vector &maxs) +{ + bmins = mins; + bmaxs = maxs; +} + + +#endif // BOUNDBOX_H diff --git a/mp/src/utils/vbsp/brushbsp.cpp b/mp/src/utils/vbsp/brushbsp.cpp index 17323c58..d455f30b 100644 --- a/mp/src/utils/vbsp/brushbsp.cpp +++ b/mp/src/utils/vbsp/brushbsp.cpp @@ -1,1469 +1,1469 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "vbsp.h" - - -int c_nodes; -int c_nonvis; -int c_active_brushes; - -// if a brush just barely pokes onto the other side, -// let it slide by without chopping -#define PLANESIDE_EPSILON 0.001 -//0.1 - - -void FindBrushInTree (node_t *node, int brushnum) -{ - bspbrush_t *b; - - if (node->planenum == PLANENUM_LEAF) - { - for (b=node->brushlist ; b ; b=b->next) - if (b->original->brushnum == brushnum) - Msg("here\n"); - return; - } - FindBrushInTree (node->children[0], brushnum); - FindBrushInTree (node->children[1], brushnum); -} - -//================================================== - -/* -================ -DrawBrushList -================ -*/ -void DrawBrushList (bspbrush_t *brush, node_t *node) -{ - int i; - side_t *s; - - GLS_BeginScene (); - for ( ; brush ; brush=brush->next) - { - for (i=0 ; inumsides ; i++) - { - s = &brush->sides[i]; - if (!s->winding) - continue; - if (s->texinfo == TEXINFO_NODE) - GLS_Winding (s->winding, 1); - else if (!s->visible) - GLS_Winding (s->winding, 2); - else - GLS_Winding (s->winding, 0); - } - } - GLS_EndScene (); -} - -/* -================ -WriteBrushList -================ -*/ -void WriteBrushList (char *name, bspbrush_t *brush, qboolean onlyvis) -{ - int i; - side_t *s; - - qprintf ("writing %s\n", name); - FileHandle_t f = g_pFileSystem->Open(name, "w"); - - for ( ; brush ; brush=brush->next) - { - for (i=0 ; inumsides ; i++) - { - s = &brush->sides[i]; - if (!s->winding) - continue; - if (onlyvis && !s->visible) - continue; - OutputWinding (brush->sides[i].winding, f); - } - } - - g_pFileSystem->Close (f); -} - -void PrintBrush (bspbrush_t *brush) -{ - int i; - - Msg("brush: %p\n", brush); - for (i=0;inumsides ; i++) - { - pw(brush->sides[i].winding); - Msg("\n"); - } -} - -/* -================== -BoundBrush - -Sets the mins/maxs based on the windings -================== -*/ -void BoundBrush (bspbrush_t *brush) -{ - int i, j; - winding_t *w; - - ClearBounds (brush->mins, brush->maxs); - for (i=0 ; inumsides ; i++) - { - w = brush->sides[i].winding; - if (!w) - continue; - for (j=0 ; jnumpoints ; j++) - AddPointToBounds (w->p[j], brush->mins, brush->maxs); - } -} - -Vector PointInsideBrush( bspbrush_t *brush ) -{ - Vector insidePoint = vec3_origin; - - bool bInside = false; - for ( int k = 0; k < 4 && !bInside; k++ ) - { - bInside = true; - for (int i = 0; i < brush->numsides; i++) - { - side_t *side = &brush->sides[i]; - plane_t *plane = &g_MainMap->mapplanes[side->planenum]; - float d = DotProduct( plane->normal, insidePoint ) - plane->dist; - if ( d < 0 ) - { - bInside = false; - insidePoint -= d * plane->normal; - } - } - } - return insidePoint; -} - -/* -================== -CreateBrushWindings - -================== -*/ -void CreateBrushWindings (bspbrush_t *brush) -{ - int i, j; - winding_t *w; - side_t *side; - plane_t *plane; - - // translate the CSG problem to improve precision - Vector insidePoint = PointInsideBrush( brush ); - Vector offset = -insidePoint; - - for (i=0 ; inumsides ; i++) - { - side = &brush->sides[i]; - plane = &g_MainMap->mapplanes[side->planenum]; - w = BaseWindingForPlane (plane->normal, plane->dist + DotProduct(plane->normal, offset)); - for (j=0 ; jnumsides && w; j++) - { - if (i == j) - continue; - if (brush->sides[j].bevel) - continue; - plane = &g_MainMap->mapplanes[brush->sides[j].planenum^1]; - ChopWindingInPlace (&w, plane->normal, plane->dist + DotProduct(plane->normal, offset), 0); //CLIP_EPSILON); - } - - TranslateWinding( w, -offset ); - side->winding = w; - } - - BoundBrush (brush); -} - -/* -================== -BrushFromBounds - -Creates a new axial brush -================== -*/ -bspbrush_t *BrushFromBounds (Vector& mins, Vector& maxs) -{ - bspbrush_t *b; - int i; - Vector normal; - vec_t dist; - - b = AllocBrush (6); - b->numsides = 6; - for (i=0 ; i<3 ; i++) - { - VectorClear (normal); - normal[i] = 1; - dist = maxs[i]; - b->sides[i].planenum = g_MainMap->FindFloatPlane (normal, dist); - - normal[i] = -1; - dist = -mins[i]; - b->sides[3+i].planenum = g_MainMap->FindFloatPlane (normal, dist); - } - - CreateBrushWindings (b); - - return b; -} - -/* -================== -BrushVolume - -================== -*/ -vec_t BrushVolume (bspbrush_t *brush) -{ - int i; - winding_t *w; - Vector corner; - vec_t d, area, volume; - plane_t *plane; - - if (!brush) - return 0; - - // grab the first valid point as the corner - - w = NULL; - for (i=0 ; inumsides ; i++) - { - w = brush->sides[i].winding; - if (w) - break; - } - if (!w) - return 0; - VectorCopy (w->p[0], corner); - - // make tetrahedrons to all other faces - - volume = 0; - for ( ; inumsides ; i++) - { - w = brush->sides[i].winding; - if (!w) - continue; - plane = &g_MainMap->mapplanes[brush->sides[i].planenum]; - d = -(DotProduct (corner, plane->normal) - plane->dist); - area = WindingArea (w); - volume += d*area; - } - - volume /= 3; - return volume; -} - -/* -================ -CountBrushList -================ -*/ -int CountBrushList (bspbrush_t *brushes) -{ - int c; - - c = 0; - for ( ; brushes ; brushes = brushes->next) - c++; - return c; -} - -/* -================ -AllocTree -================ -*/ -tree_t *AllocTree (void) -{ - tree_t *tree; - - tree = (tree_t*)malloc(sizeof(*tree)); - memset (tree, 0, sizeof(*tree)); - ClearBounds (tree->mins, tree->maxs); - - return tree; -} - -/* -================ -AllocNode -================ -*/ -node_t *AllocNode (void) -{ - static int s_NodeCount = 0; - - node_t *node; - - node = (node_t*)malloc(sizeof(*node)); - memset (node, 0, sizeof(*node)); - node->id = s_NodeCount; - node->diskId = -1; - - s_NodeCount++; - - return node; -} - - -/* -================ -AllocBrush -================ -*/ -bspbrush_t *AllocBrush (int numsides) -{ - static int s_BrushId = 0; - - bspbrush_t *bb; - int c; - - c = (int)&(((bspbrush_t *)0)->sides[numsides]); - bb = (bspbrush_t*)malloc(c); - memset (bb, 0, c); - bb->id = s_BrushId++; - if (numthreads == 1) - c_active_brushes++; - return bb; -} - -/* -================ -FreeBrush -================ -*/ -void FreeBrush (bspbrush_t *brushes) -{ - int i; - - for (i=0 ; inumsides ; i++) - if (brushes->sides[i].winding) - FreeWinding(brushes->sides[i].winding); - free (brushes); - if (numthreads == 1) - c_active_brushes--; -} - - -/* -================ -FreeBrushList -================ -*/ -void FreeBrushList (bspbrush_t *brushes) -{ - bspbrush_t *next; - - for ( ; brushes ; brushes = next) - { - next = brushes->next; - - FreeBrush (brushes); - } -} - -/* -================== -CopyBrush - -Duplicates the brush, the sides, and the windings -================== -*/ -bspbrush_t *CopyBrush (bspbrush_t *brush) -{ - bspbrush_t *newbrush; - int size; - int i; - - size = (int)&(((bspbrush_t *)0)->sides[brush->numsides]); - - newbrush = AllocBrush (brush->numsides); - memcpy (newbrush, brush, size); - - for (i=0 ; inumsides ; i++) - { - if (brush->sides[i].winding) - newbrush->sides[i].winding = CopyWinding (brush->sides[i].winding); - } - - return newbrush; -} - - -/* -================== -PointInLeaf - -================== -*/ -node_t *PointInLeaf (node_t *node, Vector& point) -{ - vec_t d; - plane_t *plane; - - while (node->planenum != PLANENUM_LEAF) - { - plane = &g_MainMap->mapplanes[node->planenum]; - if (plane->type < 3) - { - d = point[plane->type] - plane->dist; - } - else - { - d = DotProduct (point, plane->normal) - plane->dist; - } - - if (d >= 0) - node = node->children[0]; - else - node = node->children[1]; - } - - return node; -} - -//======================================================== - -/* -============== -BoxOnPlaneSide - -Returns PSIDE_FRONT, PSIDE_BACK, or PSIDE_BOTH -============== -*/ -int BrushBspBoxOnPlaneSide (const Vector& mins, const Vector& maxs, dplane_t *plane) -{ - int side; - int i; - Vector corners[2]; - vec_t dist1, dist2; - - // axial planes are easy - if (plane->type < 3) - { - side = 0; - if (maxs[plane->type] > plane->dist+PLANESIDE_EPSILON) - side |= PSIDE_FRONT; - if (mins[plane->type] < plane->dist-PLANESIDE_EPSILON) - side |= PSIDE_BACK; - return side; - } - - // create the proper leading and trailing verts for the box - - for (i=0 ; i<3 ; i++) - { - if (plane->normal[i] < 0) - { - corners[0][i] = mins[i]; - corners[1][i] = maxs[i]; - } - else - { - corners[1][i] = mins[i]; - corners[0][i] = maxs[i]; - } - } - - dist1 = DotProduct (plane->normal, corners[0]) - plane->dist; - dist2 = DotProduct (plane->normal, corners[1]) - plane->dist; - side = 0; - if (dist1 >= PLANESIDE_EPSILON) - side = PSIDE_FRONT; - if (dist2 < PLANESIDE_EPSILON) - side |= PSIDE_BACK; - - return side; -} - -/* -============ -QuickTestBrushToPlanenum - -============ -*/ -int QuickTestBrushToPlanenum (bspbrush_t *brush, int planenum, int *numsplits) -{ - int i, num; - plane_t *plane; - int s; - - *numsplits = 0; - - // if the brush actually uses the planenum, - // we can tell the side for sure - for (i=0 ; inumsides ; i++) - { - num = brush->sides[i].planenum; - if (num >= 0x10000) - Error ("bad planenum"); - if (num == planenum) - return PSIDE_BACK|PSIDE_FACING; - if (num == (planenum ^ 1) ) - return PSIDE_FRONT|PSIDE_FACING; - } - - // box on plane side - plane = &g_MainMap->mapplanes[planenum]; - s = BrushBspBoxOnPlaneSide (brush->mins, brush->maxs, plane); - - // if both sides, count the visible faces split - if (s == PSIDE_BOTH) - { - *numsplits += 3; - } - - return s; -} - -/* -============ -TestBrushToPlanenum - -============ -*/ -int TestBrushToPlanenum (bspbrush_t *brush, int planenum, - int *numsplits, qboolean *hintsplit, int *epsilonbrush) -{ - int i, j, num; - plane_t *plane; - int s; - winding_t *w; - vec_t d, d_front, d_back; - int front, back; - - *numsplits = 0; - *hintsplit = false; - - // if the brush actually uses the planenum, - // we can tell the side for sure - for (i=0 ; inumsides ; i++) - { - num = brush->sides[i].planenum; - if (num >= 0x10000) - Error ("bad planenum"); - if (num == planenum) - return PSIDE_BACK|PSIDE_FACING; - if (num == (planenum ^ 1) ) - return PSIDE_FRONT|PSIDE_FACING; - } - - // box on plane side - plane = &g_MainMap->mapplanes[planenum]; - s = BrushBspBoxOnPlaneSide (brush->mins, brush->maxs, plane); - - if (s != PSIDE_BOTH) - return s; - -// if both sides, count the visible faces split - d_front = d_back = 0; - - for (i=0 ; inumsides ; i++) - { - if (brush->sides[i].texinfo == TEXINFO_NODE) - continue; // on node, don't worry about splits - if (!brush->sides[i].visible) - continue; // we don't care about non-visible - w = brush->sides[i].winding; - if (!w) - continue; - - front = back = 0; - for (j=0 ; jnumpoints; j++) - { - d = DotProduct (w->p[j], plane->normal) - plane->dist; - - if (d > d_front) - d_front = d; - if (d < d_back) - d_back = d; - - if (d > 0.1) // PLANESIDE_EPSILON) - front = 1; - if (d < -0.1) // PLANESIDE_EPSILON) - back = 1; - } - - if (front && back) - { - if ( !(brush->sides[i].surf & SURF_SKIP) ) - { - (*numsplits)++; - if (brush->sides[i].surf & SURF_HINT) - *hintsplit = true; - } - } - } - - if ( (d_front > 0.0 && d_front < 1.0) - || (d_back < 0.0 && d_back > -1.0) ) - (*epsilonbrush)++; - -#if 0 - if (*numsplits == 0) - { // didn't really need to be split - if (front) - s = PSIDE_FRONT; - else if (back) - s = PSIDE_BACK; - else - s = 0; - } -#endif - - return s; -} - -//======================================================== - -/* -================ -WindingIsTiny - -Returns true if the winding would be crunched out of -existance by the vertex snapping. -================ -*/ -#define EDGE_LENGTH 0.2 -qboolean WindingIsTiny (winding_t *w) -{ - int i, j; - vec_t len; - Vector delta; - int edges; - - edges = 0; - for (i=0 ; inumpoints ; i++) - { - j = i == w->numpoints - 1 ? 0 : i+1; - VectorSubtract (w->p[j], w->p[i], delta); - len = VectorLength (delta); - if (len > EDGE_LENGTH) - { - if (++edges == 3) - return false; - } - } - return true; -} - - -// UNDONE: JAY: This should be a slightly better heuristic - it builds an OBB -// around the winding and tests planar dimensions. NOTE: This can fail when a -// winding normal cannot be constructed (or is degenerate), but that is probably -// desired in this case. -// UNDONE: Test & use this instead. -#if 0 -qboolean WindingIsTiny2 (winding_t *w) -{ - int i, j; - vec_t len; - Vector delta; - int edges; - - vec_t maxLen = 0; - Vector maxEdge = vec3_origin; - - edges = 0; - for (i=0 ; inumpoints ; i++) - { - j = i == w->numpoints - 1 ? 0 : i+1; - VectorSubtract (w->p[j], w->p[i], delta); - len = VectorLength (delta); - if (len > maxLen) - { - maxEdge = delta; - maxLen = len; - } - } - Vector normal; - vec_t dist; - WindingPlane (w, normal, &dist); // normal can come back vec3_origin in some cases - VectorNormalize(maxEdge); - Vector cross = CrossProduct(normal, maxEdge); - VectorNormalize(cross); - Vector mins, maxs; - ClearBounds( mins, maxs ); - for (i=0 ; inumpoints ; i++) - { - Vector point; - point.x = DotProduct( w->p[i], maxEdge ); - point.y = DotProduct( w->p[i], cross ); - point.z = DotProduct( w->p[i], normal ); - AddPointToBounds( point, mins, maxs ); - } - - // check to see if the size in the plane is too small in either dimension - Vector size = maxs - mins; - for ( i = 0; i < 2; i++ ) - { - if ( size[i] < EDGE_LENGTH ) - return true; - } - return false; -} -#endif - - -/* -================ -WindingIsHuge - -Returns true if the winding still has one of the points -from basewinding for plane -================ -*/ -qboolean WindingIsHuge (winding_t *w) -{ - int i, j; - - for (i=0 ; inumpoints ; i++) - { - for (j=0 ; j<3 ; j++) - if (w->p[i][j] < MIN_COORD_INTEGER || w->p[i][j] > MAX_COORD_INTEGER) - return true; - } - return false; -} - -//============================================================ - -/* -================ -Leafnode -================ -*/ -void LeafNode (node_t *node, bspbrush_t *brushes) -{ - bspbrush_t *b; - int i; - - node->planenum = PLANENUM_LEAF; - node->contents = 0; - - for (b=brushes ; b ; b=b->next) - { - // if the brush is solid and all of its sides are on nodes, - // it eats everything - if (b->original->contents & CONTENTS_SOLID) - { - for (i=0 ; inumsides ; i++) - if (b->sides[i].texinfo != TEXINFO_NODE) - break; - if (i == b->numsides) - { - node->contents = CONTENTS_SOLID; - break; - } - } - node->contents |= b->original->contents; - } - - node->brushlist = brushes; -} - - -void RemoveAreaPortalBrushes_R( node_t *node ) -{ - if( node->planenum == PLANENUM_LEAF ) - { - // Remove any CONTENTS_AREAPORTAL brushes we added. We don't want them in the engine - // at runtime but we do want their flags in the leaves. - bspbrush_t **pPrev = &node->brushlist; - for( bspbrush_t *b=node->brushlist; b; b=b->next ) - { - if( b->original->contents == CONTENTS_AREAPORTAL ) - { - *pPrev = b->next; - } - else - { - pPrev = &b->next; - } - } - } - else - { - RemoveAreaPortalBrushes_R( node->children[0] ); - RemoveAreaPortalBrushes_R( node->children[1] ); - } -} - - -//============================================================ - -void CheckPlaneAgainstParents (int pnum, node_t *node) -{ - node_t *p; - - for (p=node->parent ; p ; p=p->parent) - { - if (p->planenum == pnum) - Error ("Tried parent"); - } -} - -qboolean CheckPlaneAgainstVolume (int pnum, node_t *node) -{ - bspbrush_t *front, *back; - qboolean good; - - SplitBrush (node->volume, pnum, &front, &back); - - good = (front && back); - - if (front) - FreeBrush (front); - if (back) - FreeBrush (back); - - return good; -} - -/* -================ -SelectSplitSide - -Using a hueristic, choses one of the sides out of the brushlist -to partition the brushes with. -Returns NULL if there are no valid planes to split with.. -================ -*/ - -side_t *SelectSplitSide (bspbrush_t *brushes, node_t *node) -{ - int value, bestvalue; - bspbrush_t *brush, *test; - side_t *side, *bestside; - int i, j, pass, numpasses; - int pnum; - int s; - int front, back, both, facing, splits; - int bsplits; - int bestsplits; - int epsilonbrush; - qboolean hintsplit = false; - - bestside = NULL; - bestvalue = -99999; - bestsplits = 0; - - // the search order goes: visible-structural, nonvisible-structural - // If any valid plane is available in a pass, no further - // passes will be tried. - numpasses = 2; - for (pass = 0 ; pass < numpasses ; pass++) - { - for (brush = brushes ; brush ; brush=brush->next) - { - for (i=0 ; inumsides ; i++) - { - side = brush->sides + i; - - if (side->bevel) - continue; // never use a bevel as a spliter - if (!side->winding) - continue; // nothing visible, so it can't split - if (side->texinfo == TEXINFO_NODE) - continue; // allready a node splitter - if (side->tested) - continue; // we allready have metrics for this plane - if (side->surf & SURF_SKIP) - continue; // skip surfaces are never chosen - if ( side->visible ^ (pass<1) ) - continue; // only check visible faces on first pass - - pnum = side->planenum; - pnum &= ~1; // allways use positive facing plane - - CheckPlaneAgainstParents (pnum, node); - - if (!CheckPlaneAgainstVolume (pnum, node)) - continue; // would produce a tiny volume - - front = 0; - back = 0; - both = 0; - facing = 0; - splits = 0; - epsilonbrush = 0; - - for (test = brushes ; test ; test=test->next) - { - s = TestBrushToPlanenum (test, pnum, &bsplits, &hintsplit, &epsilonbrush); - - splits += bsplits; - if (bsplits && (s&PSIDE_FACING) ) - Error ("PSIDE_FACING with splits"); - - test->testside = s; - // if the brush shares this face, don't bother - // testing that facenum as a splitter again - if (s & PSIDE_FACING) - { - facing++; - for (j=0 ; jnumsides ; j++) - { - if ( (test->sides[j].planenum&~1) == pnum) - test->sides[j].tested = true; - } - } - if (s & PSIDE_FRONT) - front++; - if (s & PSIDE_BACK) - back++; - if (s == PSIDE_BOTH) - both++; - } - - // give a value estimate for using this plane - value = 5*facing - 5*splits - abs(front-back); -// value = -5*splits; -// value = 5*facing - 5*splits; - if (g_MainMap->mapplanes[pnum].type < 3) - value+=5; // axial is better - value -= epsilonbrush*1000; // avoid! - - // trans should split last - if ( side->surf & SURF_TRANS ) - { - value -= 500; - } - - // never split a hint side except with another hint - if (hintsplit && !(side->surf & SURF_HINT) ) - value = -9999999; - - // water should split first - if (side->contents & (CONTENTS_WATER | CONTENTS_SLIME)) - value = 9999999; - - // save off the side test so we don't need - // to recalculate it when we actually seperate - // the brushes - if (value > bestvalue) - { - bestvalue = value; - bestside = side; - bestsplits = splits; - for (test = brushes ; test ; test=test->next) - test->side = test->testside; - } - } - } - - // if we found a good plane, don't bother trying any - // other passes - if (bestside) - { - if (pass > 0) - { - if (numthreads == 1) - c_nonvis++; - } - break; - } - } - - // - // clear all the tested flags we set - // - for (brush = brushes ; brush ; brush=brush->next) - { - for (i=0 ; inumsides ; i++) - brush->sides[i].tested = false; - } - - return bestside; -} - - -/* -================== -BrushMostlyOnSide - -================== -*/ -int BrushMostlyOnSide (bspbrush_t *brush, plane_t *plane) -{ - int i, j; - winding_t *w; - vec_t d, max; - int side; - - max = 0; - side = PSIDE_FRONT; - for (i=0 ; inumsides ; i++) - { - w = brush->sides[i].winding; - if (!w) - continue; - for (j=0 ; jnumpoints ; j++) - { - d = DotProduct (w->p[j], plane->normal) - plane->dist; - if (d > max) - { - max = d; - side = PSIDE_FRONT; - } - if (-d > max) - { - max = -d; - side = PSIDE_BACK; - } - } - } - return side; -} - -/* -================ -SplitBrush - -Generates two new brushes, leaving the original -unchanged -================ -*/ - - -void SplitBrush( bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back ) -{ - bspbrush_t *b[2]; - int i, j; - winding_t *w, *cw[2], *midwinding; - plane_t *plane, *plane2; - side_t *s, *cs; - float d, d_front, d_back; - - *front = *back = NULL; - plane = &g_MainMap->mapplanes[planenum]; - - // check all points - d_front = d_back = 0; - for (i=0 ; inumsides ; i++) - { - w = brush->sides[i].winding; - if (!w) - continue; - for (j=0 ; jnumpoints ; j++) - { - d = DotProduct (w->p[j], plane->normal) - plane->dist; - if (d > 0 && d > d_front) - d_front = d; - if (d < 0 && d < d_back) - d_back = d; - } - } - - if (d_front < 0.1) // PLANESIDE_EPSILON) - { // only on back - *back = CopyBrush (brush); - return; - } - if (d_back > -0.1) // PLANESIDE_EPSILON) - { // only on front - *front = CopyBrush (brush); - return; - } - - - // Move the CSG problem so that offset is at the origin - // This gives us much better floating point precision in the clipping operations - Vector offset = -0.5f * (brush->mins + brush->maxs); - // create a new winding from the split plane - - w = BaseWindingForPlane (plane->normal, plane->dist + DotProduct(plane->normal,offset)); - for (i=0 ; inumsides && w ; i++) - { - plane2 = &g_MainMap->mapplanes[brush->sides[i].planenum ^ 1]; - ChopWindingInPlace (&w, plane2->normal, plane2->dist+DotProduct(plane2->normal,offset), 0); // PLANESIDE_EPSILON); - } - - if (!w || WindingIsTiny (w) ) - { // the brush isn't really split - int side; - - side = BrushMostlyOnSide (brush, plane); - if (side == PSIDE_FRONT) - *front = CopyBrush (brush); - if (side == PSIDE_BACK) - *back = CopyBrush (brush); - return; - } - - if (WindingIsHuge (w)) - { - qprintf ("WARNING: huge winding\n"); - } - - TranslateWinding( w, -offset ); - midwinding = w; - - // - // - // split it for real - // - // - - // - // allocate two new brushes referencing the original - // - for( i = 0; i < 2; i++ ) - { - b[i] = AllocBrush( brush->numsides + 1 ); - b[i]->original = brush->original; - } - - // - // split all the current windings - // - for( i = 0; i < brush->numsides; i++ ) - { - // get the current side - s = &brush->sides[i]; - - // get the sides winding - w = s->winding; - if( !w ) - continue; - - // clip the winding - ClipWindingEpsilon_Offset( w, plane->normal, plane->dist, 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1], offset ); - - for( j = 0; j < 2; j++ ) - { - // does winding exist? - if( !cw[j] ) - continue; -#if 0 - if (WindingIsTiny (cw[j])) - { - FreeWinding (cw[j]); - continue; - } -#endif - - // - // create a clipped "side" with the new winding - // - cs = &b[j]->sides[b[j]->numsides]; - b[j]->numsides++; - *cs = *s; - cs->winding = cw[j]; - cs->tested = false; - // save the original side information - //cs->original = s->original; - } - } - - - // see if we have valid polygons on both sides - - for (i=0 ; i<2 ; i++) - { - BoundBrush (b[i]); - for (j=0 ; j<3 ; j++) - { - if (b[i]->mins[j] < MIN_COORD_INTEGER || b[i]->maxs[j] > MAX_COORD_INTEGER) - { - qprintf ("bogus brush after clip\n"); - break; - } - } - - if (b[i]->numsides < 3 || j < 3) - { - FreeBrush (b[i]); - b[i] = NULL; - } - } - - if ( !(b[0] && b[1]) ) - { - if (!b[0] && !b[1]) - qprintf ("split removed brush\n"); - else - qprintf ("split not on both sides\n"); - if (b[0]) - { - FreeBrush (b[0]); - *front = CopyBrush (brush); - } - if (b[1]) - { - FreeBrush (b[1]); - *back = CopyBrush (brush); - } - return; - } - - // add the midwinding to both sides - for (i=0 ; i<2 ; i++) - { - cs = &b[i]->sides[b[i]->numsides]; - b[i]->numsides++; - - cs->planenum = planenum^i^1; - cs->texinfo = TEXINFO_NODE; - - // initialize the displacement map index - cs->pMapDisp = NULL; - - cs->visible = false; - cs->tested = false; - if (i==0) - cs->winding = CopyWinding (midwinding); - else - cs->winding = midwinding; - } - -{ - vec_t v1; - int i; - - for (i=0 ; i<2 ; i++) - { - v1 = BrushVolume (b[i]); - if (v1 < 1.0) - { - FreeBrush (b[i]); - b[i] = NULL; -// qprintf ("tiny volume after clip\n"); - } - } -} - - *front = b[0]; - *back = b[1]; -} - - -/* -================ -SplitBrushList -================ -*/ -void SplitBrushList (bspbrush_t *brushes, - node_t *node, bspbrush_t **front, bspbrush_t **back) -{ - bspbrush_t *brush, *newbrush, *newbrush2; - side_t *side; - int sides; - int i; - - *front = *back = NULL; - - for (brush = brushes ; brush ; brush=brush->next) - { - sides = brush->side; - - if (sides == PSIDE_BOTH) - { // split into two brushes - SplitBrush (brush, node->planenum, &newbrush, &newbrush2); - if (newbrush) - { - newbrush->next = *front; - *front = newbrush; - } - if (newbrush2) - { - newbrush2->next = *back; - *back = newbrush2; - } - continue; - } - - newbrush = CopyBrush (brush); - - // if the planenum is actualy a part of the brush - // find the plane and flag it as used so it won't be tried - // as a splitter again - if (sides & PSIDE_FACING) - { - for (i=0 ; inumsides ; i++) - { - side = newbrush->sides + i; - if ( (side->planenum& ~1) == node->planenum) - side->texinfo = TEXINFO_NODE; - } - } - - - if (sides & PSIDE_FRONT) - { - newbrush->next = *front; - *front = newbrush; - continue; - } - if (sides & PSIDE_BACK) - { - newbrush->next = *back; - *back = newbrush; - continue; - } - } -} - - -/* -================ -BuildTree_r -================ -*/ - - -node_t *BuildTree_r (node_t *node, bspbrush_t *brushes) -{ - node_t *newnode; - side_t *bestside; - int i; - bspbrush_t *children[2]; - - if (numthreads == 1) - c_nodes++; - - // find the best plane to use as a splitter - bestside = SelectSplitSide (brushes, node); - - if (!bestside) - { - // leaf node - node->side = NULL; - node->planenum = -1; - LeafNode (node, brushes); - return node; - } - - // this is a splitplane node - node->side = bestside; - node->planenum = bestside->planenum & ~1; // always use front facing - - SplitBrushList (brushes, node, &children[0], &children[1]); - FreeBrushList (brushes); - - // allocate children before recursing - for (i=0 ; i<2 ; i++) - { - newnode = AllocNode (); - newnode->parent = node; - node->children[i] = newnode; - } - - SplitBrush (node->volume, node->planenum, &node->children[0]->volume, - &node->children[1]->volume); - - // recursively process children - for (i=0 ; i<2 ; i++) - { - node->children[i] = BuildTree_r (node->children[i], children[i]); - } - - return node; -} - - -//=========================================================== - -/* -================= -BrushBSP - -The incoming list will be freed before exiting -================= -*/ -tree_t *BrushBSP (bspbrush_t *brushlist, Vector& mins, Vector& maxs) -{ - node_t *node; - bspbrush_t *b; - int c_faces, c_nonvisfaces; - int c_brushes; - tree_t *tree; - int i; - vec_t volume; - - qprintf ("--- BrushBSP ---\n"); - - tree = AllocTree (); - - c_faces = 0; - c_nonvisfaces = 0; - c_brushes = 0; - for (b=brushlist ; b ; b=b->next) - { - c_brushes++; - - volume = BrushVolume (b); - if (volume < microvolume) - { - Warning("Brush %i: WARNING, microbrush\n", b->original->id); - } - - for (i=0 ; inumsides ; i++) - { - if (b->sides[i].bevel) - continue; - if (!b->sides[i].winding) - continue; - if (b->sides[i].texinfo == TEXINFO_NODE) - continue; - if (b->sides[i].visible) - c_faces++; - else - c_nonvisfaces++; - } - - AddPointToBounds (b->mins, tree->mins, tree->maxs); - AddPointToBounds (b->maxs, tree->mins, tree->maxs); - } - - qprintf ("%5i brushes\n", c_brushes); - qprintf ("%5i visible faces\n", c_faces); - qprintf ("%5i nonvisible faces\n", c_nonvisfaces); - - c_nodes = 0; - c_nonvis = 0; - node = AllocNode (); - - node->volume = BrushFromBounds (mins, maxs); - - tree->headnode = node; - - node = BuildTree_r (node, brushlist); - qprintf ("%5i visible nodes\n", c_nodes/2 - c_nonvis); - qprintf ("%5i nonvis nodes\n", c_nonvis); - qprintf ("%5i leafs\n", (c_nodes+1)/2); -#if 0 -{ // debug code -static node_t *tnode; -Vector p; - -p[0] = -1469; -p[1] = -118; -p[2] = 119; -tnode = PointInLeaf (tree->headnode, p); -Msg("contents: %i\n", tnode->contents); -p[0] = 0; -} -#endif - return tree; -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" + + +int c_nodes; +int c_nonvis; +int c_active_brushes; + +// if a brush just barely pokes onto the other side, +// let it slide by without chopping +#define PLANESIDE_EPSILON 0.001 +//0.1 + + +void FindBrushInTree (node_t *node, int brushnum) +{ + bspbrush_t *b; + + if (node->planenum == PLANENUM_LEAF) + { + for (b=node->brushlist ; b ; b=b->next) + if (b->original->brushnum == brushnum) + Msg("here\n"); + return; + } + FindBrushInTree (node->children[0], brushnum); + FindBrushInTree (node->children[1], brushnum); +} + +//================================================== + +/* +================ +DrawBrushList +================ +*/ +void DrawBrushList (bspbrush_t *brush, node_t *node) +{ + int i; + side_t *s; + + GLS_BeginScene (); + for ( ; brush ; brush=brush->next) + { + for (i=0 ; inumsides ; i++) + { + s = &brush->sides[i]; + if (!s->winding) + continue; + if (s->texinfo == TEXINFO_NODE) + GLS_Winding (s->winding, 1); + else if (!s->visible) + GLS_Winding (s->winding, 2); + else + GLS_Winding (s->winding, 0); + } + } + GLS_EndScene (); +} + +/* +================ +WriteBrushList +================ +*/ +void WriteBrushList (char *name, bspbrush_t *brush, qboolean onlyvis) +{ + int i; + side_t *s; + + qprintf ("writing %s\n", name); + FileHandle_t f = g_pFileSystem->Open(name, "w"); + + for ( ; brush ; brush=brush->next) + { + for (i=0 ; inumsides ; i++) + { + s = &brush->sides[i]; + if (!s->winding) + continue; + if (onlyvis && !s->visible) + continue; + OutputWinding (brush->sides[i].winding, f); + } + } + + g_pFileSystem->Close (f); +} + +void PrintBrush (bspbrush_t *brush) +{ + int i; + + Msg("brush: %p\n", brush); + for (i=0;inumsides ; i++) + { + pw(brush->sides[i].winding); + Msg("\n"); + } +} + +/* +================== +BoundBrush + +Sets the mins/maxs based on the windings +================== +*/ +void BoundBrush (bspbrush_t *brush) +{ + int i, j; + winding_t *w; + + ClearBounds (brush->mins, brush->maxs); + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + AddPointToBounds (w->p[j], brush->mins, brush->maxs); + } +} + +Vector PointInsideBrush( bspbrush_t *brush ) +{ + Vector insidePoint = vec3_origin; + + bool bInside = false; + for ( int k = 0; k < 4 && !bInside; k++ ) + { + bInside = true; + for (int i = 0; i < brush->numsides; i++) + { + side_t *side = &brush->sides[i]; + plane_t *plane = &g_MainMap->mapplanes[side->planenum]; + float d = DotProduct( plane->normal, insidePoint ) - plane->dist; + if ( d < 0 ) + { + bInside = false; + insidePoint -= d * plane->normal; + } + } + } + return insidePoint; +} + +/* +================== +CreateBrushWindings + +================== +*/ +void CreateBrushWindings (bspbrush_t *brush) +{ + int i, j; + winding_t *w; + side_t *side; + plane_t *plane; + + // translate the CSG problem to improve precision + Vector insidePoint = PointInsideBrush( brush ); + Vector offset = -insidePoint; + + for (i=0 ; inumsides ; i++) + { + side = &brush->sides[i]; + plane = &g_MainMap->mapplanes[side->planenum]; + w = BaseWindingForPlane (plane->normal, plane->dist + DotProduct(plane->normal, offset)); + for (j=0 ; jnumsides && w; j++) + { + if (i == j) + continue; + if (brush->sides[j].bevel) + continue; + plane = &g_MainMap->mapplanes[brush->sides[j].planenum^1]; + ChopWindingInPlace (&w, plane->normal, plane->dist + DotProduct(plane->normal, offset), 0); //CLIP_EPSILON); + } + + TranslateWinding( w, -offset ); + side->winding = w; + } + + BoundBrush (brush); +} + +/* +================== +BrushFromBounds + +Creates a new axial brush +================== +*/ +bspbrush_t *BrushFromBounds (Vector& mins, Vector& maxs) +{ + bspbrush_t *b; + int i; + Vector normal; + vec_t dist; + + b = AllocBrush (6); + b->numsides = 6; + for (i=0 ; i<3 ; i++) + { + VectorClear (normal); + normal[i] = 1; + dist = maxs[i]; + b->sides[i].planenum = g_MainMap->FindFloatPlane (normal, dist); + + normal[i] = -1; + dist = -mins[i]; + b->sides[3+i].planenum = g_MainMap->FindFloatPlane (normal, dist); + } + + CreateBrushWindings (b); + + return b; +} + +/* +================== +BrushVolume + +================== +*/ +vec_t BrushVolume (bspbrush_t *brush) +{ + int i; + winding_t *w; + Vector corner; + vec_t d, area, volume; + plane_t *plane; + + if (!brush) + return 0; + + // grab the first valid point as the corner + + w = NULL; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (w) + break; + } + if (!w) + return 0; + VectorCopy (w->p[0], corner); + + // make tetrahedrons to all other faces + + volume = 0; + for ( ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + plane = &g_MainMap->mapplanes[brush->sides[i].planenum]; + d = -(DotProduct (corner, plane->normal) - plane->dist); + area = WindingArea (w); + volume += d*area; + } + + volume /= 3; + return volume; +} + +/* +================ +CountBrushList +================ +*/ +int CountBrushList (bspbrush_t *brushes) +{ + int c; + + c = 0; + for ( ; brushes ; brushes = brushes->next) + c++; + return c; +} + +/* +================ +AllocTree +================ +*/ +tree_t *AllocTree (void) +{ + tree_t *tree; + + tree = (tree_t*)malloc(sizeof(*tree)); + memset (tree, 0, sizeof(*tree)); + ClearBounds (tree->mins, tree->maxs); + + return tree; +} + +/* +================ +AllocNode +================ +*/ +node_t *AllocNode (void) +{ + static int s_NodeCount = 0; + + node_t *node; + + node = (node_t*)malloc(sizeof(*node)); + memset (node, 0, sizeof(*node)); + node->id = s_NodeCount; + node->diskId = -1; + + s_NodeCount++; + + return node; +} + + +/* +================ +AllocBrush +================ +*/ +bspbrush_t *AllocBrush (int numsides) +{ + static int s_BrushId = 0; + + bspbrush_t *bb; + int c; + + c = (int)&(((bspbrush_t *)0)->sides[numsides]); + bb = (bspbrush_t*)malloc(c); + memset (bb, 0, c); + bb->id = s_BrushId++; + if (numthreads == 1) + c_active_brushes++; + return bb; +} + +/* +================ +FreeBrush +================ +*/ +void FreeBrush (bspbrush_t *brushes) +{ + int i; + + for (i=0 ; inumsides ; i++) + if (brushes->sides[i].winding) + FreeWinding(brushes->sides[i].winding); + free (brushes); + if (numthreads == 1) + c_active_brushes--; +} + + +/* +================ +FreeBrushList +================ +*/ +void FreeBrushList (bspbrush_t *brushes) +{ + bspbrush_t *next; + + for ( ; brushes ; brushes = next) + { + next = brushes->next; + + FreeBrush (brushes); + } +} + +/* +================== +CopyBrush + +Duplicates the brush, the sides, and the windings +================== +*/ +bspbrush_t *CopyBrush (bspbrush_t *brush) +{ + bspbrush_t *newbrush; + int size; + int i; + + size = (int)&(((bspbrush_t *)0)->sides[brush->numsides]); + + newbrush = AllocBrush (brush->numsides); + memcpy (newbrush, brush, size); + + for (i=0 ; inumsides ; i++) + { + if (brush->sides[i].winding) + newbrush->sides[i].winding = CopyWinding (brush->sides[i].winding); + } + + return newbrush; +} + + +/* +================== +PointInLeaf + +================== +*/ +node_t *PointInLeaf (node_t *node, Vector& point) +{ + vec_t d; + plane_t *plane; + + while (node->planenum != PLANENUM_LEAF) + { + plane = &g_MainMap->mapplanes[node->planenum]; + if (plane->type < 3) + { + d = point[plane->type] - plane->dist; + } + else + { + d = DotProduct (point, plane->normal) - plane->dist; + } + + if (d >= 0) + node = node->children[0]; + else + node = node->children[1]; + } + + return node; +} + +//======================================================== + +/* +============== +BoxOnPlaneSide + +Returns PSIDE_FRONT, PSIDE_BACK, or PSIDE_BOTH +============== +*/ +int BrushBspBoxOnPlaneSide (const Vector& mins, const Vector& maxs, dplane_t *plane) +{ + int side; + int i; + Vector corners[2]; + vec_t dist1, dist2; + + // axial planes are easy + if (plane->type < 3) + { + side = 0; + if (maxs[plane->type] > plane->dist+PLANESIDE_EPSILON) + side |= PSIDE_FRONT; + if (mins[plane->type] < plane->dist-PLANESIDE_EPSILON) + side |= PSIDE_BACK; + return side; + } + + // create the proper leading and trailing verts for the box + + for (i=0 ; i<3 ; i++) + { + if (plane->normal[i] < 0) + { + corners[0][i] = mins[i]; + corners[1][i] = maxs[i]; + } + else + { + corners[1][i] = mins[i]; + corners[0][i] = maxs[i]; + } + } + + dist1 = DotProduct (plane->normal, corners[0]) - plane->dist; + dist2 = DotProduct (plane->normal, corners[1]) - plane->dist; + side = 0; + if (dist1 >= PLANESIDE_EPSILON) + side = PSIDE_FRONT; + if (dist2 < PLANESIDE_EPSILON) + side |= PSIDE_BACK; + + return side; +} + +/* +============ +QuickTestBrushToPlanenum + +============ +*/ +int QuickTestBrushToPlanenum (bspbrush_t *brush, int planenum, int *numsplits) +{ + int i, num; + plane_t *plane; + int s; + + *numsplits = 0; + + // if the brush actually uses the planenum, + // we can tell the side for sure + for (i=0 ; inumsides ; i++) + { + num = brush->sides[i].planenum; + if (num >= 0x10000) + Error ("bad planenum"); + if (num == planenum) + return PSIDE_BACK|PSIDE_FACING; + if (num == (planenum ^ 1) ) + return PSIDE_FRONT|PSIDE_FACING; + } + + // box on plane side + plane = &g_MainMap->mapplanes[planenum]; + s = BrushBspBoxOnPlaneSide (brush->mins, brush->maxs, plane); + + // if both sides, count the visible faces split + if (s == PSIDE_BOTH) + { + *numsplits += 3; + } + + return s; +} + +/* +============ +TestBrushToPlanenum + +============ +*/ +int TestBrushToPlanenum (bspbrush_t *brush, int planenum, + int *numsplits, qboolean *hintsplit, int *epsilonbrush) +{ + int i, j, num; + plane_t *plane; + int s; + winding_t *w; + vec_t d, d_front, d_back; + int front, back; + + *numsplits = 0; + *hintsplit = false; + + // if the brush actually uses the planenum, + // we can tell the side for sure + for (i=0 ; inumsides ; i++) + { + num = brush->sides[i].planenum; + if (num >= 0x10000) + Error ("bad planenum"); + if (num == planenum) + return PSIDE_BACK|PSIDE_FACING; + if (num == (planenum ^ 1) ) + return PSIDE_FRONT|PSIDE_FACING; + } + + // box on plane side + plane = &g_MainMap->mapplanes[planenum]; + s = BrushBspBoxOnPlaneSide (brush->mins, brush->maxs, plane); + + if (s != PSIDE_BOTH) + return s; + +// if both sides, count the visible faces split + d_front = d_back = 0; + + for (i=0 ; inumsides ; i++) + { + if (brush->sides[i].texinfo == TEXINFO_NODE) + continue; // on node, don't worry about splits + if (!brush->sides[i].visible) + continue; // we don't care about non-visible + w = brush->sides[i].winding; + if (!w) + continue; + + front = back = 0; + for (j=0 ; jnumpoints; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + + if (d > d_front) + d_front = d; + if (d < d_back) + d_back = d; + + if (d > 0.1) // PLANESIDE_EPSILON) + front = 1; + if (d < -0.1) // PLANESIDE_EPSILON) + back = 1; + } + + if (front && back) + { + if ( !(brush->sides[i].surf & SURF_SKIP) ) + { + (*numsplits)++; + if (brush->sides[i].surf & SURF_HINT) + *hintsplit = true; + } + } + } + + if ( (d_front > 0.0 && d_front < 1.0) + || (d_back < 0.0 && d_back > -1.0) ) + (*epsilonbrush)++; + +#if 0 + if (*numsplits == 0) + { // didn't really need to be split + if (front) + s = PSIDE_FRONT; + else if (back) + s = PSIDE_BACK; + else + s = 0; + } +#endif + + return s; +} + +//======================================================== + +/* +================ +WindingIsTiny + +Returns true if the winding would be crunched out of +existance by the vertex snapping. +================ +*/ +#define EDGE_LENGTH 0.2 +qboolean WindingIsTiny (winding_t *w) +{ + int i, j; + vec_t len; + Vector delta; + int edges; + + edges = 0; + for (i=0 ; inumpoints ; i++) + { + j = i == w->numpoints - 1 ? 0 : i+1; + VectorSubtract (w->p[j], w->p[i], delta); + len = VectorLength (delta); + if (len > EDGE_LENGTH) + { + if (++edges == 3) + return false; + } + } + return true; +} + + +// UNDONE: JAY: This should be a slightly better heuristic - it builds an OBB +// around the winding and tests planar dimensions. NOTE: This can fail when a +// winding normal cannot be constructed (or is degenerate), but that is probably +// desired in this case. +// UNDONE: Test & use this instead. +#if 0 +qboolean WindingIsTiny2 (winding_t *w) +{ + int i, j; + vec_t len; + Vector delta; + int edges; + + vec_t maxLen = 0; + Vector maxEdge = vec3_origin; + + edges = 0; + for (i=0 ; inumpoints ; i++) + { + j = i == w->numpoints - 1 ? 0 : i+1; + VectorSubtract (w->p[j], w->p[i], delta); + len = VectorLength (delta); + if (len > maxLen) + { + maxEdge = delta; + maxLen = len; + } + } + Vector normal; + vec_t dist; + WindingPlane (w, normal, &dist); // normal can come back vec3_origin in some cases + VectorNormalize(maxEdge); + Vector cross = CrossProduct(normal, maxEdge); + VectorNormalize(cross); + Vector mins, maxs; + ClearBounds( mins, maxs ); + for (i=0 ; inumpoints ; i++) + { + Vector point; + point.x = DotProduct( w->p[i], maxEdge ); + point.y = DotProduct( w->p[i], cross ); + point.z = DotProduct( w->p[i], normal ); + AddPointToBounds( point, mins, maxs ); + } + + // check to see if the size in the plane is too small in either dimension + Vector size = maxs - mins; + for ( i = 0; i < 2; i++ ) + { + if ( size[i] < EDGE_LENGTH ) + return true; + } + return false; +} +#endif + + +/* +================ +WindingIsHuge + +Returns true if the winding still has one of the points +from basewinding for plane +================ +*/ +qboolean WindingIsHuge (winding_t *w) +{ + int i, j; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + if (w->p[i][j] < MIN_COORD_INTEGER || w->p[i][j] > MAX_COORD_INTEGER) + return true; + } + return false; +} + +//============================================================ + +/* +================ +Leafnode +================ +*/ +void LeafNode (node_t *node, bspbrush_t *brushes) +{ + bspbrush_t *b; + int i; + + node->planenum = PLANENUM_LEAF; + node->contents = 0; + + for (b=brushes ; b ; b=b->next) + { + // if the brush is solid and all of its sides are on nodes, + // it eats everything + if (b->original->contents & CONTENTS_SOLID) + { + for (i=0 ; inumsides ; i++) + if (b->sides[i].texinfo != TEXINFO_NODE) + break; + if (i == b->numsides) + { + node->contents = CONTENTS_SOLID; + break; + } + } + node->contents |= b->original->contents; + } + + node->brushlist = brushes; +} + + +void RemoveAreaPortalBrushes_R( node_t *node ) +{ + if( node->planenum == PLANENUM_LEAF ) + { + // Remove any CONTENTS_AREAPORTAL brushes we added. We don't want them in the engine + // at runtime but we do want their flags in the leaves. + bspbrush_t **pPrev = &node->brushlist; + for( bspbrush_t *b=node->brushlist; b; b=b->next ) + { + if( b->original->contents == CONTENTS_AREAPORTAL ) + { + *pPrev = b->next; + } + else + { + pPrev = &b->next; + } + } + } + else + { + RemoveAreaPortalBrushes_R( node->children[0] ); + RemoveAreaPortalBrushes_R( node->children[1] ); + } +} + + +//============================================================ + +void CheckPlaneAgainstParents (int pnum, node_t *node) +{ + node_t *p; + + for (p=node->parent ; p ; p=p->parent) + { + if (p->planenum == pnum) + Error ("Tried parent"); + } +} + +qboolean CheckPlaneAgainstVolume (int pnum, node_t *node) +{ + bspbrush_t *front, *back; + qboolean good; + + SplitBrush (node->volume, pnum, &front, &back); + + good = (front && back); + + if (front) + FreeBrush (front); + if (back) + FreeBrush (back); + + return good; +} + +/* +================ +SelectSplitSide + +Using a hueristic, choses one of the sides out of the brushlist +to partition the brushes with. +Returns NULL if there are no valid planes to split with.. +================ +*/ + +side_t *SelectSplitSide (bspbrush_t *brushes, node_t *node) +{ + int value, bestvalue; + bspbrush_t *brush, *test; + side_t *side, *bestside; + int i, j, pass, numpasses; + int pnum; + int s; + int front, back, both, facing, splits; + int bsplits; + int bestsplits; + int epsilonbrush; + qboolean hintsplit = false; + + bestside = NULL; + bestvalue = -99999; + bestsplits = 0; + + // the search order goes: visible-structural, nonvisible-structural + // If any valid plane is available in a pass, no further + // passes will be tried. + numpasses = 2; + for (pass = 0 ; pass < numpasses ; pass++) + { + for (brush = brushes ; brush ; brush=brush->next) + { + for (i=0 ; inumsides ; i++) + { + side = brush->sides + i; + + if (side->bevel) + continue; // never use a bevel as a spliter + if (!side->winding) + continue; // nothing visible, so it can't split + if (side->texinfo == TEXINFO_NODE) + continue; // allready a node splitter + if (side->tested) + continue; // we allready have metrics for this plane + if (side->surf & SURF_SKIP) + continue; // skip surfaces are never chosen + if ( side->visible ^ (pass<1) ) + continue; // only check visible faces on first pass + + pnum = side->planenum; + pnum &= ~1; // allways use positive facing plane + + CheckPlaneAgainstParents (pnum, node); + + if (!CheckPlaneAgainstVolume (pnum, node)) + continue; // would produce a tiny volume + + front = 0; + back = 0; + both = 0; + facing = 0; + splits = 0; + epsilonbrush = 0; + + for (test = brushes ; test ; test=test->next) + { + s = TestBrushToPlanenum (test, pnum, &bsplits, &hintsplit, &epsilonbrush); + + splits += bsplits; + if (bsplits && (s&PSIDE_FACING) ) + Error ("PSIDE_FACING with splits"); + + test->testside = s; + // if the brush shares this face, don't bother + // testing that facenum as a splitter again + if (s & PSIDE_FACING) + { + facing++; + for (j=0 ; jnumsides ; j++) + { + if ( (test->sides[j].planenum&~1) == pnum) + test->sides[j].tested = true; + } + } + if (s & PSIDE_FRONT) + front++; + if (s & PSIDE_BACK) + back++; + if (s == PSIDE_BOTH) + both++; + } + + // give a value estimate for using this plane + value = 5*facing - 5*splits - abs(front-back); +// value = -5*splits; +// value = 5*facing - 5*splits; + if (g_MainMap->mapplanes[pnum].type < 3) + value+=5; // axial is better + value -= epsilonbrush*1000; // avoid! + + // trans should split last + if ( side->surf & SURF_TRANS ) + { + value -= 500; + } + + // never split a hint side except with another hint + if (hintsplit && !(side->surf & SURF_HINT) ) + value = -9999999; + + // water should split first + if (side->contents & (CONTENTS_WATER | CONTENTS_SLIME)) + value = 9999999; + + // save off the side test so we don't need + // to recalculate it when we actually seperate + // the brushes + if (value > bestvalue) + { + bestvalue = value; + bestside = side; + bestsplits = splits; + for (test = brushes ; test ; test=test->next) + test->side = test->testside; + } + } + } + + // if we found a good plane, don't bother trying any + // other passes + if (bestside) + { + if (pass > 0) + { + if (numthreads == 1) + c_nonvis++; + } + break; + } + } + + // + // clear all the tested flags we set + // + for (brush = brushes ; brush ; brush=brush->next) + { + for (i=0 ; inumsides ; i++) + brush->sides[i].tested = false; + } + + return bestside; +} + + +/* +================== +BrushMostlyOnSide + +================== +*/ +int BrushMostlyOnSide (bspbrush_t *brush, plane_t *plane) +{ + int i, j; + winding_t *w; + vec_t d, max; + int side; + + max = 0; + side = PSIDE_FRONT; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > max) + { + max = d; + side = PSIDE_FRONT; + } + if (-d > max) + { + max = -d; + side = PSIDE_BACK; + } + } + } + return side; +} + +/* +================ +SplitBrush + +Generates two new brushes, leaving the original +unchanged +================ +*/ + + +void SplitBrush( bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back ) +{ + bspbrush_t *b[2]; + int i, j; + winding_t *w, *cw[2], *midwinding; + plane_t *plane, *plane2; + side_t *s, *cs; + float d, d_front, d_back; + + *front = *back = NULL; + plane = &g_MainMap->mapplanes[planenum]; + + // check all points + d_front = d_back = 0; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > 0 && d > d_front) + d_front = d; + if (d < 0 && d < d_back) + d_back = d; + } + } + + if (d_front < 0.1) // PLANESIDE_EPSILON) + { // only on back + *back = CopyBrush (brush); + return; + } + if (d_back > -0.1) // PLANESIDE_EPSILON) + { // only on front + *front = CopyBrush (brush); + return; + } + + + // Move the CSG problem so that offset is at the origin + // This gives us much better floating point precision in the clipping operations + Vector offset = -0.5f * (brush->mins + brush->maxs); + // create a new winding from the split plane + + w = BaseWindingForPlane (plane->normal, plane->dist + DotProduct(plane->normal,offset)); + for (i=0 ; inumsides && w ; i++) + { + plane2 = &g_MainMap->mapplanes[brush->sides[i].planenum ^ 1]; + ChopWindingInPlace (&w, plane2->normal, plane2->dist+DotProduct(plane2->normal,offset), 0); // PLANESIDE_EPSILON); + } + + if (!w || WindingIsTiny (w) ) + { // the brush isn't really split + int side; + + side = BrushMostlyOnSide (brush, plane); + if (side == PSIDE_FRONT) + *front = CopyBrush (brush); + if (side == PSIDE_BACK) + *back = CopyBrush (brush); + return; + } + + if (WindingIsHuge (w)) + { + qprintf ("WARNING: huge winding\n"); + } + + TranslateWinding( w, -offset ); + midwinding = w; + + // + // + // split it for real + // + // + + // + // allocate two new brushes referencing the original + // + for( i = 0; i < 2; i++ ) + { + b[i] = AllocBrush( brush->numsides + 1 ); + b[i]->original = brush->original; + } + + // + // split all the current windings + // + for( i = 0; i < brush->numsides; i++ ) + { + // get the current side + s = &brush->sides[i]; + + // get the sides winding + w = s->winding; + if( !w ) + continue; + + // clip the winding + ClipWindingEpsilon_Offset( w, plane->normal, plane->dist, 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1], offset ); + + for( j = 0; j < 2; j++ ) + { + // does winding exist? + if( !cw[j] ) + continue; +#if 0 + if (WindingIsTiny (cw[j])) + { + FreeWinding (cw[j]); + continue; + } +#endif + + // + // create a clipped "side" with the new winding + // + cs = &b[j]->sides[b[j]->numsides]; + b[j]->numsides++; + *cs = *s; + cs->winding = cw[j]; + cs->tested = false; + // save the original side information + //cs->original = s->original; + } + } + + + // see if we have valid polygons on both sides + + for (i=0 ; i<2 ; i++) + { + BoundBrush (b[i]); + for (j=0 ; j<3 ; j++) + { + if (b[i]->mins[j] < MIN_COORD_INTEGER || b[i]->maxs[j] > MAX_COORD_INTEGER) + { + qprintf ("bogus brush after clip\n"); + break; + } + } + + if (b[i]->numsides < 3 || j < 3) + { + FreeBrush (b[i]); + b[i] = NULL; + } + } + + if ( !(b[0] && b[1]) ) + { + if (!b[0] && !b[1]) + qprintf ("split removed brush\n"); + else + qprintf ("split not on both sides\n"); + if (b[0]) + { + FreeBrush (b[0]); + *front = CopyBrush (brush); + } + if (b[1]) + { + FreeBrush (b[1]); + *back = CopyBrush (brush); + } + return; + } + + // add the midwinding to both sides + for (i=0 ; i<2 ; i++) + { + cs = &b[i]->sides[b[i]->numsides]; + b[i]->numsides++; + + cs->planenum = planenum^i^1; + cs->texinfo = TEXINFO_NODE; + + // initialize the displacement map index + cs->pMapDisp = NULL; + + cs->visible = false; + cs->tested = false; + if (i==0) + cs->winding = CopyWinding (midwinding); + else + cs->winding = midwinding; + } + +{ + vec_t v1; + int i; + + for (i=0 ; i<2 ; i++) + { + v1 = BrushVolume (b[i]); + if (v1 < 1.0) + { + FreeBrush (b[i]); + b[i] = NULL; +// qprintf ("tiny volume after clip\n"); + } + } +} + + *front = b[0]; + *back = b[1]; +} + + +/* +================ +SplitBrushList +================ +*/ +void SplitBrushList (bspbrush_t *brushes, + node_t *node, bspbrush_t **front, bspbrush_t **back) +{ + bspbrush_t *brush, *newbrush, *newbrush2; + side_t *side; + int sides; + int i; + + *front = *back = NULL; + + for (brush = brushes ; brush ; brush=brush->next) + { + sides = brush->side; + + if (sides == PSIDE_BOTH) + { // split into two brushes + SplitBrush (brush, node->planenum, &newbrush, &newbrush2); + if (newbrush) + { + newbrush->next = *front; + *front = newbrush; + } + if (newbrush2) + { + newbrush2->next = *back; + *back = newbrush2; + } + continue; + } + + newbrush = CopyBrush (brush); + + // if the planenum is actualy a part of the brush + // find the plane and flag it as used so it won't be tried + // as a splitter again + if (sides & PSIDE_FACING) + { + for (i=0 ; inumsides ; i++) + { + side = newbrush->sides + i; + if ( (side->planenum& ~1) == node->planenum) + side->texinfo = TEXINFO_NODE; + } + } + + + if (sides & PSIDE_FRONT) + { + newbrush->next = *front; + *front = newbrush; + continue; + } + if (sides & PSIDE_BACK) + { + newbrush->next = *back; + *back = newbrush; + continue; + } + } +} + + +/* +================ +BuildTree_r +================ +*/ + + +node_t *BuildTree_r (node_t *node, bspbrush_t *brushes) +{ + node_t *newnode; + side_t *bestside; + int i; + bspbrush_t *children[2]; + + if (numthreads == 1) + c_nodes++; + + // find the best plane to use as a splitter + bestside = SelectSplitSide (brushes, node); + + if (!bestside) + { + // leaf node + node->side = NULL; + node->planenum = -1; + LeafNode (node, brushes); + return node; + } + + // this is a splitplane node + node->side = bestside; + node->planenum = bestside->planenum & ~1; // always use front facing + + SplitBrushList (brushes, node, &children[0], &children[1]); + FreeBrushList (brushes); + + // allocate children before recursing + for (i=0 ; i<2 ; i++) + { + newnode = AllocNode (); + newnode->parent = node; + node->children[i] = newnode; + } + + SplitBrush (node->volume, node->planenum, &node->children[0]->volume, + &node->children[1]->volume); + + // recursively process children + for (i=0 ; i<2 ; i++) + { + node->children[i] = BuildTree_r (node->children[i], children[i]); + } + + return node; +} + + +//=========================================================== + +/* +================= +BrushBSP + +The incoming list will be freed before exiting +================= +*/ +tree_t *BrushBSP (bspbrush_t *brushlist, Vector& mins, Vector& maxs) +{ + node_t *node; + bspbrush_t *b; + int c_faces, c_nonvisfaces; + int c_brushes; + tree_t *tree; + int i; + vec_t volume; + + qprintf ("--- BrushBSP ---\n"); + + tree = AllocTree (); + + c_faces = 0; + c_nonvisfaces = 0; + c_brushes = 0; + for (b=brushlist ; b ; b=b->next) + { + c_brushes++; + + volume = BrushVolume (b); + if (volume < microvolume) + { + Warning("Brush %i: WARNING, microbrush\n", b->original->id); + } + + for (i=0 ; inumsides ; i++) + { + if (b->sides[i].bevel) + continue; + if (!b->sides[i].winding) + continue; + if (b->sides[i].texinfo == TEXINFO_NODE) + continue; + if (b->sides[i].visible) + c_faces++; + else + c_nonvisfaces++; + } + + AddPointToBounds (b->mins, tree->mins, tree->maxs); + AddPointToBounds (b->maxs, tree->mins, tree->maxs); + } + + qprintf ("%5i brushes\n", c_brushes); + qprintf ("%5i visible faces\n", c_faces); + qprintf ("%5i nonvisible faces\n", c_nonvisfaces); + + c_nodes = 0; + c_nonvis = 0; + node = AllocNode (); + + node->volume = BrushFromBounds (mins, maxs); + + tree->headnode = node; + + node = BuildTree_r (node, brushlist); + qprintf ("%5i visible nodes\n", c_nodes/2 - c_nonvis); + qprintf ("%5i nonvis nodes\n", c_nonvis); + qprintf ("%5i leafs\n", (c_nodes+1)/2); +#if 0 +{ // debug code +static node_t *tnode; +Vector p; + +p[0] = -1469; +p[1] = -118; +p[2] = 119; +tnode = PointInLeaf (tree->headnode, p); +Msg("contents: %i\n", tnode->contents); +p[0] = 0; +} +#endif + return tree; +} + diff --git a/mp/src/utils/vbsp/csg.cpp b/mp/src/utils/vbsp/csg.cpp index c4b89bd9..5de1d680 100644 --- a/mp/src/utils/vbsp/csg.cpp +++ b/mp/src/utils/vbsp/csg.cpp @@ -1,784 +1,784 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "vbsp.h" - -/* - -tag all brushes with original contents -brushes may contain multiple contents -there will be no brush overlap after csg phase - - - - -each side has a count of the other sides it splits - -the best split will be the one that minimizes the total split counts -of all remaining sides - -precalc side on plane table - -evaluate split side -{ -cost = 0 -for all sides - for all sides - get - if side splits side and splitside is on same child - cost++; -} - - - */ - -void SplitBrush2( bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back ) -{ - SplitBrush( brush, planenum, front, back ); -#if 0 - if (*front && (*front)->sides[(*front)->numsides-1].texinfo == -1) - (*front)->sides[(*front)->numsides-1].texinfo = (*front)->sides[0].texinfo; // not -1 - if (*back && (*back)->sides[(*back)->numsides-1].texinfo == -1) - (*back)->sides[(*back)->numsides-1].texinfo = (*back)->sides[0].texinfo; // not -1 -#endif -} - -/* -=============== -SubtractBrush - -Returns a list of brushes that remain after B is subtracted from A. -May by empty if A is contained inside B. - -The originals are undisturbed. -=============== -*/ -bspbrush_t *SubtractBrush (bspbrush_t *a, bspbrush_t *b) -{ // a - b = out (list) - int i; - bspbrush_t *front, *back; - bspbrush_t *out, *in; - - in = a; - out = NULL; - for (i=0 ; inumsides && in ; i++) - { - SplitBrush2 (in, b->sides[i].planenum, &front, &back); - if (in != a) - FreeBrush (in); - if (front) - { // add to list - front->next = out; - out = front; - } - in = back; - } - if (in) - FreeBrush (in); - else - { // didn't really intersect - FreeBrushList (out); - return a; - } - return out; -} - -/* -=============== -IntersectBrush - -Returns a single brush made up by the intersection of the -two provided brushes, or NULL if they are disjoint. - -The originals are undisturbed. -=============== -*/ -bspbrush_t *IntersectBrush (bspbrush_t *a, bspbrush_t *b) -{ - int i; - bspbrush_t *front, *back; - bspbrush_t *in; - - in = a; - for (i=0 ; inumsides && in ; i++) - { - SplitBrush2 (in, b->sides[i].planenum, &front, &back); - if (in != a) - FreeBrush (in); - if (front) - FreeBrush (front); - in = back; - } - - if (in == a || !in) - return NULL; - - in->next = NULL; - return in; -} - - -/* -=============== -BrushesDisjoint - -Returns true if the two brushes definately do not intersect. -There will be false negatives for some non-axial combinations. -=============== -*/ -qboolean BrushesDisjoint (bspbrush_t *a, bspbrush_t *b) -{ - int i, j; - - // check bounding boxes - for (i=0 ; i<3 ; i++) - if (a->mins[i] >= b->maxs[i] - || a->maxs[i] <= b->mins[i]) - return true; // bounding boxes don't overlap - - // check for opposing planes - for (i=0 ; inumsides ; i++) - { - for (j=0 ; jnumsides ; j++) - { - if (a->sides[i].planenum == - (b->sides[j].planenum^1) ) - return true; // opposite planes, so not touching - } - } - - return false; // might intersect -} - - -int minplanenums[3]; -int maxplanenums[3]; - -/* -=============== -ClipBrushToBox - -Any planes shared with the box edge will be set to no texinfo -=============== -*/ -bspbrush_t *ClipBrushToBox (bspbrush_t *brush, const Vector& clipmins, const Vector& clipmaxs) -{ - int i, j; - bspbrush_t *front, *back; - int p; - - for (j=0 ; j<2 ; j++) - { - if (brush->maxs[j] > clipmaxs[j]) - { - SplitBrush (brush, maxplanenums[j], &front, &back); - if (front) - FreeBrush (front); - brush = back; - if (!brush) - return NULL; - } - if (brush->mins[j] < clipmins[j]) - { - SplitBrush (brush, minplanenums[j], &front, &back); - if (back) - FreeBrush (back); - brush = front; - if (!brush) - return NULL; - } - } - - // remove any colinear faces - - for (i=0 ; inumsides ; i++) - { - p = brush->sides[i].planenum & ~1; - if (p == maxplanenums[0] || p == maxplanenums[1] - || p == minplanenums[0] || p == minplanenums[1]) - { - brush->sides[i].texinfo = TEXINFO_NODE; - brush->sides[i].visible = false; - } - } - return brush; -} - - -//----------------------------------------------------------------------------- -// Creates a clipped brush from a map brush -//----------------------------------------------------------------------------- -static bspbrush_t *CreateClippedBrush( mapbrush_t *mb, const Vector& clipmins, const Vector& clipmaxs ) -{ - int nNumSides = mb->numsides; - if (!nNumSides) - return NULL; - - // if the brush is outside the clip area, skip it - for (int j=0 ; j<3 ; j++) - { - if (mb->mins[j] >= clipmaxs[j] || mb->maxs[j] <= clipmins[j]) - { - return NULL; - } - } - - // make a copy of the brush - bspbrush_t *newbrush = AllocBrush( nNumSides ); - newbrush->original = mb; - newbrush->numsides = nNumSides; - memcpy (newbrush->sides, mb->original_sides, nNumSides*sizeof(side_t)); - - for (int j=0 ; jsides[j].winding) - { - newbrush->sides[j].winding = CopyWinding (newbrush->sides[j].winding); - } - - if (newbrush->sides[j].surf & SURF_HINT) - { - newbrush->sides[j].visible = true; // hints are always visible - } - - // keep a pointer to the original map brush side -- use to create the original face later!! - //newbrush->sides[j].original = &mb->original_sides[j]; - } - - VectorCopy (mb->mins, newbrush->mins); - VectorCopy (mb->maxs, newbrush->maxs); - - // carve off anything outside the clip box - newbrush = ClipBrushToBox (newbrush, clipmins, clipmaxs); - return newbrush; -} - - -//----------------------------------------------------------------------------- -// Creates a clipped brush from a map brush -//----------------------------------------------------------------------------- -static void ComputeBoundingPlanes( const Vector& clipmins, const Vector& clipmaxs ) -{ - Vector normal; - float dist; - for (int i=0 ; i<2 ; i++) - { - VectorClear (normal); - normal[i] = 1; - dist = clipmaxs[i]; - maxplanenums[i] = g_MainMap->FindFloatPlane (normal, dist); - dist = clipmins[i]; - minplanenums[i] = g_MainMap->FindFloatPlane (normal, dist); - } -} - - -//----------------------------------------------------------------------------- -// This forces copies of texinfo data for matching sides of a brush -//----------------------------------------------------------------------------- -void CopyMatchingTexinfos( side_t *pDestSides, int numDestSides, const bspbrush_t *pSource ) -{ - for ( int i = 0; i < numDestSides; i++ ) - { - side_t *pSide = &pDestSides[i]; - plane_t *pPlane = &g_MainMap->mapplanes[pSide->planenum]; - - // We have to use the *original sides* because MapBSPBrushList could have generated - // splits when cutting the original brush to the block being processed. This - // will generate faces that use TEXINFO_NODE, which is definitely *not* what we want. - // If we end up with faces using TEXINFO_NODE here, the area portal will flood into - // the entire water volume intersecting the areaportal. - - mapbrush_t *pSourceBrush = pSource->original; - Assert( pSourceBrush ); - - const side_t *pSourceSide = pSourceBrush->original_sides; - const side_t *pBestSide = NULL; - float flBestDot = -1.0f; - for ( int j = 0; j < pSourceBrush->numsides; ++j, ++pSourceSide ) - { - if ( pSourceSide->texinfo == TEXINFO_NODE ) - continue; - - plane_t *pSourcePlane = &g_MainMap->mapplanes[pSourceSide->planenum]; - float flDot = DotProduct( pPlane->normal, pSourcePlane->normal ); - if ( flDot == 1.0f || pSide->planenum == pSourceSide->planenum ) - { - pBestSide = pSourceSide; - break; - } - else if ( flDot > flBestDot ) - { - pBestSide = pSourceSide; - flBestDot = flDot; - } - } - - if ( pBestSide ) - { - pSide->texinfo = pBestSide->texinfo; - if ( pSide->original ) - { - pSide->original->texinfo = pSide->texinfo; - } - } - else - { - texinfo_t *pTexInfo = &texinfo[pSide->texinfo]; - dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); - Msg("Found no matching plane for %s\n", TexDataStringTable_GetString( pTexData->nameStringTableID ) ); - } - } -} - -// This is a hack to allow areaportals to work in water -// It was done this way for ease of implementation. -// This searches a brush list to find intersecting areaportals and water -// If an areaportal is found inside water, then the water contents and -// texture information is copied over to the areaportal so that the -// resulting space has the same properties as the water (normal areaportals assume "empty" surroundings) -void FixupAreaportalWaterBrushes( bspbrush_t *pList ) -{ - for ( bspbrush_t *pAreaportal = pList; pAreaportal; pAreaportal = pAreaportal->next ) - { - if ( !(pAreaportal->original->contents & CONTENTS_AREAPORTAL) ) - continue; - - for ( bspbrush_t *pWater = pList; pWater; pWater = pWater->next ) - { - // avoid using areaportal/water combo brushes that have already been fixed up - if ( pWater->original->contents & CONTENTS_AREAPORTAL ) - continue; - - if ( !(pWater->original->contents & MASK_SPLITAREAPORTAL) ) - continue; - - if ( BrushesDisjoint( pAreaportal, pWater ) ) - continue; - - bspbrush_t *pIntersect = IntersectBrush( pAreaportal, pWater ); - if ( !pIntersect ) - continue; - FreeBrush( pIntersect ); - pAreaportal->original->contents |= pWater->original->contents; - - // HACKHACK: Ideally, this should have been done before the bspbrush_t was - // created from the map brush. But since it hasn't been, retexture the original map - // brush's sides - CopyMatchingTexinfos( pAreaportal->sides, pAreaportal->numsides, pWater ); - CopyMatchingTexinfos( pAreaportal->original->original_sides, pAreaportal->original->numsides, pWater ); - } - } -} - - -//----------------------------------------------------------------------------- -// MakeBspBrushList -//----------------------------------------------------------------------------- -// UNDONE: Put detail brushes in a separate brush array and pass that instead of "onlyDetail" ? -bspbrush_t *MakeBspBrushList (int startbrush, int endbrush, const Vector& clipmins, const Vector& clipmaxs, int detailScreen) -{ - ComputeBoundingPlanes( clipmins, clipmaxs ); - - bspbrush_t *pBrushList = NULL; - - int i; - for (i=startbrush ; imapbrushes[i]; - if ( detailScreen != FULL_DETAIL ) - { - bool onlyDetail = (detailScreen == ONLY_DETAIL); - bool detail = (mb->contents & CONTENTS_DETAIL) != 0; - if ( onlyDetail ^ detail ) - { - // both of these must have the same value or we're not interested in this brush - continue; - } - } - - bspbrush_t *pNewBrush = CreateClippedBrush( mb, clipmins, clipmaxs ); - if ( pNewBrush ) - { - pNewBrush->next = pBrushList; - pBrushList = pNewBrush; - } - } - - return pBrushList; -} - - -//----------------------------------------------------------------------------- -// A version which uses a passed-in list of brushes -//----------------------------------------------------------------------------- -bspbrush_t *MakeBspBrushList (mapbrush_t **pBrushes, int nBrushCount, const Vector& clipmins, const Vector& clipmaxs) -{ - ComputeBoundingPlanes( clipmins, clipmaxs ); - - bspbrush_t *pBrushList = NULL; - for ( int i=0; i < nBrushCount; ++i ) - { - bspbrush_t *pNewBrush = CreateClippedBrush( pBrushes[i], clipmins, clipmaxs ); - if ( pNewBrush ) - { - pNewBrush->next = pBrushList; - pBrushList = pNewBrush; - } - } - - return pBrushList; -} - - -/* -=============== -AddBspBrushListToTail -=============== -*/ -bspbrush_t *AddBrushListToTail (bspbrush_t *list, bspbrush_t *tail) -{ - bspbrush_t *walk, *next; - - for (walk=list ; walk ; walk=next) - { // add to end of list - next = walk->next; - walk->next = NULL; - tail->next = walk; - tail = walk; - } - - return tail; -} - -/* -=========== -CullList - -Builds a new list that doesn't hold the given brush -=========== -*/ -bspbrush_t *CullList (bspbrush_t *list, bspbrush_t *skip1) -{ - bspbrush_t *newlist; - bspbrush_t *next; - - newlist = NULL; - - for ( ; list ; list = next) - { - next = list->next; - if (list == skip1) - { - FreeBrush (list); - continue; - } - list->next = newlist; - newlist = list; - } - return newlist; -} - - -/* -================== -WriteBrushMap -================== -*/ -void WriteBrushMap (char *name, bspbrush_t *list) -{ - FILE *f; - side_t *s; - int i; - winding_t *w; - - Msg("writing %s\n", name); - f = fopen (name, "w"); - if (!f) - Error ("Can't write %s\b", name); - - fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); - - for ( ; list ; list=list->next ) - { - fprintf (f, "{\n"); - for (i=0,s=list->sides ; inumsides ; i++,s++) - { - w = BaseWindingForPlane (g_MainMap->mapplanes[s->planenum].normal, g_MainMap->mapplanes[s->planenum].dist); - - fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); - fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); - fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); - - fprintf (f, "%s 0 0 0 1 1\n", TexDataStringTable_GetString( GetTexData( texinfo[s->texinfo].texdata )->nameStringTableID ) ); - FreeWinding (w); - } - fprintf (f, "}\n"); - } - fprintf (f, "}\n"); - - fclose (f); - -} - -// UNDONE: This isn't quite working yet -#if 0 -void WriteBrushVMF(char *name, bspbrush_t *list) -{ - FILE *f; - side_t *s; - int i; - winding_t *w; - Vector u, v; - - Msg("writing %s\n", name); - f = fopen (name, "w"); - if (!f) - Error ("Can't write %s\b", name); - - fprintf (f, "world\n{\n\"classname\" \"worldspawn\"\n"); - - for ( ; list ; list=list->next ) - { - fprintf (f, "\tsolid\n\t{\n"); - for (i=0,s=list->sides ; inumsides ; i++,s++) - { - fprintf( f, "\t\tside\n\t\t{\n" ); - fprintf( f, "\t\t\t\"plane\" \"" ); - w = BaseWindingForPlane (mapplanes[s->planenum].normal, mapplanes[s->planenum].dist); - - fprintf (f,"(%i %i %i) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); - fprintf (f,"(%i %i %i) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); - fprintf (f,"(%i %i %i)", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); - fprintf( f, "\"\n" ); - fprintf( f, "\t\t\t\"material\" \"%s\"\n", GetTexData( texinfo[s->texinfo].texdata )->name ); - // UNDONE: recreate correct texture axes - BasisForPlane( mapplanes[s->planenum].normal, u, v ); - fprintf( f, "\t\t\t\"uaxis\" \"[%.3f %.3f %.3f 0] 1.0\"\n", u[0], u[1], u[2] ); - fprintf( f, "\t\t\t\"vaxis\" \"[%.3f %.3f %.3f 0] 1.0\"\n", v[0], v[1], v[2] ); - - fprintf( f, "\t\t\t\"rotation\" \"0.0\"\n" ); - fprintf( f, "\t\t\t\"lightmapscale\" \"16.0\"\n" ); - - FreeWinding (w); - fprintf (f, "\t\t}\n"); - } - fprintf (f, "\t}\n"); - } - fprintf (f, "}\n"); - - fclose (f); - -} -#endif - -void PrintBrushContentsToString( int contents, char *pOut, int nMaxChars ) -{ - #define ADD_CONTENTS( flag ) \ - if ( contents & flag ) \ - Q_strncat( pOut, #flag " ", nMaxChars, COPY_ALL_CHARACTERS ); - - pOut[0] = 0; - - ADD_CONTENTS(CONTENTS_SOLID) - ADD_CONTENTS(CONTENTS_WINDOW) - ADD_CONTENTS(CONTENTS_AUX) - ADD_CONTENTS(CONTENTS_GRATE) - ADD_CONTENTS(CONTENTS_SLIME) - ADD_CONTENTS(CONTENTS_WATER) - ADD_CONTENTS(CONTENTS_BLOCKLOS) - ADD_CONTENTS(CONTENTS_OPAQUE) - ADD_CONTENTS(CONTENTS_TESTFOGVOLUME) - ADD_CONTENTS(CONTENTS_MOVEABLE) - ADD_CONTENTS(CONTENTS_AREAPORTAL) - ADD_CONTENTS(CONTENTS_PLAYERCLIP) - ADD_CONTENTS(CONTENTS_MONSTERCLIP) - ADD_CONTENTS(CONTENTS_CURRENT_0) - ADD_CONTENTS(CONTENTS_CURRENT_90) - ADD_CONTENTS(CONTENTS_CURRENT_180) - ADD_CONTENTS(CONTENTS_CURRENT_270) - ADD_CONTENTS(CONTENTS_CURRENT_UP) - ADD_CONTENTS(CONTENTS_CURRENT_DOWN) - ADD_CONTENTS(CONTENTS_ORIGIN) - ADD_CONTENTS(CONTENTS_MONSTER) - ADD_CONTENTS(CONTENTS_DEBRIS) - ADD_CONTENTS(CONTENTS_DETAIL) - ADD_CONTENTS(CONTENTS_TRANSLUCENT) - ADD_CONTENTS(CONTENTS_LADDER) - ADD_CONTENTS(CONTENTS_HITBOX) -} - -void PrintBrushContents( int contents ) -{ - char str[1024]; - PrintBrushContentsToString( contents, str, sizeof( str ) ); - Msg( "%s", str ); -} - -/* -================== -BrushGE - -Returns true if b1 is allowed to bite b2 -================== -*/ -qboolean BrushGE (bspbrush_t *b1, bspbrush_t *b2) -{ - // Areaportals are allowed to bite water + slime - // NOTE: This brush combo should have been fixed up - // in a first pass (FixupAreaportalWaterBrushes) - if( (b2->original->contents & MASK_SPLITAREAPORTAL) && - (b1->original->contents & CONTENTS_AREAPORTAL) ) - { - return true; - } - - // detail brushes never bite structural brushes - if ( (b1->original->contents & CONTENTS_DETAIL) - && !(b2->original->contents & CONTENTS_DETAIL) ) - return false; - if (b1->original->contents & CONTENTS_SOLID) - return true; - // Transparent brushes are not marked as detail anymore, so let them cut each other. - if ( (b1->original->contents & TRANSPARENT_CONTENTS) && (b2->original->contents & TRANSPARENT_CONTENTS) ) - return true; - - return false; -} - -/* -================= -ChopBrushes - -Carves any intersecting solid brushes into the minimum number -of non-intersecting brushes. -================= -*/ -bspbrush_t *ChopBrushes (bspbrush_t *head) -{ - bspbrush_t *b1, *b2, *next; - bspbrush_t *tail; - bspbrush_t *keep; - bspbrush_t *sub, *sub2; - int c1, c2; - - qprintf ("---- ChopBrushes ----\n"); - qprintf ("original brushes: %i\n", CountBrushList (head)); - -#if DEBUG_BRUSHMODEL - if (entity_num == DEBUG_BRUSHMODEL) - WriteBrushList ("before.gl", head, false); -#endif - keep = NULL; - -newlist: - // find tail - if (!head) - return NULL; - for (tail=head ; tail->next ; tail=tail->next) - ; - - for (b1=head ; b1 ; b1=next) - { - next = b1->next; - for (b2=b1->next ; b2 ; b2 = b2->next) - { - if (BrushesDisjoint (b1, b2)) - continue; - - sub = NULL; - sub2 = NULL; - c1 = 999999; - c2 = 999999; - - if ( BrushGE (b2, b1) ) - { -// printf( "b2 bites b1\n" ); - sub = SubtractBrush (b1, b2); - if (sub == b1) - continue; // didn't really intersect - if (!sub) - { // b1 is swallowed by b2 - head = CullList (b1, b1); - goto newlist; - } - c1 = CountBrushList (sub); - } - - if ( BrushGE (b1, b2) ) - { -// printf( "b1 bites b2\n" ); - sub2 = SubtractBrush (b2, b1); - if (sub2 == b2) - continue; // didn't really intersect - if (!sub2) - { // b2 is swallowed by b1 - FreeBrushList (sub); - head = CullList (b1, b2); - goto newlist; - } - c2 = CountBrushList (sub2); - } - - if (!sub && !sub2) - continue; // neither one can bite - - // only accept if it didn't fragment - // (commening this out allows full fragmentation) - if (c1 > 1 && c2 > 1) - { - const int contents1 = b1->original->contents; - const int contents2 = b2->original->contents; - // if both detail, allow fragmentation - if ( !((contents1&contents2) & CONTENTS_DETAIL) && !((contents1|contents2) & CONTENTS_AREAPORTAL) ) - { - if (sub2) - FreeBrushList (sub2); - if (sub) - FreeBrushList (sub); - continue; - } - } - - if (c1 < c2) - { - if (sub2) - FreeBrushList (sub2); - tail = AddBrushListToTail (sub, tail); - head = CullList (b1, b1); - goto newlist; - } - else - { - if (sub) - FreeBrushList (sub); - tail = AddBrushListToTail (sub2, tail); - head = CullList (b1, b2); - goto newlist; - } - } - - if (!b2) - { // b1 is no longer intersecting anything, so keep it - b1->next = keep; - keep = b1; - } - } - - qprintf ("output brushes: %i\n", CountBrushList (keep)); -#if DEBUG_BRUSHMODEL - if ( entity_num == DEBUG_BRUSHMODEL ) - { - WriteBrushList ("after.gl", keep, false); - WriteBrushMap ("after.map", keep); - } -#endif - return keep; -} - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" + +/* + +tag all brushes with original contents +brushes may contain multiple contents +there will be no brush overlap after csg phase + + + + +each side has a count of the other sides it splits + +the best split will be the one that minimizes the total split counts +of all remaining sides + +precalc side on plane table + +evaluate split side +{ +cost = 0 +for all sides + for all sides + get + if side splits side and splitside is on same child + cost++; +} + + + */ + +void SplitBrush2( bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back ) +{ + SplitBrush( brush, planenum, front, back ); +#if 0 + if (*front && (*front)->sides[(*front)->numsides-1].texinfo == -1) + (*front)->sides[(*front)->numsides-1].texinfo = (*front)->sides[0].texinfo; // not -1 + if (*back && (*back)->sides[(*back)->numsides-1].texinfo == -1) + (*back)->sides[(*back)->numsides-1].texinfo = (*back)->sides[0].texinfo; // not -1 +#endif +} + +/* +=============== +SubtractBrush + +Returns a list of brushes that remain after B is subtracted from A. +May by empty if A is contained inside B. + +The originals are undisturbed. +=============== +*/ +bspbrush_t *SubtractBrush (bspbrush_t *a, bspbrush_t *b) +{ // a - b = out (list) + int i; + bspbrush_t *front, *back; + bspbrush_t *out, *in; + + in = a; + out = NULL; + for (i=0 ; inumsides && in ; i++) + { + SplitBrush2 (in, b->sides[i].planenum, &front, &back); + if (in != a) + FreeBrush (in); + if (front) + { // add to list + front->next = out; + out = front; + } + in = back; + } + if (in) + FreeBrush (in); + else + { // didn't really intersect + FreeBrushList (out); + return a; + } + return out; +} + +/* +=============== +IntersectBrush + +Returns a single brush made up by the intersection of the +two provided brushes, or NULL if they are disjoint. + +The originals are undisturbed. +=============== +*/ +bspbrush_t *IntersectBrush (bspbrush_t *a, bspbrush_t *b) +{ + int i; + bspbrush_t *front, *back; + bspbrush_t *in; + + in = a; + for (i=0 ; inumsides && in ; i++) + { + SplitBrush2 (in, b->sides[i].planenum, &front, &back); + if (in != a) + FreeBrush (in); + if (front) + FreeBrush (front); + in = back; + } + + if (in == a || !in) + return NULL; + + in->next = NULL; + return in; +} + + +/* +=============== +BrushesDisjoint + +Returns true if the two brushes definately do not intersect. +There will be false negatives for some non-axial combinations. +=============== +*/ +qboolean BrushesDisjoint (bspbrush_t *a, bspbrush_t *b) +{ + int i, j; + + // check bounding boxes + for (i=0 ; i<3 ; i++) + if (a->mins[i] >= b->maxs[i] + || a->maxs[i] <= b->mins[i]) + return true; // bounding boxes don't overlap + + // check for opposing planes + for (i=0 ; inumsides ; i++) + { + for (j=0 ; jnumsides ; j++) + { + if (a->sides[i].planenum == + (b->sides[j].planenum^1) ) + return true; // opposite planes, so not touching + } + } + + return false; // might intersect +} + + +int minplanenums[3]; +int maxplanenums[3]; + +/* +=============== +ClipBrushToBox + +Any planes shared with the box edge will be set to no texinfo +=============== +*/ +bspbrush_t *ClipBrushToBox (bspbrush_t *brush, const Vector& clipmins, const Vector& clipmaxs) +{ + int i, j; + bspbrush_t *front, *back; + int p; + + for (j=0 ; j<2 ; j++) + { + if (brush->maxs[j] > clipmaxs[j]) + { + SplitBrush (brush, maxplanenums[j], &front, &back); + if (front) + FreeBrush (front); + brush = back; + if (!brush) + return NULL; + } + if (brush->mins[j] < clipmins[j]) + { + SplitBrush (brush, minplanenums[j], &front, &back); + if (back) + FreeBrush (back); + brush = front; + if (!brush) + return NULL; + } + } + + // remove any colinear faces + + for (i=0 ; inumsides ; i++) + { + p = brush->sides[i].planenum & ~1; + if (p == maxplanenums[0] || p == maxplanenums[1] + || p == minplanenums[0] || p == minplanenums[1]) + { + brush->sides[i].texinfo = TEXINFO_NODE; + brush->sides[i].visible = false; + } + } + return brush; +} + + +//----------------------------------------------------------------------------- +// Creates a clipped brush from a map brush +//----------------------------------------------------------------------------- +static bspbrush_t *CreateClippedBrush( mapbrush_t *mb, const Vector& clipmins, const Vector& clipmaxs ) +{ + int nNumSides = mb->numsides; + if (!nNumSides) + return NULL; + + // if the brush is outside the clip area, skip it + for (int j=0 ; j<3 ; j++) + { + if (mb->mins[j] >= clipmaxs[j] || mb->maxs[j] <= clipmins[j]) + { + return NULL; + } + } + + // make a copy of the brush + bspbrush_t *newbrush = AllocBrush( nNumSides ); + newbrush->original = mb; + newbrush->numsides = nNumSides; + memcpy (newbrush->sides, mb->original_sides, nNumSides*sizeof(side_t)); + + for (int j=0 ; jsides[j].winding) + { + newbrush->sides[j].winding = CopyWinding (newbrush->sides[j].winding); + } + + if (newbrush->sides[j].surf & SURF_HINT) + { + newbrush->sides[j].visible = true; // hints are always visible + } + + // keep a pointer to the original map brush side -- use to create the original face later!! + //newbrush->sides[j].original = &mb->original_sides[j]; + } + + VectorCopy (mb->mins, newbrush->mins); + VectorCopy (mb->maxs, newbrush->maxs); + + // carve off anything outside the clip box + newbrush = ClipBrushToBox (newbrush, clipmins, clipmaxs); + return newbrush; +} + + +//----------------------------------------------------------------------------- +// Creates a clipped brush from a map brush +//----------------------------------------------------------------------------- +static void ComputeBoundingPlanes( const Vector& clipmins, const Vector& clipmaxs ) +{ + Vector normal; + float dist; + for (int i=0 ; i<2 ; i++) + { + VectorClear (normal); + normal[i] = 1; + dist = clipmaxs[i]; + maxplanenums[i] = g_MainMap->FindFloatPlane (normal, dist); + dist = clipmins[i]; + minplanenums[i] = g_MainMap->FindFloatPlane (normal, dist); + } +} + + +//----------------------------------------------------------------------------- +// This forces copies of texinfo data for matching sides of a brush +//----------------------------------------------------------------------------- +void CopyMatchingTexinfos( side_t *pDestSides, int numDestSides, const bspbrush_t *pSource ) +{ + for ( int i = 0; i < numDestSides; i++ ) + { + side_t *pSide = &pDestSides[i]; + plane_t *pPlane = &g_MainMap->mapplanes[pSide->planenum]; + + // We have to use the *original sides* because MapBSPBrushList could have generated + // splits when cutting the original brush to the block being processed. This + // will generate faces that use TEXINFO_NODE, which is definitely *not* what we want. + // If we end up with faces using TEXINFO_NODE here, the area portal will flood into + // the entire water volume intersecting the areaportal. + + mapbrush_t *pSourceBrush = pSource->original; + Assert( pSourceBrush ); + + const side_t *pSourceSide = pSourceBrush->original_sides; + const side_t *pBestSide = NULL; + float flBestDot = -1.0f; + for ( int j = 0; j < pSourceBrush->numsides; ++j, ++pSourceSide ) + { + if ( pSourceSide->texinfo == TEXINFO_NODE ) + continue; + + plane_t *pSourcePlane = &g_MainMap->mapplanes[pSourceSide->planenum]; + float flDot = DotProduct( pPlane->normal, pSourcePlane->normal ); + if ( flDot == 1.0f || pSide->planenum == pSourceSide->planenum ) + { + pBestSide = pSourceSide; + break; + } + else if ( flDot > flBestDot ) + { + pBestSide = pSourceSide; + flBestDot = flDot; + } + } + + if ( pBestSide ) + { + pSide->texinfo = pBestSide->texinfo; + if ( pSide->original ) + { + pSide->original->texinfo = pSide->texinfo; + } + } + else + { + texinfo_t *pTexInfo = &texinfo[pSide->texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + Msg("Found no matching plane for %s\n", TexDataStringTable_GetString( pTexData->nameStringTableID ) ); + } + } +} + +// This is a hack to allow areaportals to work in water +// It was done this way for ease of implementation. +// This searches a brush list to find intersecting areaportals and water +// If an areaportal is found inside water, then the water contents and +// texture information is copied over to the areaportal so that the +// resulting space has the same properties as the water (normal areaportals assume "empty" surroundings) +void FixupAreaportalWaterBrushes( bspbrush_t *pList ) +{ + for ( bspbrush_t *pAreaportal = pList; pAreaportal; pAreaportal = pAreaportal->next ) + { + if ( !(pAreaportal->original->contents & CONTENTS_AREAPORTAL) ) + continue; + + for ( bspbrush_t *pWater = pList; pWater; pWater = pWater->next ) + { + // avoid using areaportal/water combo brushes that have already been fixed up + if ( pWater->original->contents & CONTENTS_AREAPORTAL ) + continue; + + if ( !(pWater->original->contents & MASK_SPLITAREAPORTAL) ) + continue; + + if ( BrushesDisjoint( pAreaportal, pWater ) ) + continue; + + bspbrush_t *pIntersect = IntersectBrush( pAreaportal, pWater ); + if ( !pIntersect ) + continue; + FreeBrush( pIntersect ); + pAreaportal->original->contents |= pWater->original->contents; + + // HACKHACK: Ideally, this should have been done before the bspbrush_t was + // created from the map brush. But since it hasn't been, retexture the original map + // brush's sides + CopyMatchingTexinfos( pAreaportal->sides, pAreaportal->numsides, pWater ); + CopyMatchingTexinfos( pAreaportal->original->original_sides, pAreaportal->original->numsides, pWater ); + } + } +} + + +//----------------------------------------------------------------------------- +// MakeBspBrushList +//----------------------------------------------------------------------------- +// UNDONE: Put detail brushes in a separate brush array and pass that instead of "onlyDetail" ? +bspbrush_t *MakeBspBrushList (int startbrush, int endbrush, const Vector& clipmins, const Vector& clipmaxs, int detailScreen) +{ + ComputeBoundingPlanes( clipmins, clipmaxs ); + + bspbrush_t *pBrushList = NULL; + + int i; + for (i=startbrush ; imapbrushes[i]; + if ( detailScreen != FULL_DETAIL ) + { + bool onlyDetail = (detailScreen == ONLY_DETAIL); + bool detail = (mb->contents & CONTENTS_DETAIL) != 0; + if ( onlyDetail ^ detail ) + { + // both of these must have the same value or we're not interested in this brush + continue; + } + } + + bspbrush_t *pNewBrush = CreateClippedBrush( mb, clipmins, clipmaxs ); + if ( pNewBrush ) + { + pNewBrush->next = pBrushList; + pBrushList = pNewBrush; + } + } + + return pBrushList; +} + + +//----------------------------------------------------------------------------- +// A version which uses a passed-in list of brushes +//----------------------------------------------------------------------------- +bspbrush_t *MakeBspBrushList (mapbrush_t **pBrushes, int nBrushCount, const Vector& clipmins, const Vector& clipmaxs) +{ + ComputeBoundingPlanes( clipmins, clipmaxs ); + + bspbrush_t *pBrushList = NULL; + for ( int i=0; i < nBrushCount; ++i ) + { + bspbrush_t *pNewBrush = CreateClippedBrush( pBrushes[i], clipmins, clipmaxs ); + if ( pNewBrush ) + { + pNewBrush->next = pBrushList; + pBrushList = pNewBrush; + } + } + + return pBrushList; +} + + +/* +=============== +AddBspBrushListToTail +=============== +*/ +bspbrush_t *AddBrushListToTail (bspbrush_t *list, bspbrush_t *tail) +{ + bspbrush_t *walk, *next; + + for (walk=list ; walk ; walk=next) + { // add to end of list + next = walk->next; + walk->next = NULL; + tail->next = walk; + tail = walk; + } + + return tail; +} + +/* +=========== +CullList + +Builds a new list that doesn't hold the given brush +=========== +*/ +bspbrush_t *CullList (bspbrush_t *list, bspbrush_t *skip1) +{ + bspbrush_t *newlist; + bspbrush_t *next; + + newlist = NULL; + + for ( ; list ; list = next) + { + next = list->next; + if (list == skip1) + { + FreeBrush (list); + continue; + } + list->next = newlist; + newlist = list; + } + return newlist; +} + + +/* +================== +WriteBrushMap +================== +*/ +void WriteBrushMap (char *name, bspbrush_t *list) +{ + FILE *f; + side_t *s; + int i; + winding_t *w; + + Msg("writing %s\n", name); + f = fopen (name, "w"); + if (!f) + Error ("Can't write %s\b", name); + + fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); + + for ( ; list ; list=list->next ) + { + fprintf (f, "{\n"); + for (i=0,s=list->sides ; inumsides ; i++,s++) + { + w = BaseWindingForPlane (g_MainMap->mapplanes[s->planenum].normal, g_MainMap->mapplanes[s->planenum].dist); + + fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); + + fprintf (f, "%s 0 0 0 1 1\n", TexDataStringTable_GetString( GetTexData( texinfo[s->texinfo].texdata )->nameStringTableID ) ); + FreeWinding (w); + } + fprintf (f, "}\n"); + } + fprintf (f, "}\n"); + + fclose (f); + +} + +// UNDONE: This isn't quite working yet +#if 0 +void WriteBrushVMF(char *name, bspbrush_t *list) +{ + FILE *f; + side_t *s; + int i; + winding_t *w; + Vector u, v; + + Msg("writing %s\n", name); + f = fopen (name, "w"); + if (!f) + Error ("Can't write %s\b", name); + + fprintf (f, "world\n{\n\"classname\" \"worldspawn\"\n"); + + for ( ; list ; list=list->next ) + { + fprintf (f, "\tsolid\n\t{\n"); + for (i=0,s=list->sides ; inumsides ; i++,s++) + { + fprintf( f, "\t\tside\n\t\t{\n" ); + fprintf( f, "\t\t\t\"plane\" \"" ); + w = BaseWindingForPlane (mapplanes[s->planenum].normal, mapplanes[s->planenum].dist); + + fprintf (f,"(%i %i %i) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); + fprintf (f,"(%i %i %i) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); + fprintf (f,"(%i %i %i)", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); + fprintf( f, "\"\n" ); + fprintf( f, "\t\t\t\"material\" \"%s\"\n", GetTexData( texinfo[s->texinfo].texdata )->name ); + // UNDONE: recreate correct texture axes + BasisForPlane( mapplanes[s->planenum].normal, u, v ); + fprintf( f, "\t\t\t\"uaxis\" \"[%.3f %.3f %.3f 0] 1.0\"\n", u[0], u[1], u[2] ); + fprintf( f, "\t\t\t\"vaxis\" \"[%.3f %.3f %.3f 0] 1.0\"\n", v[0], v[1], v[2] ); + + fprintf( f, "\t\t\t\"rotation\" \"0.0\"\n" ); + fprintf( f, "\t\t\t\"lightmapscale\" \"16.0\"\n" ); + + FreeWinding (w); + fprintf (f, "\t\t}\n"); + } + fprintf (f, "\t}\n"); + } + fprintf (f, "}\n"); + + fclose (f); + +} +#endif + +void PrintBrushContentsToString( int contents, char *pOut, int nMaxChars ) +{ + #define ADD_CONTENTS( flag ) \ + if ( contents & flag ) \ + Q_strncat( pOut, #flag " ", nMaxChars, COPY_ALL_CHARACTERS ); + + pOut[0] = 0; + + ADD_CONTENTS(CONTENTS_SOLID) + ADD_CONTENTS(CONTENTS_WINDOW) + ADD_CONTENTS(CONTENTS_AUX) + ADD_CONTENTS(CONTENTS_GRATE) + ADD_CONTENTS(CONTENTS_SLIME) + ADD_CONTENTS(CONTENTS_WATER) + ADD_CONTENTS(CONTENTS_BLOCKLOS) + ADD_CONTENTS(CONTENTS_OPAQUE) + ADD_CONTENTS(CONTENTS_TESTFOGVOLUME) + ADD_CONTENTS(CONTENTS_MOVEABLE) + ADD_CONTENTS(CONTENTS_AREAPORTAL) + ADD_CONTENTS(CONTENTS_PLAYERCLIP) + ADD_CONTENTS(CONTENTS_MONSTERCLIP) + ADD_CONTENTS(CONTENTS_CURRENT_0) + ADD_CONTENTS(CONTENTS_CURRENT_90) + ADD_CONTENTS(CONTENTS_CURRENT_180) + ADD_CONTENTS(CONTENTS_CURRENT_270) + ADD_CONTENTS(CONTENTS_CURRENT_UP) + ADD_CONTENTS(CONTENTS_CURRENT_DOWN) + ADD_CONTENTS(CONTENTS_ORIGIN) + ADD_CONTENTS(CONTENTS_MONSTER) + ADD_CONTENTS(CONTENTS_DEBRIS) + ADD_CONTENTS(CONTENTS_DETAIL) + ADD_CONTENTS(CONTENTS_TRANSLUCENT) + ADD_CONTENTS(CONTENTS_LADDER) + ADD_CONTENTS(CONTENTS_HITBOX) +} + +void PrintBrushContents( int contents ) +{ + char str[1024]; + PrintBrushContentsToString( contents, str, sizeof( str ) ); + Msg( "%s", str ); +} + +/* +================== +BrushGE + +Returns true if b1 is allowed to bite b2 +================== +*/ +qboolean BrushGE (bspbrush_t *b1, bspbrush_t *b2) +{ + // Areaportals are allowed to bite water + slime + // NOTE: This brush combo should have been fixed up + // in a first pass (FixupAreaportalWaterBrushes) + if( (b2->original->contents & MASK_SPLITAREAPORTAL) && + (b1->original->contents & CONTENTS_AREAPORTAL) ) + { + return true; + } + + // detail brushes never bite structural brushes + if ( (b1->original->contents & CONTENTS_DETAIL) + && !(b2->original->contents & CONTENTS_DETAIL) ) + return false; + if (b1->original->contents & CONTENTS_SOLID) + return true; + // Transparent brushes are not marked as detail anymore, so let them cut each other. + if ( (b1->original->contents & TRANSPARENT_CONTENTS) && (b2->original->contents & TRANSPARENT_CONTENTS) ) + return true; + + return false; +} + +/* +================= +ChopBrushes + +Carves any intersecting solid brushes into the minimum number +of non-intersecting brushes. +================= +*/ +bspbrush_t *ChopBrushes (bspbrush_t *head) +{ + bspbrush_t *b1, *b2, *next; + bspbrush_t *tail; + bspbrush_t *keep; + bspbrush_t *sub, *sub2; + int c1, c2; + + qprintf ("---- ChopBrushes ----\n"); + qprintf ("original brushes: %i\n", CountBrushList (head)); + +#if DEBUG_BRUSHMODEL + if (entity_num == DEBUG_BRUSHMODEL) + WriteBrushList ("before.gl", head, false); +#endif + keep = NULL; + +newlist: + // find tail + if (!head) + return NULL; + for (tail=head ; tail->next ; tail=tail->next) + ; + + for (b1=head ; b1 ; b1=next) + { + next = b1->next; + for (b2=b1->next ; b2 ; b2 = b2->next) + { + if (BrushesDisjoint (b1, b2)) + continue; + + sub = NULL; + sub2 = NULL; + c1 = 999999; + c2 = 999999; + + if ( BrushGE (b2, b1) ) + { +// printf( "b2 bites b1\n" ); + sub = SubtractBrush (b1, b2); + if (sub == b1) + continue; // didn't really intersect + if (!sub) + { // b1 is swallowed by b2 + head = CullList (b1, b1); + goto newlist; + } + c1 = CountBrushList (sub); + } + + if ( BrushGE (b1, b2) ) + { +// printf( "b1 bites b2\n" ); + sub2 = SubtractBrush (b2, b1); + if (sub2 == b2) + continue; // didn't really intersect + if (!sub2) + { // b2 is swallowed by b1 + FreeBrushList (sub); + head = CullList (b1, b2); + goto newlist; + } + c2 = CountBrushList (sub2); + } + + if (!sub && !sub2) + continue; // neither one can bite + + // only accept if it didn't fragment + // (commening this out allows full fragmentation) + if (c1 > 1 && c2 > 1) + { + const int contents1 = b1->original->contents; + const int contents2 = b2->original->contents; + // if both detail, allow fragmentation + if ( !((contents1&contents2) & CONTENTS_DETAIL) && !((contents1|contents2) & CONTENTS_AREAPORTAL) ) + { + if (sub2) + FreeBrushList (sub2); + if (sub) + FreeBrushList (sub); + continue; + } + } + + if (c1 < c2) + { + if (sub2) + FreeBrushList (sub2); + tail = AddBrushListToTail (sub, tail); + head = CullList (b1, b1); + goto newlist; + } + else + { + if (sub) + FreeBrushList (sub); + tail = AddBrushListToTail (sub2, tail); + head = CullList (b1, b2); + goto newlist; + } + } + + if (!b2) + { // b1 is no longer intersecting anything, so keep it + b1->next = keep; + keep = b1; + } + } + + qprintf ("output brushes: %i\n", CountBrushList (keep)); +#if DEBUG_BRUSHMODEL + if ( entity_num == DEBUG_BRUSHMODEL ) + { + WriteBrushList ("after.gl", keep, false); + WriteBrushMap ("after.map", keep); + } +#endif + return keep; +} + + diff --git a/mp/src/utils/vbsp/csg.h b/mp/src/utils/vbsp/csg.h index 30436dd9..158dcf43 100644 --- a/mp/src/utils/vbsp/csg.h +++ b/mp/src/utils/vbsp/csg.h @@ -1,32 +1,32 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef CSG_H -#define CSG_H -#ifdef _WIN32 -#pragma once -#endif - - -// Print a CONTENTS_ mask to a string. -void PrintBrushContentsToString( int contents, char *pOut, int nMaxChars ); - -// Print a CONTENTS_ mask with Msg(). -void PrintBrushContents( int contents ); - -void FixupAreaportalWaterBrushes( bspbrush_t *pList ); - -bspbrush_t *MakeBspBrushList (int startbrush, int endbrush, - const Vector& clipmins, const Vector& clipmaxs, int detailScreen); -bspbrush_t *MakeBspBrushList (mapbrush_t **pBrushes, int nBrushCount, const Vector& clipmins, const Vector& clipmaxs); - -void WriteBrushMap (char *name, bspbrush_t *list); - -bspbrush_t *ChopBrushes (bspbrush_t *head); -bspbrush_t *IntersectBrush (bspbrush_t *a, bspbrush_t *b); -qboolean BrushesDisjoint (bspbrush_t *a, bspbrush_t *b); - -#endif // CSG_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef CSG_H +#define CSG_H +#ifdef _WIN32 +#pragma once +#endif + + +// Print a CONTENTS_ mask to a string. +void PrintBrushContentsToString( int contents, char *pOut, int nMaxChars ); + +// Print a CONTENTS_ mask with Msg(). +void PrintBrushContents( int contents ); + +void FixupAreaportalWaterBrushes( bspbrush_t *pList ); + +bspbrush_t *MakeBspBrushList (int startbrush, int endbrush, + const Vector& clipmins, const Vector& clipmaxs, int detailScreen); +bspbrush_t *MakeBspBrushList (mapbrush_t **pBrushes, int nBrushCount, const Vector& clipmins, const Vector& clipmaxs); + +void WriteBrushMap (char *name, bspbrush_t *list); + +bspbrush_t *ChopBrushes (bspbrush_t *head); +bspbrush_t *IntersectBrush (bspbrush_t *a, bspbrush_t *b); +qboolean BrushesDisjoint (bspbrush_t *a, bspbrush_t *b); + +#endif // CSG_H diff --git a/mp/src/utils/vbsp/cubemap.cpp b/mp/src/utils/vbsp/cubemap.cpp index 2415115b..aeff12f1 100644 --- a/mp/src/utils/vbsp/cubemap.cpp +++ b/mp/src/utils/vbsp/cubemap.cpp @@ -1,996 +1,996 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "vbsp.h" -#include "bsplib.h" -#include "tier1/UtlBuffer.h" -#include "tier1/utlvector.h" -#include "bitmap/imageformat.h" -#include -#include "tier1/strtools.h" -#include "tier1/utlsymbol.h" -#include "vtf/vtf.h" -#include "materialpatch.h" -#include "materialsystem/imaterialsystem.h" -#include "materialsystem/imaterial.h" -#include "materialsystem/imaterialvar.h" - - -/* - Meager documentation for how the cubemaps are assigned. - - - While loading the map, it calls: - *** Cubemap_SaveBrushSides - Builds a list of what cubemaps manually were assigned to what faces - in s_EnvCubemapToBrushSides. - - Immediately after loading the map, it calls: - *** Cubemap_FixupBrushSidesMaterials - Goes through s_EnvCubemapToBrushSides and does Cubemap_CreateTexInfo for each - side referenced by an env_cubemap manually. - - Then it calls Cubemap_AttachDefaultCubemapToSpecularSides: - *** Cubemap_InitCubemapSideData: - Setup s_aCubemapSideData.bHasEnvMapInMaterial and bManuallyPickedByAnEnvCubemap for each side. - bHasEnvMapInMaterial is set if the side's material has $envmap. - bManuallyPickedByAnEnvCubemap is true if the side was in s_EnvCubemapToBrushSides. - - Then, for each bHasEnvMapInMaterial and !bManuallyPickedByAnEnvCubemap (ie: every specular surface that wasn't - referenced by some env_cubemap), it does Cubemap_CreateTexInfo. -*/ - -struct PatchInfo_t -{ - char *m_pMapName; - int m_pOrigin[3]; -}; - -struct CubemapInfo_t -{ - int m_nTableId; - bool m_bSpecular; -}; - -static bool CubemapLessFunc( const CubemapInfo_t &lhs, const CubemapInfo_t &rhs ) -{ - return ( lhs.m_nTableId < rhs.m_nTableId ); -} - - -typedef CUtlVector IntVector_t; -static CUtlVector s_EnvCubemapToBrushSides; - -static CUtlVector s_DefaultCubemapNames; -static char g_IsCubemapTexData[MAX_MAP_TEXDATA]; - - -struct CubemapSideData_t -{ - bool bHasEnvMapInMaterial; - bool bManuallyPickedByAnEnvCubemap; -}; - -static CubemapSideData_t s_aCubemapSideData[MAX_MAP_BRUSHSIDES]; - - - -inline bool SideHasCubemapAndWasntManuallyReferenced( int iSide ) -{ - return s_aCubemapSideData[iSide].bHasEnvMapInMaterial && !s_aCubemapSideData[iSide].bManuallyPickedByAnEnvCubemap; -} - - -void Cubemap_InsertSample( const Vector& origin, int size ) -{ - dcubemapsample_t *pSample = &g_CubemapSamples[g_nCubemapSamples]; - pSample->origin[0] = ( int )origin[0]; - pSample->origin[1] = ( int )origin[1]; - pSample->origin[2] = ( int )origin[2]; - pSample->size = size; - g_nCubemapSamples++; -} - -static const char *FindSkyboxMaterialName( void ) -{ - for( int i = 0; i < g_MainMap->num_entities; i++ ) - { - char* pEntity = ValueForKey(&g_MainMap->entities[i], "classname"); - if (!strcmp(pEntity, "worldspawn")) - { - return ValueForKey( &g_MainMap->entities[i], "skyname" ); - } - } - return NULL; -} - -static void BackSlashToForwardSlash( char *pname ) -{ - while ( *pname ) { - if ( *pname == '\\' ) - *pname = '/'; - pname++; - } -} - -static void ForwardSlashToBackSlash( char *pname ) -{ - while ( *pname ) { - if ( *pname == '/' ) - *pname = '\\'; - pname++; - } -} - - -//----------------------------------------------------------------------------- -// Finds materials that are used by a particular material -//----------------------------------------------------------------------------- -#define MAX_MATERIAL_NAME 512 - -// This is the list of materialvars which are used in our codebase to look up dependent materials -static const char *s_pDependentMaterialVar[] = -{ - "$bottommaterial", // Used by water materials - "$crackmaterial", // Used by shattered glass materials - "$fallbackmaterial", // Used by all materials - - "", // Always must be last -}; - -static const char *FindDependentMaterial( const char *pMaterialName, const char **ppMaterialVar = NULL ) -{ - // FIXME: This is a terrible way of doing this! It creates a dependency - // between vbsp and *all* code which reads dependent materials from materialvars - // At the time of writing this function, that means the engine + studiorender. - // We need a better way of figuring out how to do this, but for now I'm trying to do - // the fastest solution possible since it's close to ship - - static char pDependentMaterialName[MAX_MATERIAL_NAME]; - for( int i = 0; s_pDependentMaterialVar[i][0]; ++i ) - { - if ( !GetValueFromMaterial( pMaterialName, s_pDependentMaterialVar[i], pDependentMaterialName, MAX_MATERIAL_NAME - 1 ) ) - continue; - - if ( !Q_stricmp( pDependentMaterialName, pMaterialName ) ) - { - Warning( "Material %s is depending on itself through materialvar %s! Ignoring...\n", pMaterialName, s_pDependentMaterialVar[i] ); - continue; - } - - // Return the material var that caused the dependency - if ( ppMaterialVar ) - { - *ppMaterialVar = s_pDependentMaterialVar[i]; - } - -#ifdef _DEBUG - // FIXME: Note that this code breaks if a material has more than 1 dependent material - ++i; - static char pDependentMaterialName2[MAX_MATERIAL_NAME]; - while( s_pDependentMaterialVar[i][0] ) - { - Assert( !GetValueFromMaterial( pMaterialName, s_pDependentMaterialVar[i], pDependentMaterialName2, MAX_MATERIAL_NAME - 1 ) ); - ++i; - } -#endif - - return pDependentMaterialName; - } - - return NULL; -} - - -//----------------------------------------------------------------------------- -// Loads VTF files -//----------------------------------------------------------------------------- -static bool LoadSrcVTFFiles( IVTFTexture *pSrcVTFTextures[6], const char *pSkyboxMaterialBaseName, - int *pUnionTextureFlags, bool bHDR ) -{ - const char *facingName[6] = { "rt", "lf", "bk", "ft", "up", "dn" }; - int i; - for( i = 0; i < 6; i++ ) - { - char srcMaterialName[1024]; - sprintf( srcMaterialName, "%s%s", pSkyboxMaterialBaseName, facingName[i] ); - - IMaterial *pSkyboxMaterial = g_pMaterialSystem->FindMaterial( srcMaterialName, "skybox" ); - //IMaterialVar *pSkyTextureVar = pSkyboxMaterial->FindVar( bHDR ? "$hdrbasetexture" : "$basetexture", NULL ); //, bHDR ? false : true ); - IMaterialVar *pSkyTextureVar = pSkyboxMaterial->FindVar( "$basetexture", NULL ); // Since we're setting it to black anyway, just use $basetexture for HDR - const char *vtfName = pSkyTextureVar->GetStringValue(); - char srcVTFFileName[MAX_PATH]; - Q_snprintf( srcVTFFileName, MAX_PATH, "materials/%s.vtf", vtfName ); - - CUtlBuffer buf; - if ( !g_pFullFileSystem->ReadFile( srcVTFFileName, NULL, buf ) ) - { - // Try looking for a compressed HDR texture - if ( bHDR ) - { - /* // FIXME: We need a way to uncompress this format! - bool bHDRCompressed = true; - - pSkyTextureVar = pSkyboxMaterial->FindVar( "$hdrcompressedTexture", NULL ); - vtfName = pSkyTextureVar->GetStringValue(); - Q_snprintf( srcVTFFileName, MAX_PATH, "materials/%s.vtf", vtfName ); - - if ( !g_pFullFileSystem->ReadFile( srcVTFFileName, NULL, buf ) ) - */ - { - return false; - } - } - else - { - return false; - } - } - - pSrcVTFTextures[i] = CreateVTFTexture(); - if (!pSrcVTFTextures[i]->Unserialize(buf)) - { - Warning("*** Error unserializing skybox texture: %s\n", pSkyboxMaterialBaseName ); - return false; - } - - *pUnionTextureFlags |= pSrcVTFTextures[i]->Flags(); - int flagsNoAlpha = pSrcVTFTextures[i]->Flags() & ~( TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_ONEBITALPHA ); - int flagsFirstNoAlpha = pSrcVTFTextures[0]->Flags() & ~( TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_ONEBITALPHA ); - - // NOTE: texture[0] is a side texture that could be 1/2 height, so allow this and also allow 4x4 faces - if ( ( ( pSrcVTFTextures[i]->Width() != pSrcVTFTextures[0]->Width() ) && ( pSrcVTFTextures[i]->Width() != 4 ) ) || - ( ( pSrcVTFTextures[i]->Height() != pSrcVTFTextures[0]->Height() ) && ( pSrcVTFTextures[i]->Height() != pSrcVTFTextures[0]->Height()*2 ) && ( pSrcVTFTextures[i]->Height() != 4 ) ) || - ( flagsNoAlpha != flagsFirstNoAlpha ) ) - { - Warning("*** Error: Skybox vtf files for %s weren't compiled with the same size texture and/or same flags!\n", pSkyboxMaterialBaseName ); - return false; - } - - if ( bHDR ) - { - pSrcVTFTextures[i]->ConvertImageFormat( IMAGE_FORMAT_RGB323232F, false ); - pSrcVTFTextures[i]->GenerateMipmaps(); - pSrcVTFTextures[i]->ConvertImageFormat( IMAGE_FORMAT_RGBA16161616F, false ); - } - } - - return true; -} - -void VTFNameToHDRVTFName( const char *pSrcName, char *pDest, int maxLen, bool bHDR ) -{ - Q_strncpy( pDest, pSrcName, maxLen ); - if( !bHDR ) - { - return; - } - char *pDot = Q_stristr( pDest, ".vtf" ); - if( !pDot ) - { - return; - } - Q_strncpy( pDot, ".hdr.vtf", maxLen - ( pDot - pDest ) ); -} - -#define DEFAULT_CUBEMAP_SIZE 32 - -void CreateDefaultCubemaps( bool bHDR ) -{ - memset( g_IsCubemapTexData, 0, sizeof(g_IsCubemapTexData) ); - - // NOTE: This implementation depends on the fact that all VTF files contain - // all mipmap levels - const char *pSkyboxBaseName = FindSkyboxMaterialName(); - - if( !pSkyboxBaseName ) - { - if( s_DefaultCubemapNames.Count() ) - { - Warning( "This map uses env_cubemap, and you don't have a skybox, so no default env_cubemaps will be generated.\n" ); - } - return; - } - - char skyboxMaterialName[MAX_PATH]; - Q_snprintf( skyboxMaterialName, MAX_PATH, "skybox/%s", pSkyboxBaseName ); - - IVTFTexture *pSrcVTFTextures[6]; - - int unionTextureFlags = 0; - if( !LoadSrcVTFFiles( pSrcVTFTextures, skyboxMaterialName, &unionTextureFlags, bHDR ) ) - { - Warning( "Can't load skybox file %s to build the default cubemap!\n", skyboxMaterialName ); - return; - } - Msg( "Creating default %scubemaps for env_cubemap using skybox materials:\n %s*.vmt\n" - " ! Run buildcubemaps in the engine to get the correct cube maps.\n", bHDR ? "HDR " : "LDR ", skyboxMaterialName ); - - // Figure out the mip differences between the two textures - int iMipLevelOffset = 0; - int tmp = pSrcVTFTextures[0]->Width(); - while( tmp > DEFAULT_CUBEMAP_SIZE ) - { - iMipLevelOffset++; - tmp >>= 1; - } - - // Create the destination cubemap - IVTFTexture *pDstCubemap = CreateVTFTexture(); - pDstCubemap->Init( DEFAULT_CUBEMAP_SIZE, DEFAULT_CUBEMAP_SIZE, 1, - pSrcVTFTextures[0]->Format(), unionTextureFlags | TEXTUREFLAGS_ENVMAP, - pSrcVTFTextures[0]->FrameCount() ); - - // First iterate over all frames - for (int iFrame = 0; iFrame < pDstCubemap->FrameCount(); ++iFrame) - { - // Next iterate over all normal cube faces (we know there's 6 cause it's an envmap) - for (int iFace = 0; iFace < 6; ++iFace ) - { - // Finally, iterate over all mip levels in the *destination* - for (int iMip = 0; iMip < pDstCubemap->MipCount(); ++iMip ) - { - // Copy the bits from the source images into the cube faces - unsigned char *pSrcBits = pSrcVTFTextures[iFace]->ImageData( iFrame, 0, iMip + iMipLevelOffset ); - unsigned char *pDstBits = pDstCubemap->ImageData( iFrame, iFace, iMip ); - int iSize = pDstCubemap->ComputeMipSize( iMip ); - int iSrcMipSize = pSrcVTFTextures[iFace]->ComputeMipSize( iMip + iMipLevelOffset ); - - // !!! FIXME: Set this to black until HDR cubemaps are built properly! - memset( pDstBits, 0, iSize ); - continue; - - if ( ( pSrcVTFTextures[iFace]->Width() == 4 ) && ( pSrcVTFTextures[iFace]->Height() == 4 ) ) // If texture is 4x4 square - { - // Force mip level 2 to get the 1x1 face - unsigned char *pSrcBits = pSrcVTFTextures[iFace]->ImageData( iFrame, 0, 2 ); - int iSrcMipSize = pSrcVTFTextures[iFace]->ComputeMipSize( 2 ); - - // Replicate 1x1 mip level across entire face - //memset( pDstBits, 0, iSize ); - for ( int i = 0; i < ( iSize / iSrcMipSize ); i++ ) - { - memcpy( pDstBits + ( i * iSrcMipSize ), pSrcBits, iSrcMipSize ); - } - } - else if ( pSrcVTFTextures[iFace]->Width() == pSrcVTFTextures[iFace]->Height() ) // If texture is square - { - if ( iSrcMipSize != iSize ) - { - Warning( "%s - ERROR! Cannot copy square face for default cubemap! iSrcMipSize(%d) != iSize(%d)\n", skyboxMaterialName, iSrcMipSize, iSize ); - memset( pDstBits, 0, iSize ); - } - else - { - // Just copy the mip level - memcpy( pDstBits, pSrcBits, iSize ); - } - } - else if ( pSrcVTFTextures[iFace]->Width() == pSrcVTFTextures[iFace]->Height()*2 ) // If texture is rectangle 2x wide - { - int iMipWidth, iMipHeight, iMipDepth; - pDstCubemap->ComputeMipLevelDimensions( iMip, &iMipWidth, &iMipHeight, &iMipDepth ); - if ( ( iMipHeight > 1 ) && ( iSrcMipSize*2 != iSize ) ) - { - Warning( "%s - ERROR building default cube map! %d*2 != %d\n", skyboxMaterialName, iSrcMipSize, iSize ); - memset( pDstBits, 0, iSize ); - } - else - { - // Copy row at a time and repeat last row - memcpy( pDstBits, pSrcBits, iSize/2 ); - //memcpy( pDstBits + iSize/2, pSrcBits, iSize/2 ); - int nSrcRowSize = pSrcVTFTextures[iFace]->RowSizeInBytes( iMip + iMipLevelOffset ); - int nDstRowSize = pDstCubemap->RowSizeInBytes( iMip ); - if ( nSrcRowSize != nDstRowSize ) - { - Warning( "%s - ERROR building default cube map! nSrcRowSize(%d) != nDstRowSize(%d)!\n", skyboxMaterialName, nSrcRowSize, nDstRowSize ); - memset( pDstBits, 0, iSize ); - } - else - { - for ( int i = 0; i < ( iSize/2 / nSrcRowSize ); i++ ) - { - memcpy( pDstBits + iSize/2 + i*nSrcRowSize, pSrcBits + iSrcMipSize - nSrcRowSize, nSrcRowSize ); - } - } - } - } - else - { - // ERROR! This code only supports square and rectangluar 2x wide - Warning( "%s - Couldn't create default cubemap because texture res is %dx%d\n", skyboxMaterialName, pSrcVTFTextures[iFace]->Width(), pSrcVTFTextures[iFace]->Height() ); - memset( pDstBits, 0, iSize ); - return; - } - } - } - } - - ImageFormat originalFormat = pDstCubemap->Format(); - if( !bHDR ) - { - // Convert the cube to format that we can apply tools to it... - pDstCubemap->ConvertImageFormat( IMAGE_FORMAT_DEFAULT, false ); - } - - // Fixup the cubemap facing - pDstCubemap->FixCubemapFaceOrientation(); - - // Now that the bits are in place, compute the spheremaps... - pDstCubemap->GenerateSpheremap(); - - if( !bHDR ) - { - // Convert the cubemap to the final format - pDstCubemap->ConvertImageFormat( originalFormat, false ); - } - - // Write the puppy out! - char dstVTFFileName[1024]; - if( bHDR ) - { - sprintf( dstVTFFileName, "materials/maps/%s/cubemapdefault.hdr.vtf", mapbase ); - } - else - { - sprintf( dstVTFFileName, "materials/maps/%s/cubemapdefault.vtf", mapbase ); - } - - CUtlBuffer outputBuf; - if (!pDstCubemap->Serialize( outputBuf )) - { - Warning( "Error serializing default cubemap %s\n", dstVTFFileName ); - return; - } - - IZip *pak = GetPakFile(); - - // spit out the default one. - AddBufferToPak( pak, dstVTFFileName, outputBuf.Base(), outputBuf.TellPut(), false ); - - // spit out all of the ones that are attached to world geometry. - int i; - for( i = 0; i < s_DefaultCubemapNames.Count(); i++ ) - { - char vtfName[MAX_PATH]; - VTFNameToHDRVTFName( s_DefaultCubemapNames[i], vtfName, MAX_PATH, bHDR ); - if( FileExistsInPak( pak, vtfName ) ) - { - continue; - } - AddBufferToPak( pak, vtfName, outputBuf.Base(),outputBuf.TellPut(), false ); - } - - // Clean up the textures - for( i = 0; i < 6; i++ ) - { - DestroyVTFTexture( pSrcVTFTextures[i] ); - } - DestroyVTFTexture( pDstCubemap ); -} - -void Cubemap_CreateDefaultCubemaps( void ) -{ - CreateDefaultCubemaps( false ); - CreateDefaultCubemaps( true ); -} - -// Builds a list of what cubemaps manually were assigned to what faces -// in s_EnvCubemapToBrushSides. -void Cubemap_SaveBrushSides( const char *pSideListStr ) -{ - IntVector_t &brushSidesVector = s_EnvCubemapToBrushSides[s_EnvCubemapToBrushSides.AddToTail()]; - char *pTmp = ( char * )_alloca( strlen( pSideListStr ) + 1 ); - strcpy( pTmp, pSideListStr ); - const char *pScan = strtok( pTmp, " " ); - if( !pScan ) - { - return; - } - do - { - int brushSideID; - if( sscanf( pScan, "%d", &brushSideID ) == 1 ) - { - brushSidesVector.AddToTail( brushSideID ); - } - } while( ( pScan = strtok( NULL, " " ) ) ); -} - - -//----------------------------------------------------------------------------- -// Generate patched material name -//----------------------------------------------------------------------------- -static void GeneratePatchedName( const char *pMaterialName, const PatchInfo_t &info, bool bMaterialName, char *pBuffer, int nMaxLen ) -{ - const char *pSeparator = bMaterialName ? "_" : ""; - int nLen = Q_snprintf( pBuffer, nMaxLen, "maps/%s/%s%s%d_%d_%d", info.m_pMapName, - pMaterialName, pSeparator, info.m_pOrigin[0], info.m_pOrigin[1], info.m_pOrigin[2] ); - - if ( bMaterialName ) - { - Assert( nLen < TEXTURE_NAME_LENGTH - 1 ); - if ( nLen >= TEXTURE_NAME_LENGTH - 1 ) - { - Error( "Generated env_cubemap patch name : %s too long! (max = %d)\n", pBuffer, TEXTURE_NAME_LENGTH ); - } - } - - BackSlashToForwardSlash( pBuffer ); - Q_strlower( pBuffer ); -} - - -//----------------------------------------------------------------------------- -// Patches the $envmap for a material and all its dependents, returns true if any patching happened -//----------------------------------------------------------------------------- -static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, const PatchInfo_t &info, const char *pCubemapTexture ) -{ - // Do *NOT* patch the material if there is an $envmap specified and it's not 'env_cubemap' - - // FIXME: It's theoretically ok to patch the material if $envmap is not specified, - // because we're using the 'replace' block, which will only add the env_cubemap if - // $envmap is specified in the source material. But it will fail if someone adds - // a specific non-env_cubemap $envmap to the source material at a later point. Bleah - - // See if we have an $envmap to patch - bool bShouldPatchEnvCubemap = DoesMaterialHaveKeyValuePair( pMaterialName, "$envmap", "env_cubemap" ); - - // See if we have a dependent material to patch - bool bDependentMaterialPatched = false; - const char *pDependentMaterialVar = NULL; - const char *pDependentMaterial = FindDependentMaterial( pMaterialName, &pDependentMaterialVar ); - if ( pDependentMaterial ) - { - bDependentMaterialPatched = PatchEnvmapForMaterialAndDependents( pDependentMaterial, info, pCubemapTexture ); - } - - // If we have neither to patch, we're done - if ( !bShouldPatchEnvCubemap && !bDependentMaterialPatched ) - return false; - - // Otherwise we have to make a patched version of ourselves - char pPatchedMaterialName[1024]; - GeneratePatchedName( pMaterialName, info, true, pPatchedMaterialName, 1024 ); - - MaterialPatchInfo_t pPatchInfo[2]; - int nPatchCount = 0; - if ( bShouldPatchEnvCubemap ) - { - pPatchInfo[nPatchCount].m_pKey = "$envmap"; - pPatchInfo[nPatchCount].m_pRequiredOriginalValue = "env_cubemap"; - pPatchInfo[nPatchCount].m_pValue = pCubemapTexture; - ++nPatchCount; - } - - char pDependentPatchedMaterialName[1024]; - if ( bDependentMaterialPatched ) - { - // FIXME: Annoying! I either have to pass back the patched dependent material name - // or reconstruct it. Both are sucky. - GeneratePatchedName( pDependentMaterial, info, true, pDependentPatchedMaterialName, 1024 ); - pPatchInfo[nPatchCount].m_pKey = pDependentMaterialVar; - pPatchInfo[nPatchCount].m_pValue = pDependentPatchedMaterialName; - ++nPatchCount; - } - - CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_REPLACE ); - - return true; -} - - -//----------------------------------------------------------------------------- -// Finds a texinfo that has a particular -//----------------------------------------------------------------------------- - - -//----------------------------------------------------------------------------- -// Create a VMT to override the specified texinfo which references the cubemap entity at the specified origin. -// Returns the index of the new (or preexisting) texinfo referencing that VMT. -// -// Also adds the new cubemap VTF filename to s_DefaultCubemapNames so it can copy the -// default (skybox) cubemap into this file so the cubemap doesn't have the pink checkerboard at -// runtime before they run buildcubemaps. -//----------------------------------------------------------------------------- -static int Cubemap_CreateTexInfo( int originalTexInfo, int origin[3] ) -{ - // Don't make cubemap tex infos for nodes - if ( originalTexInfo == TEXINFO_NODE ) - return originalTexInfo; - - texinfo_t *pTexInfo = &texinfo[originalTexInfo]; - dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); - const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); - if ( g_IsCubemapTexData[pTexInfo->texdata] ) - { - Warning("Multiple references for cubemap on texture %s!!!\n", pMaterialName ); - return originalTexInfo; - } - - // Get out of here if the originalTexInfo is already a generated material for this position. - char pStringToSearchFor[512]; - Q_snprintf( pStringToSearchFor, 512, "_%d_%d_%d", origin[0], origin[1], origin[2] ); - if ( Q_stristr( pMaterialName, pStringToSearchFor ) ) - return originalTexInfo; - - // Package up information needed to generate patch names - PatchInfo_t info; - info.m_pMapName = mapbase; - info.m_pOrigin[0] = origin[0]; - info.m_pOrigin[1] = origin[1]; - info.m_pOrigin[2] = origin[2]; - - // Generate the name of the patched material - char pGeneratedTexDataName[1024]; - GeneratePatchedName( pMaterialName, info, true, pGeneratedTexDataName, 1024 ); - - // Make sure the texdata doesn't already exist. - int nTexDataID = FindTexData( pGeneratedTexDataName ); - bool bHasTexData = (nTexDataID != -1); - if( !bHasTexData ) - { - // Generate the new "$envmap" texture name. - char pTextureName[1024]; - GeneratePatchedName( "c", info, false, pTextureName, 1024 ); - - // Hook the texture into the material and all dependent materials - // but if no hooking was necessary, exit out - if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName ) ) - return originalTexInfo; - - // Store off the name of the cubemap that we need to create since we successfully patched - char pFileName[1024]; - int nLen = Q_snprintf( pFileName, 1024, "materials/%s.vtf", pTextureName ); - int id = s_DefaultCubemapNames.AddToTail(); - s_DefaultCubemapNames[id] = new char[ nLen + 1 ]; - strcpy( s_DefaultCubemapNames[id], pFileName ); - - // Make a new texdata - nTexDataID = AddCloneTexData( pTexData, pGeneratedTexDataName ); - g_IsCubemapTexData[nTexDataID] = true; - } - - Assert( nTexDataID != -1 ); - - texinfo_t newTexInfo; - newTexInfo = *pTexInfo; - newTexInfo.texdata = nTexDataID; - - int nTexInfoID = -1; - - // See if we need to make a new texinfo - bool bHasTexInfo = false; - if( bHasTexData ) - { - nTexInfoID = FindTexInfo( newTexInfo ); - bHasTexInfo = (nTexInfoID != -1); - } - - // Make a new texinfo if we need to. - if( !bHasTexInfo ) - { - nTexInfoID = texinfo.AddToTail( newTexInfo ); - } - - Assert( nTexInfoID != -1 ); - return nTexInfoID; -} - -static int SideIDToIndex( int brushSideID ) -{ - int i; - for( i = 0; i < g_MainMap->nummapbrushsides; i++ ) - { - if( g_MainMap->brushsides[i].id == brushSideID ) - { - return i; - } - } - return -1; -} - - -//----------------------------------------------------------------------------- -// Goes through s_EnvCubemapToBrushSides and does Cubemap_CreateTexInfo for each -// side referenced by an env_cubemap manually. -//----------------------------------------------------------------------------- -void Cubemap_FixupBrushSidesMaterials( void ) -{ - Msg( "fixing up env_cubemap materials on brush sides...\n" ); - Assert( s_EnvCubemapToBrushSides.Count() == g_nCubemapSamples ); - - int cubemapID; - for( cubemapID = 0; cubemapID < g_nCubemapSamples; cubemapID++ ) - { - IntVector_t &brushSidesVector = s_EnvCubemapToBrushSides[cubemapID]; - int i; - for( i = 0; i < brushSidesVector.Count(); i++ ) - { - int brushSideID = brushSidesVector[i]; - int sideIndex = SideIDToIndex( brushSideID ); - if( sideIndex < 0 ) - { - Warning("env_cubemap pointing at deleted brushside near (%d, %d, %d)\n", - g_CubemapSamples[cubemapID].origin[0], g_CubemapSamples[cubemapID].origin[1], g_CubemapSamples[cubemapID].origin[2] ); - - continue; - } - - side_t *pSide = &g_MainMap->brushsides[sideIndex]; - -#ifdef DEBUG - if ( pSide->pMapDisp ) - { - Assert( pSide->texinfo == pSide->pMapDisp->face.texinfo ); - } -#endif - - pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin ); - if ( pSide->pMapDisp ) - { - pSide->pMapDisp->face.texinfo = pSide->texinfo; - } - } - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void Cubemap_ResetCubemapSideData( void ) -{ - for ( int iSide = 0; iSide < MAX_MAP_BRUSHSIDES; ++iSide ) - { - s_aCubemapSideData[iSide].bHasEnvMapInMaterial = false; - s_aCubemapSideData[iSide].bManuallyPickedByAnEnvCubemap = false; - } -} - - -//----------------------------------------------------------------------------- -// Returns true if the material or any of its dependents use an $envmap -//----------------------------------------------------------------------------- -bool DoesMaterialOrDependentsUseEnvmap( const char *pPatchedMaterialName ) -{ - const char *pOriginalMaterialName = GetOriginalMaterialNameForPatchedMaterial( pPatchedMaterialName ); - if( DoesMaterialHaveKey( pOriginalMaterialName, "$envmap" ) ) - return true; - - const char *pDependentMaterial = FindDependentMaterial( pOriginalMaterialName ); - if ( !pDependentMaterial ) - return false; - - return DoesMaterialOrDependentsUseEnvmap( pDependentMaterial ); -} - - -//----------------------------------------------------------------------------- -// Builds a list of all texdatas which need fixing up -//----------------------------------------------------------------------------- -void Cubemap_InitCubemapSideData( void ) -{ - // This tree is used to prevent re-parsing material vars multiple times - CUtlRBTree lookup( 0, g_MainMap->nummapbrushsides, CubemapLessFunc ); - - // Fill in specular data. - for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide ) - { - side_t *pSide = &g_MainMap->brushsides[iSide]; - if ( !pSide ) - continue; - - if ( pSide->texinfo == TEXINFO_NODE ) - continue; - - texinfo_t *pTex = &texinfo[pSide->texinfo]; - if ( !pTex ) - continue; - - dtexdata_t *pTexData = GetTexData( pTex->texdata ); - if ( !pTexData ) - continue; - - CubemapInfo_t info; - info.m_nTableId = pTexData->nameStringTableID; - - // Have we encountered this materal? If so, then copy the data we cached off before - int i = lookup.Find( info ); - if ( i != lookup.InvalidIndex() ) - { - s_aCubemapSideData[iSide].bHasEnvMapInMaterial = lookup[i].m_bSpecular; - continue; - } - - // First time we've seen this material. Figure out if it uses env_cubemap - const char *pPatchedMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); - info.m_bSpecular = DoesMaterialOrDependentsUseEnvmap( pPatchedMaterialName ); - s_aCubemapSideData[ iSide ].bHasEnvMapInMaterial = info.m_bSpecular; - lookup.Insert( info ); - } - - // Fill in cube map data. - for ( int iCubemap = 0; iCubemap < g_nCubemapSamples; ++iCubemap ) - { - IntVector_t &sideList = s_EnvCubemapToBrushSides[iCubemap]; - int nSideCount = sideList.Count(); - for ( int iSide = 0; iSide < nSideCount; ++iSide ) - { - int nSideID = sideList[iSide]; - int nIndex = SideIDToIndex( nSideID ); - if ( nIndex < 0 ) - continue; - - s_aCubemapSideData[nIndex].bManuallyPickedByAnEnvCubemap = true; - } - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int Cubemap_FindClosestCubemap( const Vector &entityOrigin, side_t *pSide ) -{ - if ( !pSide ) - return -1; - - // Return a valid (if random) cubemap if there's no winding - if ( !pSide->winding ) - return 0; - - // Calculate the center point. - Vector vecCenter; - vecCenter.Init(); - - for ( int iPoint = 0; iPoint < pSide->winding->numpoints; ++iPoint ) - { - VectorAdd( vecCenter, pSide->winding->p[iPoint], vecCenter ); - } - VectorScale( vecCenter, 1.0f / pSide->winding->numpoints, vecCenter ); - vecCenter += entityOrigin; - plane_t *pPlane = &g_MainMap->mapplanes[pSide->planenum]; - - // Find the closest cubemap. - int iMinCubemap = -1; - float flMinDist = FLT_MAX; - - // Look for cubemaps in front of the surface first. - for ( int iCubemap = 0; iCubemap < g_nCubemapSamples; ++iCubemap ) - { - dcubemapsample_t *pSample = &g_CubemapSamples[iCubemap]; - Vector vecSampleOrigin( static_cast( pSample->origin[0] ), - static_cast( pSample->origin[1] ), - static_cast( pSample->origin[2] ) ); - Vector vecDelta; - VectorSubtract( vecSampleOrigin, vecCenter, vecDelta ); - float flDist = vecDelta.NormalizeInPlace(); - float flDot = DotProduct( vecDelta, pPlane->normal ); - if ( ( flDot >= 0.0f ) && ( flDist < flMinDist ) ) - { - flMinDist = flDist; - iMinCubemap = iCubemap; - } - } - - // Didn't find anything in front search for closest. - if( iMinCubemap == -1 ) - { - for ( int iCubemap = 0; iCubemap < g_nCubemapSamples; ++iCubemap ) - { - dcubemapsample_t *pSample = &g_CubemapSamples[iCubemap]; - Vector vecSampleOrigin( static_cast( pSample->origin[0] ), - static_cast( pSample->origin[1] ), - static_cast( pSample->origin[2] ) ); - Vector vecDelta; - VectorSubtract( vecSampleOrigin, vecCenter, vecDelta ); - float flDist = vecDelta.Length(); - if ( flDist < flMinDist ) - { - flMinDist = flDist; - iMinCubemap = iCubemap; - } - } - } - - return iMinCubemap; -} - - -//----------------------------------------------------------------------------- -// For every specular surface that wasn't referenced by some env_cubemap, call Cubemap_CreateTexInfo. -//----------------------------------------------------------------------------- -void Cubemap_AttachDefaultCubemapToSpecularSides( void ) -{ - Cubemap_ResetCubemapSideData(); - Cubemap_InitCubemapSideData(); - - // build a mapping from side to entity id so that we can get the entity origin - CUtlVector sideToEntityIndex; - sideToEntityIndex.SetCount(g_MainMap->nummapbrushsides); - int i; - for ( i = 0; i < g_MainMap->nummapbrushsides; i++ ) - { - sideToEntityIndex[i] = -1; - } - - for ( i = 0; i < g_MainMap->nummapbrushes; i++ ) - { - int entityIndex = g_MainMap->mapbrushes[i].entitynum; - for ( int j = 0; j < g_MainMap->mapbrushes[i].numsides; j++ ) - { - side_t *side = &g_MainMap->mapbrushes[i].original_sides[j]; - int sideIndex = side - g_MainMap->brushsides; - sideToEntityIndex[sideIndex] = entityIndex; - } - } - - for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide ) - { - side_t *pSide = &g_MainMap->brushsides[iSide]; - if ( !SideHasCubemapAndWasntManuallyReferenced( iSide ) ) - continue; - - - int currentEntity = sideToEntityIndex[iSide]; - - int iCubemap = Cubemap_FindClosestCubemap( g_MainMap->entities[currentEntity].origin, pSide ); - if ( iCubemap == -1 ) - continue; - -#ifdef DEBUG - if ( pSide->pMapDisp ) - { - Assert( pSide->texinfo == pSide->pMapDisp->face.texinfo ); - } -#endif - pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin ); - if ( pSide->pMapDisp ) - { - pSide->pMapDisp->face.texinfo = pSide->texinfo; - } - } -} - -// Populate with cubemaps that were skipped -void Cubemap_AddUnreferencedCubemaps() -{ - char pTextureName[1024]; - char pFileName[1024]; - PatchInfo_t info; - dcubemapsample_t *pSample; - int i,j; - - for ( i=0; iorigin[0]; - info.m_pOrigin[1] = pSample->origin[1]; - info.m_pOrigin[2] = pSample->origin[2]; - GeneratePatchedName( "c", info, false, pTextureName, 1024 ); - - // find or add - for ( j=0; j +#include "tier1/strtools.h" +#include "tier1/utlsymbol.h" +#include "vtf/vtf.h" +#include "materialpatch.h" +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/imaterialvar.h" + + +/* + Meager documentation for how the cubemaps are assigned. + + + While loading the map, it calls: + *** Cubemap_SaveBrushSides + Builds a list of what cubemaps manually were assigned to what faces + in s_EnvCubemapToBrushSides. + + Immediately after loading the map, it calls: + *** Cubemap_FixupBrushSidesMaterials + Goes through s_EnvCubemapToBrushSides and does Cubemap_CreateTexInfo for each + side referenced by an env_cubemap manually. + + Then it calls Cubemap_AttachDefaultCubemapToSpecularSides: + *** Cubemap_InitCubemapSideData: + Setup s_aCubemapSideData.bHasEnvMapInMaterial and bManuallyPickedByAnEnvCubemap for each side. + bHasEnvMapInMaterial is set if the side's material has $envmap. + bManuallyPickedByAnEnvCubemap is true if the side was in s_EnvCubemapToBrushSides. + + Then, for each bHasEnvMapInMaterial and !bManuallyPickedByAnEnvCubemap (ie: every specular surface that wasn't + referenced by some env_cubemap), it does Cubemap_CreateTexInfo. +*/ + +struct PatchInfo_t +{ + char *m_pMapName; + int m_pOrigin[3]; +}; + +struct CubemapInfo_t +{ + int m_nTableId; + bool m_bSpecular; +}; + +static bool CubemapLessFunc( const CubemapInfo_t &lhs, const CubemapInfo_t &rhs ) +{ + return ( lhs.m_nTableId < rhs.m_nTableId ); +} + + +typedef CUtlVector IntVector_t; +static CUtlVector s_EnvCubemapToBrushSides; + +static CUtlVector s_DefaultCubemapNames; +static char g_IsCubemapTexData[MAX_MAP_TEXDATA]; + + +struct CubemapSideData_t +{ + bool bHasEnvMapInMaterial; + bool bManuallyPickedByAnEnvCubemap; +}; + +static CubemapSideData_t s_aCubemapSideData[MAX_MAP_BRUSHSIDES]; + + + +inline bool SideHasCubemapAndWasntManuallyReferenced( int iSide ) +{ + return s_aCubemapSideData[iSide].bHasEnvMapInMaterial && !s_aCubemapSideData[iSide].bManuallyPickedByAnEnvCubemap; +} + + +void Cubemap_InsertSample( const Vector& origin, int size ) +{ + dcubemapsample_t *pSample = &g_CubemapSamples[g_nCubemapSamples]; + pSample->origin[0] = ( int )origin[0]; + pSample->origin[1] = ( int )origin[1]; + pSample->origin[2] = ( int )origin[2]; + pSample->size = size; + g_nCubemapSamples++; +} + +static const char *FindSkyboxMaterialName( void ) +{ + for( int i = 0; i < g_MainMap->num_entities; i++ ) + { + char* pEntity = ValueForKey(&g_MainMap->entities[i], "classname"); + if (!strcmp(pEntity, "worldspawn")) + { + return ValueForKey( &g_MainMap->entities[i], "skyname" ); + } + } + return NULL; +} + +static void BackSlashToForwardSlash( char *pname ) +{ + while ( *pname ) { + if ( *pname == '\\' ) + *pname = '/'; + pname++; + } +} + +static void ForwardSlashToBackSlash( char *pname ) +{ + while ( *pname ) { + if ( *pname == '/' ) + *pname = '\\'; + pname++; + } +} + + +//----------------------------------------------------------------------------- +// Finds materials that are used by a particular material +//----------------------------------------------------------------------------- +#define MAX_MATERIAL_NAME 512 + +// This is the list of materialvars which are used in our codebase to look up dependent materials +static const char *s_pDependentMaterialVar[] = +{ + "$bottommaterial", // Used by water materials + "$crackmaterial", // Used by shattered glass materials + "$fallbackmaterial", // Used by all materials + + "", // Always must be last +}; + +static const char *FindDependentMaterial( const char *pMaterialName, const char **ppMaterialVar = NULL ) +{ + // FIXME: This is a terrible way of doing this! It creates a dependency + // between vbsp and *all* code which reads dependent materials from materialvars + // At the time of writing this function, that means the engine + studiorender. + // We need a better way of figuring out how to do this, but for now I'm trying to do + // the fastest solution possible since it's close to ship + + static char pDependentMaterialName[MAX_MATERIAL_NAME]; + for( int i = 0; s_pDependentMaterialVar[i][0]; ++i ) + { + if ( !GetValueFromMaterial( pMaterialName, s_pDependentMaterialVar[i], pDependentMaterialName, MAX_MATERIAL_NAME - 1 ) ) + continue; + + if ( !Q_stricmp( pDependentMaterialName, pMaterialName ) ) + { + Warning( "Material %s is depending on itself through materialvar %s! Ignoring...\n", pMaterialName, s_pDependentMaterialVar[i] ); + continue; + } + + // Return the material var that caused the dependency + if ( ppMaterialVar ) + { + *ppMaterialVar = s_pDependentMaterialVar[i]; + } + +#ifdef _DEBUG + // FIXME: Note that this code breaks if a material has more than 1 dependent material + ++i; + static char pDependentMaterialName2[MAX_MATERIAL_NAME]; + while( s_pDependentMaterialVar[i][0] ) + { + Assert( !GetValueFromMaterial( pMaterialName, s_pDependentMaterialVar[i], pDependentMaterialName2, MAX_MATERIAL_NAME - 1 ) ); + ++i; + } +#endif + + return pDependentMaterialName; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Loads VTF files +//----------------------------------------------------------------------------- +static bool LoadSrcVTFFiles( IVTFTexture *pSrcVTFTextures[6], const char *pSkyboxMaterialBaseName, + int *pUnionTextureFlags, bool bHDR ) +{ + const char *facingName[6] = { "rt", "lf", "bk", "ft", "up", "dn" }; + int i; + for( i = 0; i < 6; i++ ) + { + char srcMaterialName[1024]; + sprintf( srcMaterialName, "%s%s", pSkyboxMaterialBaseName, facingName[i] ); + + IMaterial *pSkyboxMaterial = g_pMaterialSystem->FindMaterial( srcMaterialName, "skybox" ); + //IMaterialVar *pSkyTextureVar = pSkyboxMaterial->FindVar( bHDR ? "$hdrbasetexture" : "$basetexture", NULL ); //, bHDR ? false : true ); + IMaterialVar *pSkyTextureVar = pSkyboxMaterial->FindVar( "$basetexture", NULL ); // Since we're setting it to black anyway, just use $basetexture for HDR + const char *vtfName = pSkyTextureVar->GetStringValue(); + char srcVTFFileName[MAX_PATH]; + Q_snprintf( srcVTFFileName, MAX_PATH, "materials/%s.vtf", vtfName ); + + CUtlBuffer buf; + if ( !g_pFullFileSystem->ReadFile( srcVTFFileName, NULL, buf ) ) + { + // Try looking for a compressed HDR texture + if ( bHDR ) + { + /* // FIXME: We need a way to uncompress this format! + bool bHDRCompressed = true; + + pSkyTextureVar = pSkyboxMaterial->FindVar( "$hdrcompressedTexture", NULL ); + vtfName = pSkyTextureVar->GetStringValue(); + Q_snprintf( srcVTFFileName, MAX_PATH, "materials/%s.vtf", vtfName ); + + if ( !g_pFullFileSystem->ReadFile( srcVTFFileName, NULL, buf ) ) + */ + { + return false; + } + } + else + { + return false; + } + } + + pSrcVTFTextures[i] = CreateVTFTexture(); + if (!pSrcVTFTextures[i]->Unserialize(buf)) + { + Warning("*** Error unserializing skybox texture: %s\n", pSkyboxMaterialBaseName ); + return false; + } + + *pUnionTextureFlags |= pSrcVTFTextures[i]->Flags(); + int flagsNoAlpha = pSrcVTFTextures[i]->Flags() & ~( TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_ONEBITALPHA ); + int flagsFirstNoAlpha = pSrcVTFTextures[0]->Flags() & ~( TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_ONEBITALPHA ); + + // NOTE: texture[0] is a side texture that could be 1/2 height, so allow this and also allow 4x4 faces + if ( ( ( pSrcVTFTextures[i]->Width() != pSrcVTFTextures[0]->Width() ) && ( pSrcVTFTextures[i]->Width() != 4 ) ) || + ( ( pSrcVTFTextures[i]->Height() != pSrcVTFTextures[0]->Height() ) && ( pSrcVTFTextures[i]->Height() != pSrcVTFTextures[0]->Height()*2 ) && ( pSrcVTFTextures[i]->Height() != 4 ) ) || + ( flagsNoAlpha != flagsFirstNoAlpha ) ) + { + Warning("*** Error: Skybox vtf files for %s weren't compiled with the same size texture and/or same flags!\n", pSkyboxMaterialBaseName ); + return false; + } + + if ( bHDR ) + { + pSrcVTFTextures[i]->ConvertImageFormat( IMAGE_FORMAT_RGB323232F, false ); + pSrcVTFTextures[i]->GenerateMipmaps(); + pSrcVTFTextures[i]->ConvertImageFormat( IMAGE_FORMAT_RGBA16161616F, false ); + } + } + + return true; +} + +void VTFNameToHDRVTFName( const char *pSrcName, char *pDest, int maxLen, bool bHDR ) +{ + Q_strncpy( pDest, pSrcName, maxLen ); + if( !bHDR ) + { + return; + } + char *pDot = Q_stristr( pDest, ".vtf" ); + if( !pDot ) + { + return; + } + Q_strncpy( pDot, ".hdr.vtf", maxLen - ( pDot - pDest ) ); +} + +#define DEFAULT_CUBEMAP_SIZE 32 + +void CreateDefaultCubemaps( bool bHDR ) +{ + memset( g_IsCubemapTexData, 0, sizeof(g_IsCubemapTexData) ); + + // NOTE: This implementation depends on the fact that all VTF files contain + // all mipmap levels + const char *pSkyboxBaseName = FindSkyboxMaterialName(); + + if( !pSkyboxBaseName ) + { + if( s_DefaultCubemapNames.Count() ) + { + Warning( "This map uses env_cubemap, and you don't have a skybox, so no default env_cubemaps will be generated.\n" ); + } + return; + } + + char skyboxMaterialName[MAX_PATH]; + Q_snprintf( skyboxMaterialName, MAX_PATH, "skybox/%s", pSkyboxBaseName ); + + IVTFTexture *pSrcVTFTextures[6]; + + int unionTextureFlags = 0; + if( !LoadSrcVTFFiles( pSrcVTFTextures, skyboxMaterialName, &unionTextureFlags, bHDR ) ) + { + Warning( "Can't load skybox file %s to build the default cubemap!\n", skyboxMaterialName ); + return; + } + Msg( "Creating default %scubemaps for env_cubemap using skybox materials:\n %s*.vmt\n" + " ! Run buildcubemaps in the engine to get the correct cube maps.\n", bHDR ? "HDR " : "LDR ", skyboxMaterialName ); + + // Figure out the mip differences between the two textures + int iMipLevelOffset = 0; + int tmp = pSrcVTFTextures[0]->Width(); + while( tmp > DEFAULT_CUBEMAP_SIZE ) + { + iMipLevelOffset++; + tmp >>= 1; + } + + // Create the destination cubemap + IVTFTexture *pDstCubemap = CreateVTFTexture(); + pDstCubemap->Init( DEFAULT_CUBEMAP_SIZE, DEFAULT_CUBEMAP_SIZE, 1, + pSrcVTFTextures[0]->Format(), unionTextureFlags | TEXTUREFLAGS_ENVMAP, + pSrcVTFTextures[0]->FrameCount() ); + + // First iterate over all frames + for (int iFrame = 0; iFrame < pDstCubemap->FrameCount(); ++iFrame) + { + // Next iterate over all normal cube faces (we know there's 6 cause it's an envmap) + for (int iFace = 0; iFace < 6; ++iFace ) + { + // Finally, iterate over all mip levels in the *destination* + for (int iMip = 0; iMip < pDstCubemap->MipCount(); ++iMip ) + { + // Copy the bits from the source images into the cube faces + unsigned char *pSrcBits = pSrcVTFTextures[iFace]->ImageData( iFrame, 0, iMip + iMipLevelOffset ); + unsigned char *pDstBits = pDstCubemap->ImageData( iFrame, iFace, iMip ); + int iSize = pDstCubemap->ComputeMipSize( iMip ); + int iSrcMipSize = pSrcVTFTextures[iFace]->ComputeMipSize( iMip + iMipLevelOffset ); + + // !!! FIXME: Set this to black until HDR cubemaps are built properly! + memset( pDstBits, 0, iSize ); + continue; + + if ( ( pSrcVTFTextures[iFace]->Width() == 4 ) && ( pSrcVTFTextures[iFace]->Height() == 4 ) ) // If texture is 4x4 square + { + // Force mip level 2 to get the 1x1 face + unsigned char *pSrcBits = pSrcVTFTextures[iFace]->ImageData( iFrame, 0, 2 ); + int iSrcMipSize = pSrcVTFTextures[iFace]->ComputeMipSize( 2 ); + + // Replicate 1x1 mip level across entire face + //memset( pDstBits, 0, iSize ); + for ( int i = 0; i < ( iSize / iSrcMipSize ); i++ ) + { + memcpy( pDstBits + ( i * iSrcMipSize ), pSrcBits, iSrcMipSize ); + } + } + else if ( pSrcVTFTextures[iFace]->Width() == pSrcVTFTextures[iFace]->Height() ) // If texture is square + { + if ( iSrcMipSize != iSize ) + { + Warning( "%s - ERROR! Cannot copy square face for default cubemap! iSrcMipSize(%d) != iSize(%d)\n", skyboxMaterialName, iSrcMipSize, iSize ); + memset( pDstBits, 0, iSize ); + } + else + { + // Just copy the mip level + memcpy( pDstBits, pSrcBits, iSize ); + } + } + else if ( pSrcVTFTextures[iFace]->Width() == pSrcVTFTextures[iFace]->Height()*2 ) // If texture is rectangle 2x wide + { + int iMipWidth, iMipHeight, iMipDepth; + pDstCubemap->ComputeMipLevelDimensions( iMip, &iMipWidth, &iMipHeight, &iMipDepth ); + if ( ( iMipHeight > 1 ) && ( iSrcMipSize*2 != iSize ) ) + { + Warning( "%s - ERROR building default cube map! %d*2 != %d\n", skyboxMaterialName, iSrcMipSize, iSize ); + memset( pDstBits, 0, iSize ); + } + else + { + // Copy row at a time and repeat last row + memcpy( pDstBits, pSrcBits, iSize/2 ); + //memcpy( pDstBits + iSize/2, pSrcBits, iSize/2 ); + int nSrcRowSize = pSrcVTFTextures[iFace]->RowSizeInBytes( iMip + iMipLevelOffset ); + int nDstRowSize = pDstCubemap->RowSizeInBytes( iMip ); + if ( nSrcRowSize != nDstRowSize ) + { + Warning( "%s - ERROR building default cube map! nSrcRowSize(%d) != nDstRowSize(%d)!\n", skyboxMaterialName, nSrcRowSize, nDstRowSize ); + memset( pDstBits, 0, iSize ); + } + else + { + for ( int i = 0; i < ( iSize/2 / nSrcRowSize ); i++ ) + { + memcpy( pDstBits + iSize/2 + i*nSrcRowSize, pSrcBits + iSrcMipSize - nSrcRowSize, nSrcRowSize ); + } + } + } + } + else + { + // ERROR! This code only supports square and rectangluar 2x wide + Warning( "%s - Couldn't create default cubemap because texture res is %dx%d\n", skyboxMaterialName, pSrcVTFTextures[iFace]->Width(), pSrcVTFTextures[iFace]->Height() ); + memset( pDstBits, 0, iSize ); + return; + } + } + } + } + + ImageFormat originalFormat = pDstCubemap->Format(); + if( !bHDR ) + { + // Convert the cube to format that we can apply tools to it... + pDstCubemap->ConvertImageFormat( IMAGE_FORMAT_DEFAULT, false ); + } + + // Fixup the cubemap facing + pDstCubemap->FixCubemapFaceOrientation(); + + // Now that the bits are in place, compute the spheremaps... + pDstCubemap->GenerateSpheremap(); + + if( !bHDR ) + { + // Convert the cubemap to the final format + pDstCubemap->ConvertImageFormat( originalFormat, false ); + } + + // Write the puppy out! + char dstVTFFileName[1024]; + if( bHDR ) + { + sprintf( dstVTFFileName, "materials/maps/%s/cubemapdefault.hdr.vtf", mapbase ); + } + else + { + sprintf( dstVTFFileName, "materials/maps/%s/cubemapdefault.vtf", mapbase ); + } + + CUtlBuffer outputBuf; + if (!pDstCubemap->Serialize( outputBuf )) + { + Warning( "Error serializing default cubemap %s\n", dstVTFFileName ); + return; + } + + IZip *pak = GetPakFile(); + + // spit out the default one. + AddBufferToPak( pak, dstVTFFileName, outputBuf.Base(), outputBuf.TellPut(), false ); + + // spit out all of the ones that are attached to world geometry. + int i; + for( i = 0; i < s_DefaultCubemapNames.Count(); i++ ) + { + char vtfName[MAX_PATH]; + VTFNameToHDRVTFName( s_DefaultCubemapNames[i], vtfName, MAX_PATH, bHDR ); + if( FileExistsInPak( pak, vtfName ) ) + { + continue; + } + AddBufferToPak( pak, vtfName, outputBuf.Base(),outputBuf.TellPut(), false ); + } + + // Clean up the textures + for( i = 0; i < 6; i++ ) + { + DestroyVTFTexture( pSrcVTFTextures[i] ); + } + DestroyVTFTexture( pDstCubemap ); +} + +void Cubemap_CreateDefaultCubemaps( void ) +{ + CreateDefaultCubemaps( false ); + CreateDefaultCubemaps( true ); +} + +// Builds a list of what cubemaps manually were assigned to what faces +// in s_EnvCubemapToBrushSides. +void Cubemap_SaveBrushSides( const char *pSideListStr ) +{ + IntVector_t &brushSidesVector = s_EnvCubemapToBrushSides[s_EnvCubemapToBrushSides.AddToTail()]; + char *pTmp = ( char * )_alloca( strlen( pSideListStr ) + 1 ); + strcpy( pTmp, pSideListStr ); + const char *pScan = strtok( pTmp, " " ); + if( !pScan ) + { + return; + } + do + { + int brushSideID; + if( sscanf( pScan, "%d", &brushSideID ) == 1 ) + { + brushSidesVector.AddToTail( brushSideID ); + } + } while( ( pScan = strtok( NULL, " " ) ) ); +} + + +//----------------------------------------------------------------------------- +// Generate patched material name +//----------------------------------------------------------------------------- +static void GeneratePatchedName( const char *pMaterialName, const PatchInfo_t &info, bool bMaterialName, char *pBuffer, int nMaxLen ) +{ + const char *pSeparator = bMaterialName ? "_" : ""; + int nLen = Q_snprintf( pBuffer, nMaxLen, "maps/%s/%s%s%d_%d_%d", info.m_pMapName, + pMaterialName, pSeparator, info.m_pOrigin[0], info.m_pOrigin[1], info.m_pOrigin[2] ); + + if ( bMaterialName ) + { + Assert( nLen < TEXTURE_NAME_LENGTH - 1 ); + if ( nLen >= TEXTURE_NAME_LENGTH - 1 ) + { + Error( "Generated env_cubemap patch name : %s too long! (max = %d)\n", pBuffer, TEXTURE_NAME_LENGTH ); + } + } + + BackSlashToForwardSlash( pBuffer ); + Q_strlower( pBuffer ); +} + + +//----------------------------------------------------------------------------- +// Patches the $envmap for a material and all its dependents, returns true if any patching happened +//----------------------------------------------------------------------------- +static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, const PatchInfo_t &info, const char *pCubemapTexture ) +{ + // Do *NOT* patch the material if there is an $envmap specified and it's not 'env_cubemap' + + // FIXME: It's theoretically ok to patch the material if $envmap is not specified, + // because we're using the 'replace' block, which will only add the env_cubemap if + // $envmap is specified in the source material. But it will fail if someone adds + // a specific non-env_cubemap $envmap to the source material at a later point. Bleah + + // See if we have an $envmap to patch + bool bShouldPatchEnvCubemap = DoesMaterialHaveKeyValuePair( pMaterialName, "$envmap", "env_cubemap" ); + + // See if we have a dependent material to patch + bool bDependentMaterialPatched = false; + const char *pDependentMaterialVar = NULL; + const char *pDependentMaterial = FindDependentMaterial( pMaterialName, &pDependentMaterialVar ); + if ( pDependentMaterial ) + { + bDependentMaterialPatched = PatchEnvmapForMaterialAndDependents( pDependentMaterial, info, pCubemapTexture ); + } + + // If we have neither to patch, we're done + if ( !bShouldPatchEnvCubemap && !bDependentMaterialPatched ) + return false; + + // Otherwise we have to make a patched version of ourselves + char pPatchedMaterialName[1024]; + GeneratePatchedName( pMaterialName, info, true, pPatchedMaterialName, 1024 ); + + MaterialPatchInfo_t pPatchInfo[2]; + int nPatchCount = 0; + if ( bShouldPatchEnvCubemap ) + { + pPatchInfo[nPatchCount].m_pKey = "$envmap"; + pPatchInfo[nPatchCount].m_pRequiredOriginalValue = "env_cubemap"; + pPatchInfo[nPatchCount].m_pValue = pCubemapTexture; + ++nPatchCount; + } + + char pDependentPatchedMaterialName[1024]; + if ( bDependentMaterialPatched ) + { + // FIXME: Annoying! I either have to pass back the patched dependent material name + // or reconstruct it. Both are sucky. + GeneratePatchedName( pDependentMaterial, info, true, pDependentPatchedMaterialName, 1024 ); + pPatchInfo[nPatchCount].m_pKey = pDependentMaterialVar; + pPatchInfo[nPatchCount].m_pValue = pDependentPatchedMaterialName; + ++nPatchCount; + } + + CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_REPLACE ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Finds a texinfo that has a particular +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Create a VMT to override the specified texinfo which references the cubemap entity at the specified origin. +// Returns the index of the new (or preexisting) texinfo referencing that VMT. +// +// Also adds the new cubemap VTF filename to s_DefaultCubemapNames so it can copy the +// default (skybox) cubemap into this file so the cubemap doesn't have the pink checkerboard at +// runtime before they run buildcubemaps. +//----------------------------------------------------------------------------- +static int Cubemap_CreateTexInfo( int originalTexInfo, int origin[3] ) +{ + // Don't make cubemap tex infos for nodes + if ( originalTexInfo == TEXINFO_NODE ) + return originalTexInfo; + + texinfo_t *pTexInfo = &texinfo[originalTexInfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + if ( g_IsCubemapTexData[pTexInfo->texdata] ) + { + Warning("Multiple references for cubemap on texture %s!!!\n", pMaterialName ); + return originalTexInfo; + } + + // Get out of here if the originalTexInfo is already a generated material for this position. + char pStringToSearchFor[512]; + Q_snprintf( pStringToSearchFor, 512, "_%d_%d_%d", origin[0], origin[1], origin[2] ); + if ( Q_stristr( pMaterialName, pStringToSearchFor ) ) + return originalTexInfo; + + // Package up information needed to generate patch names + PatchInfo_t info; + info.m_pMapName = mapbase; + info.m_pOrigin[0] = origin[0]; + info.m_pOrigin[1] = origin[1]; + info.m_pOrigin[2] = origin[2]; + + // Generate the name of the patched material + char pGeneratedTexDataName[1024]; + GeneratePatchedName( pMaterialName, info, true, pGeneratedTexDataName, 1024 ); + + // Make sure the texdata doesn't already exist. + int nTexDataID = FindTexData( pGeneratedTexDataName ); + bool bHasTexData = (nTexDataID != -1); + if( !bHasTexData ) + { + // Generate the new "$envmap" texture name. + char pTextureName[1024]; + GeneratePatchedName( "c", info, false, pTextureName, 1024 ); + + // Hook the texture into the material and all dependent materials + // but if no hooking was necessary, exit out + if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName ) ) + return originalTexInfo; + + // Store off the name of the cubemap that we need to create since we successfully patched + char pFileName[1024]; + int nLen = Q_snprintf( pFileName, 1024, "materials/%s.vtf", pTextureName ); + int id = s_DefaultCubemapNames.AddToTail(); + s_DefaultCubemapNames[id] = new char[ nLen + 1 ]; + strcpy( s_DefaultCubemapNames[id], pFileName ); + + // Make a new texdata + nTexDataID = AddCloneTexData( pTexData, pGeneratedTexDataName ); + g_IsCubemapTexData[nTexDataID] = true; + } + + Assert( nTexDataID != -1 ); + + texinfo_t newTexInfo; + newTexInfo = *pTexInfo; + newTexInfo.texdata = nTexDataID; + + int nTexInfoID = -1; + + // See if we need to make a new texinfo + bool bHasTexInfo = false; + if( bHasTexData ) + { + nTexInfoID = FindTexInfo( newTexInfo ); + bHasTexInfo = (nTexInfoID != -1); + } + + // Make a new texinfo if we need to. + if( !bHasTexInfo ) + { + nTexInfoID = texinfo.AddToTail( newTexInfo ); + } + + Assert( nTexInfoID != -1 ); + return nTexInfoID; +} + +static int SideIDToIndex( int brushSideID ) +{ + int i; + for( i = 0; i < g_MainMap->nummapbrushsides; i++ ) + { + if( g_MainMap->brushsides[i].id == brushSideID ) + { + return i; + } + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Goes through s_EnvCubemapToBrushSides and does Cubemap_CreateTexInfo for each +// side referenced by an env_cubemap manually. +//----------------------------------------------------------------------------- +void Cubemap_FixupBrushSidesMaterials( void ) +{ + Msg( "fixing up env_cubemap materials on brush sides...\n" ); + Assert( s_EnvCubemapToBrushSides.Count() == g_nCubemapSamples ); + + int cubemapID; + for( cubemapID = 0; cubemapID < g_nCubemapSamples; cubemapID++ ) + { + IntVector_t &brushSidesVector = s_EnvCubemapToBrushSides[cubemapID]; + int i; + for( i = 0; i < brushSidesVector.Count(); i++ ) + { + int brushSideID = brushSidesVector[i]; + int sideIndex = SideIDToIndex( brushSideID ); + if( sideIndex < 0 ) + { + Warning("env_cubemap pointing at deleted brushside near (%d, %d, %d)\n", + g_CubemapSamples[cubemapID].origin[0], g_CubemapSamples[cubemapID].origin[1], g_CubemapSamples[cubemapID].origin[2] ); + + continue; + } + + side_t *pSide = &g_MainMap->brushsides[sideIndex]; + +#ifdef DEBUG + if ( pSide->pMapDisp ) + { + Assert( pSide->texinfo == pSide->pMapDisp->face.texinfo ); + } +#endif + + pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin ); + if ( pSide->pMapDisp ) + { + pSide->pMapDisp->face.texinfo = pSide->texinfo; + } + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void Cubemap_ResetCubemapSideData( void ) +{ + for ( int iSide = 0; iSide < MAX_MAP_BRUSHSIDES; ++iSide ) + { + s_aCubemapSideData[iSide].bHasEnvMapInMaterial = false; + s_aCubemapSideData[iSide].bManuallyPickedByAnEnvCubemap = false; + } +} + + +//----------------------------------------------------------------------------- +// Returns true if the material or any of its dependents use an $envmap +//----------------------------------------------------------------------------- +bool DoesMaterialOrDependentsUseEnvmap( const char *pPatchedMaterialName ) +{ + const char *pOriginalMaterialName = GetOriginalMaterialNameForPatchedMaterial( pPatchedMaterialName ); + if( DoesMaterialHaveKey( pOriginalMaterialName, "$envmap" ) ) + return true; + + const char *pDependentMaterial = FindDependentMaterial( pOriginalMaterialName ); + if ( !pDependentMaterial ) + return false; + + return DoesMaterialOrDependentsUseEnvmap( pDependentMaterial ); +} + + +//----------------------------------------------------------------------------- +// Builds a list of all texdatas which need fixing up +//----------------------------------------------------------------------------- +void Cubemap_InitCubemapSideData( void ) +{ + // This tree is used to prevent re-parsing material vars multiple times + CUtlRBTree lookup( 0, g_MainMap->nummapbrushsides, CubemapLessFunc ); + + // Fill in specular data. + for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide ) + { + side_t *pSide = &g_MainMap->brushsides[iSide]; + if ( !pSide ) + continue; + + if ( pSide->texinfo == TEXINFO_NODE ) + continue; + + texinfo_t *pTex = &texinfo[pSide->texinfo]; + if ( !pTex ) + continue; + + dtexdata_t *pTexData = GetTexData( pTex->texdata ); + if ( !pTexData ) + continue; + + CubemapInfo_t info; + info.m_nTableId = pTexData->nameStringTableID; + + // Have we encountered this materal? If so, then copy the data we cached off before + int i = lookup.Find( info ); + if ( i != lookup.InvalidIndex() ) + { + s_aCubemapSideData[iSide].bHasEnvMapInMaterial = lookup[i].m_bSpecular; + continue; + } + + // First time we've seen this material. Figure out if it uses env_cubemap + const char *pPatchedMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + info.m_bSpecular = DoesMaterialOrDependentsUseEnvmap( pPatchedMaterialName ); + s_aCubemapSideData[ iSide ].bHasEnvMapInMaterial = info.m_bSpecular; + lookup.Insert( info ); + } + + // Fill in cube map data. + for ( int iCubemap = 0; iCubemap < g_nCubemapSamples; ++iCubemap ) + { + IntVector_t &sideList = s_EnvCubemapToBrushSides[iCubemap]; + int nSideCount = sideList.Count(); + for ( int iSide = 0; iSide < nSideCount; ++iSide ) + { + int nSideID = sideList[iSide]; + int nIndex = SideIDToIndex( nSideID ); + if ( nIndex < 0 ) + continue; + + s_aCubemapSideData[nIndex].bManuallyPickedByAnEnvCubemap = true; + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int Cubemap_FindClosestCubemap( const Vector &entityOrigin, side_t *pSide ) +{ + if ( !pSide ) + return -1; + + // Return a valid (if random) cubemap if there's no winding + if ( !pSide->winding ) + return 0; + + // Calculate the center point. + Vector vecCenter; + vecCenter.Init(); + + for ( int iPoint = 0; iPoint < pSide->winding->numpoints; ++iPoint ) + { + VectorAdd( vecCenter, pSide->winding->p[iPoint], vecCenter ); + } + VectorScale( vecCenter, 1.0f / pSide->winding->numpoints, vecCenter ); + vecCenter += entityOrigin; + plane_t *pPlane = &g_MainMap->mapplanes[pSide->planenum]; + + // Find the closest cubemap. + int iMinCubemap = -1; + float flMinDist = FLT_MAX; + + // Look for cubemaps in front of the surface first. + for ( int iCubemap = 0; iCubemap < g_nCubemapSamples; ++iCubemap ) + { + dcubemapsample_t *pSample = &g_CubemapSamples[iCubemap]; + Vector vecSampleOrigin( static_cast( pSample->origin[0] ), + static_cast( pSample->origin[1] ), + static_cast( pSample->origin[2] ) ); + Vector vecDelta; + VectorSubtract( vecSampleOrigin, vecCenter, vecDelta ); + float flDist = vecDelta.NormalizeInPlace(); + float flDot = DotProduct( vecDelta, pPlane->normal ); + if ( ( flDot >= 0.0f ) && ( flDist < flMinDist ) ) + { + flMinDist = flDist; + iMinCubemap = iCubemap; + } + } + + // Didn't find anything in front search for closest. + if( iMinCubemap == -1 ) + { + for ( int iCubemap = 0; iCubemap < g_nCubemapSamples; ++iCubemap ) + { + dcubemapsample_t *pSample = &g_CubemapSamples[iCubemap]; + Vector vecSampleOrigin( static_cast( pSample->origin[0] ), + static_cast( pSample->origin[1] ), + static_cast( pSample->origin[2] ) ); + Vector vecDelta; + VectorSubtract( vecSampleOrigin, vecCenter, vecDelta ); + float flDist = vecDelta.Length(); + if ( flDist < flMinDist ) + { + flMinDist = flDist; + iMinCubemap = iCubemap; + } + } + } + + return iMinCubemap; +} + + +//----------------------------------------------------------------------------- +// For every specular surface that wasn't referenced by some env_cubemap, call Cubemap_CreateTexInfo. +//----------------------------------------------------------------------------- +void Cubemap_AttachDefaultCubemapToSpecularSides( void ) +{ + Cubemap_ResetCubemapSideData(); + Cubemap_InitCubemapSideData(); + + // build a mapping from side to entity id so that we can get the entity origin + CUtlVector sideToEntityIndex; + sideToEntityIndex.SetCount(g_MainMap->nummapbrushsides); + int i; + for ( i = 0; i < g_MainMap->nummapbrushsides; i++ ) + { + sideToEntityIndex[i] = -1; + } + + for ( i = 0; i < g_MainMap->nummapbrushes; i++ ) + { + int entityIndex = g_MainMap->mapbrushes[i].entitynum; + for ( int j = 0; j < g_MainMap->mapbrushes[i].numsides; j++ ) + { + side_t *side = &g_MainMap->mapbrushes[i].original_sides[j]; + int sideIndex = side - g_MainMap->brushsides; + sideToEntityIndex[sideIndex] = entityIndex; + } + } + + for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide ) + { + side_t *pSide = &g_MainMap->brushsides[iSide]; + if ( !SideHasCubemapAndWasntManuallyReferenced( iSide ) ) + continue; + + + int currentEntity = sideToEntityIndex[iSide]; + + int iCubemap = Cubemap_FindClosestCubemap( g_MainMap->entities[currentEntity].origin, pSide ); + if ( iCubemap == -1 ) + continue; + +#ifdef DEBUG + if ( pSide->pMapDisp ) + { + Assert( pSide->texinfo == pSide->pMapDisp->face.texinfo ); + } +#endif + pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin ); + if ( pSide->pMapDisp ) + { + pSide->pMapDisp->face.texinfo = pSide->texinfo; + } + } +} + +// Populate with cubemaps that were skipped +void Cubemap_AddUnreferencedCubemaps() +{ + char pTextureName[1024]; + char pFileName[1024]; + PatchInfo_t info; + dcubemapsample_t *pSample; + int i,j; + + for ( i=0; iorigin[0]; + info.m_pOrigin[1] = pSample->origin[1]; + info.m_pOrigin[2] = pSample->origin[2]; + GeneratePatchedName( "c", info, false, pTextureName, 1024 ); + + // find or add + for ( j=0; j - -face_t *NewFaceFromFace (face_t *f); -face_t *ComputeVisibleBrushSides( bspbrush_t *list ); - -//----------------------------------------------------------------------------- -// Purpose: Copies a face and its winding -// Input : *pFace - -// Output : face_t -//----------------------------------------------------------------------------- -face_t *CopyFace( face_t *pFace ) -{ - face_t *f = NewFaceFromFace( pFace ); - f->w = CopyWinding( pFace->w ); - - return f; -} - -//----------------------------------------------------------------------------- -// Purpose: Link this brush into the list for this leaf -// Input : *node - -// *brush - -//----------------------------------------------------------------------------- -void AddBrushToLeaf( node_t *node, bspbrush_t *brush ) -{ - brush->next = node->brushlist; - node->brushlist = brush; -} - -//----------------------------------------------------------------------------- -// Purpose: Recursively filter a brush through the tree -// Input : *node - -// *brush - -//----------------------------------------------------------------------------- -void MergeBrush_r( node_t *node, bspbrush_t *brush ) -{ - if ( node->planenum == PLANENUM_LEAF ) - { - if ( node->contents & CONTENTS_SOLID ) - { - FreeBrush( brush ); - } - else - { - AddBrushToLeaf( node, brush ); - } - return; - } - - bspbrush_t *front, *back; - SplitBrush( brush, node->planenum, &front, &back ); - FreeBrush( brush ); - - if ( front ) - { - MergeBrush_r( node->children[0], front ); - } - if ( back ) - { - MergeBrush_r( node->children[1], back ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Recursively filter a face into the tree leaving references to the -// original face in any visible leaves that a clipped fragment falls -// into. -// Input : *node - current head of tree -// *face - clipped face fragment -// *original - unclipped original face -// Output : Returns true if any references were left -//----------------------------------------------------------------------------- -bool MergeFace_r( node_t *node, face_t *face, face_t *original ) -{ - bool referenced = false; - - if ( node->planenum == PLANENUM_LEAF ) - { - if ( node->contents & CONTENTS_SOLID ) - { - FreeFace( face ); - return false; - } - - leafface_t *plist = new leafface_t; - plist->pFace = original; - plist->pNext = node->leaffacelist; - node->leaffacelist = plist; - - referenced = true; - } - else - { - // UNDONE: Don't copy the faces each time unless it's necessary!?!?! - plane_t *plane = &g_MainMap->mapplanes[node->planenum]; - winding_t *frontwinding, *backwinding, *onwinding; - - Vector offset; - WindingCenter( face->w, offset ); - - // UNDONE: Export epsilon from original face clipping code - ClassifyWindingEpsilon_Offset(face->w, plane->normal, plane->dist, 0.001, &frontwinding, &backwinding, &onwinding, -offset); - - if ( onwinding ) - { - // face is in the split plane, go down the appropriate side according to the facing direction - assert( frontwinding == NULL ); - assert( backwinding == NULL ); - - if ( DotProduct( g_MainMap->mapplanes[face->planenum].normal, g_MainMap->mapplanes[node->planenum].normal ) > 0 ) - { - frontwinding = onwinding; - } - else - { - backwinding = onwinding; - } - } - - if ( frontwinding ) - { - face_t *tmp = NewFaceFromFace( face ); - tmp->w = frontwinding; - referenced = MergeFace_r( node->children[0], tmp, original ); - } - if ( backwinding ) - { - face_t *tmp = NewFaceFromFace( face ); - tmp->w = backwinding; - bool test = MergeFace_r( node->children[1], tmp, original ); - referenced = referenced || test; - } - } - FreeFace( face ); - - return referenced; -} - -//----------------------------------------------------------------------------- -// Purpose: Loop through each face and filter it into the tree -// Input : *out - -// *pFaces - -//----------------------------------------------------------------------------- -face_t *FilterFacesIntoTree( tree_t *out, face_t *pFaces ) -{ - face_t *pLeafFaceList = NULL; - for ( face_t *f = pFaces; f; f = f->next ) - { - if( f->merged || f->split[0] || f->split[1] ) - continue; - - face_t *tmp = CopyFace( f ); - face_t *original = CopyFace( f ); - - if ( MergeFace_r( out->headnode, tmp, original ) ) - { - // clear out portal (comes from a different tree) - original->portal = NULL; - original->next = pLeafFaceList; - pLeafFaceList = original; - } - else - { - FreeFace( original ); - } - } - - return pLeafFaceList; -} - - -//----------------------------------------------------------------------------- -// Purpose: Splits the face list into faces from the same plane and tries to merge -// them if possible -// Input : **pFaceList - -//----------------------------------------------------------------------------- -void TryMergeFaceList( face_t **pFaceList ) -{ - face_t **pPlaneList = NULL; - - // divide the list into buckets by plane number - pPlaneList = new face_t *[g_MainMap->nummapplanes]; - memset( pPlaneList, 0, sizeof(face_t *) * g_MainMap->nummapplanes ); - - face_t *pFaces = *pFaceList; - face_t *pOutput = NULL; - - while ( pFaces ) - { - face_t *next = pFaces->next; - - // go ahead and delete the old split/merged faces - if ( pFaces->merged || pFaces->split[0] || pFaces->split[1] ) - { - Error("Split face in merge list!"); - } - else - { - // add to the list for this plane - pFaces->next = pPlaneList[pFaces->planenum]; - pPlaneList[pFaces->planenum] = pFaces; - } - - pFaces = next; - } - - // now merge each plane's list of faces - int merged = 0; - for ( int i = 0; i < g_MainMap->nummapplanes; i++ ) - { - if ( pPlaneList[i] ) - { - MergeFaceList( &pPlaneList[i] ); - } - - // move these over to the output face list - face_t *list = pPlaneList[i]; - while ( list ) - { - face_t *next = list->next; - - if ( list->merged ) - merged++; - - list->next = pOutput; - pOutput = list; - list = next; - } - } - - if ( merged ) - { - Msg("\nMerged %d detail faces...", merged ); - } - delete[] pPlaneList; - - *pFaceList = pOutput; -} - - -//----------------------------------------------------------------------------- -// Purpose: filter each brush in the list into the tree -// Input : *out - -// *brushes - -//----------------------------------------------------------------------------- -void FilterBrushesIntoTree( tree_t *out, bspbrush_t *brushes ) -{ - // Merge all of the brushes into the world tree - for ( bspbrush_t *plist = brushes; plist; plist = plist->next ) - { - MergeBrush_r( out->headnode, CopyBrush(plist) ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Build faces for the detail brushes and merge them into the BSP -// Input : *worldtree - -// brush_start - -// brush_end - -//----------------------------------------------------------------------------- -face_t *MergeDetailTree( tree_t *worldtree, int brush_start, int brush_end ) -{ - int start; - bspbrush_t *detailbrushes = NULL; - face_t *pFaces = NULL; - face_t *pLeafFaceList = NULL; - - // Grab the list of detail brushes - detailbrushes = MakeBspBrushList (brush_start, brush_end, g_MainMap->map_mins, g_MainMap->map_maxs, ONLY_DETAIL ); - if (detailbrushes) - { - start = Plat_FloatTime(); - Msg("Chop Details..."); - // if there are detail brushes, chop them against each other - if (!nocsg) - detailbrushes = ChopBrushes (detailbrushes); - - Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); - // Now mark the visible sides so we can eliminate all detail brush sides - // that are covered by other detail brush sides - // NOTE: This still leaves detail brush sides that are covered by the world. (these are removed in the merge operation) - Msg("Find Visible Detail Sides..."); - pFaces = ComputeVisibleBrushSides( detailbrushes ); - TryMergeFaceList( &pFaces ); - SubdivideFaceList( &pFaces ); - Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); - - start = Plat_FloatTime(); - Msg("Merging details..."); - // Merge the detail solids and faces into the world tree - // Merge all of the faces into the world tree - pLeafFaceList = FilterFacesIntoTree( worldtree, pFaces ); - FilterBrushesIntoTree( worldtree, detailbrushes ); - - FreeFaceList( pFaces ); - FreeBrushList(detailbrushes); - - Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); - } - - return pLeafFaceList; -} - - -//----------------------------------------------------------------------------- -// Purpose: Quick overlap test for brushes -// Input : *p1 - -// *p2 - -// Output : Returns false if the brushes cannot intersect -//----------------------------------------------------------------------------- -bool BrushBoxOverlap( bspbrush_t *p1, bspbrush_t *p2 ) -{ - if ( p1 == p2 ) - return false; - - for ( int i = 0; i < 3; i++ ) - { - if ( p1->mins[i] > p2->maxs[i] || p1->maxs[i] < p2->mins[i] ) - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pFace - input face to test -// *pbrush - brush to clip face against -// **pOutputList - list of faces clipped from pFace -// Output : Returns true if the brush completely clips the face -//----------------------------------------------------------------------------- -// NOTE: This assumes the brushes have already been chopped so that no solid space -// is enclosed by more than one brush!! -bool ClipFaceToBrush( face_t *pFace, bspbrush_t *pbrush, face_t **pOutputList ) -{ - int planenum = pFace->planenum & (~1); - int foundSide = -1; - - CUtlVector sortedSides; - - int i; - for ( i = 0; i < pbrush->numsides && foundSide < 0; i++ ) - { - int bplane = pbrush->sides[i].planenum & (~1); - if ( bplane == planenum ) - foundSide = i; - } - - Vector offset = -0.5f * (pbrush->maxs + pbrush->mins); - face_t *currentface = CopyFace( pFace ); - - if ( foundSide >= 0 ) - { - sortedSides.RemoveAll(); - for ( i = 0; i < pbrush->numsides; i++ ) - { - // don't clip to bevels - if ( pbrush->sides[i].bevel ) - continue; - - if ( g_MainMap->mapplanes[pbrush->sides[i].planenum].type <= PLANE_Z ) - { - sortedSides.AddToHead( i ); - } - else - { - sortedSides.AddToTail( i ); - } - } - - for ( i = 0; i < sortedSides.Size(); i++ ) - { - int index = sortedSides[i]; - if ( index == foundSide ) - continue; - - plane_t *plane = &g_MainMap->mapplanes[pbrush->sides[index].planenum]; - winding_t *frontwinding, *backwinding; - ClipWindingEpsilon_Offset(currentface->w, plane->normal, plane->dist, 0.001, &frontwinding, &backwinding, offset); - - // only clip if some part of this face is on the back side of all brush sides - if ( !backwinding || WindingIsTiny(backwinding)) - { - FreeFaceList( *pOutputList ); - *pOutputList = NULL; - break; - } - if ( frontwinding && !WindingIsTiny(frontwinding) ) - { - // add this fragment to the return list - // make a face for the fragment - face_t *f = NewFaceFromFace( pFace ); - f->w = frontwinding; - - // link the fragment in - f->next = *pOutputList; - *pOutputList = f; - } - - // update the current winding to be the part behind each plane - FreeWinding( currentface->w ); - currentface->w = backwinding; - } - - // free the bit that is left in solid or not clipped (if we broke out early) - FreeFace( currentface ); - - // if we made it all the way through and didn't produce any fragments then the whole face was clipped away - if ( !*pOutputList && i == sortedSides.Size() ) - { - return true; - } - } - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: Given an original side and chopped winding, make a face_t -// Input : *side - side of the original brush -// *winding - winding for this face (portion of the side) -// Output : face_t -//----------------------------------------------------------------------------- -face_t *MakeBrushFace( side_t *originalSide, winding_t *winding ) -{ - face_t *f = AllocFace(); - f->merged = NULL; - f->split[0] = f->split[1] = NULL; - f->w = CopyWinding( winding ); - f->originalface = originalSide; - // - // save material info - // - f->texinfo = originalSide->texinfo; - f->dispinfo = -1; - - // save plane info - f->planenum = originalSide->planenum; - f->contents = originalSide->contents; - - return f; -} - - -//----------------------------------------------------------------------------- -// Purpose: Chop away sides that are inside other brushes. -// Brushes have already been chopped up so that they do not overlap, -// they merely touch. -// Input : *list - list of brushes -// Output : face_t * - list of visible faces (some marked bad/split) -//----------------------------------------------------------------------------- -// assumes brushes were chopped! - - -side_t *FindOriginalSide( mapbrush_t *mb, side_t *pBspSide ) -{ - side_t *bestside = NULL; - float bestdot = 0; - - plane_t *p1 = g_MainMap->mapplanes + pBspSide->planenum; - - for (int i=0 ; inumsides ; i++) - { - side_t *side = &mb->original_sides[i]; - if (side->bevel) - continue; - if (side->texinfo == TEXINFO_NODE) - continue; // non-visible - if ((side->planenum&~1) == (pBspSide->planenum&~1)) - { // exact match - return mb->original_sides + i; - } - // see how close the match is - plane_t *p2 = &g_MainMap->mapplanes[side->planenum&~1]; - float dot = DotProduct (p1->normal, p2->normal); - if (dot > bestdot) - { - bestdot = dot; - bestside = side; - } - } - - if ( !bestside ) - { - Error( "Bad detail brush side\n" ); - } - return bestside; -} - -// Get a list of brushes from pBrushList that could cut faces on the source brush -int GetListOfCutBrushes( CUtlVector &out, bspbrush_t *pSourceBrush, bspbrush_t *pBrushList ) -{ - mapbrush_t *mb = pSourceBrush->original; - for ( bspbrush_t *walk = pBrushList; walk; walk = walk->next ) - { - if ( walk == pSourceBrush ) - continue; - - // only clip to transparent brushes if the original brush is transparent - if ( walk->original->contents & TRANSPARENT_CONTENTS ) - { - if ( !(mb->contents & TRANSPARENT_CONTENTS) ) - continue; - } - - // don't clip to clip brushes, etc. - if ( !(walk->original->contents & ALL_VISIBLE_CONTENTS) ) - continue; - - // brushes overlap, test faces - if ( !BrushBoxOverlap( pSourceBrush, walk ) ) - continue; - - out.AddToTail( walk ); - } - return out.Count(); -} - -// Count the number of real (unsplit) faces in the list -static int CountFaceList( face_t *f ) -{ - int count = 0; - for ( ; f; f = f->next ) - { - if ( f->split[0] ) - continue; - count++; - } - - return count; -} - -// Clips f to a list of potential cutting brushes -// If f clips into new faces, returns the list of new faces in pOutputList -static void ClipFaceToBrushList( face_t *f, const CUtlVector &cutBrushes, face_t **pOutputList ) -{ - *pOutputList = NULL; - - if ( f->split[0] ) - return; - - face_t *pClipList = CopyFace( f ); - pClipList->next = NULL; - bool clipped = false; - for ( int i = 0; i < cutBrushes.Count(); i++ ) - { - bspbrush_t *cut = cutBrushes[i]; - for ( face_t *pCutFace = pClipList; pCutFace; pCutFace = pCutFace->next ) - { - face_t *pClip = NULL; - // already split, no need to clip - if ( pCutFace->split[0] ) - continue; - - if ( ClipFaceToBrush( pCutFace, cut, &pClip ) ) - { - clipped = true; - // mark face bad, the brush clipped it away - pCutFace->split[0] = pCutFace; - } - else if ( pClip ) - { - clipped = true; - // mark this face as split - pCutFace->split[0] = pCutFace; - - // insert face fragments at head of list (UNDONE: reverses order, do we care?) - while ( pClip ) - { - face_t *next = pClip->next; - pClip->next = pClipList; - pClipList = pClip; - pClip = next; - } - } - } - } - if ( clipped ) - { - *pOutputList = pClipList; - } - else - { - // didn't do any clipping, go ahead and free the copy of the face here. - FreeFaceList( pClipList ); - } -} - -// Compute a list of faces that are visible on the detail brush sides -face_t *ComputeVisibleBrushSides( bspbrush_t *list ) -{ - face_t *pTotalFaces = NULL; - CUtlVector cutBrushes; - - // Go through the whole brush list - for ( bspbrush_t *pbrush = list; pbrush; pbrush = pbrush->next ) - { - face_t *pFaces = NULL; - mapbrush_t *mb = pbrush->original; - - if ( !(mb->contents & ALL_VISIBLE_CONTENTS) ) - continue; - - // Make a face for each brush side, then clip it by the other - // details to see if any fragments are visible - for ( int i = 0; i < pbrush->numsides; i++ ) - { - winding_t *winding = pbrush->sides[i].winding; - if ( !winding ) - continue; - - if (! (pbrush->sides[i].contents & ALL_VISIBLE_CONTENTS) ) - continue; - - side_t *side = FindOriginalSide( mb, pbrush->sides + i ); - face_t *f = MakeBrushFace( side, winding ); - - // link to head of face list - f->next = pFaces; - pFaces = f; - } - - // Make a list of brushes that can cut the face list for this brush - cutBrushes.RemoveAll(); - if ( GetListOfCutBrushes( cutBrushes, pbrush, list ) ) - { - // now cut each face to find visible fragments - for ( face_t *f = pFaces; f; f = f->next ) - { - // this will be a new list of faces that this face cuts into - face_t *pClip = NULL; - ClipFaceToBrushList( f, cutBrushes, &pClip ); - if ( pClip ) - { - int outCount = CountFaceList(pClip); - // it cut into more faces (or it was completely cut away) - if ( outCount <= 1 ) - { - // was removed or cut down, mark as split - f->split[0] = f; - // insert face fragments at head of list (UNDONE: reverses order, do we care?) - while ( pClip ) - { - face_t *next = pClip->next; - pClip->next = pFaces; - pFaces = pClip; - pClip = next; - } - } - else - { - // it cut into more than one visible fragment - // Don't fragment details - // UNDONE: Build 2d convex hull of this list and swap face winding - // with that polygon? That would fix the remaining issues. - FreeFaceList( pClip ); - pClip = NULL; - } - } - } - } - - // move visible fragments to global face list - while ( pFaces ) - { - face_t *next = pFaces->next; - if ( pFaces->split[0] ) - { - FreeFace( pFaces ); - } - else - { - pFaces->next = pTotalFaces; - pTotalFaces = pFaces; - } - pFaces = next; - } - } - - return pTotalFaces; -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Builds/merges the BSP tree of detail brushes +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "detail.h" +#include "utlvector.h" +#include + +face_t *NewFaceFromFace (face_t *f); +face_t *ComputeVisibleBrushSides( bspbrush_t *list ); + +//----------------------------------------------------------------------------- +// Purpose: Copies a face and its winding +// Input : *pFace - +// Output : face_t +//----------------------------------------------------------------------------- +face_t *CopyFace( face_t *pFace ) +{ + face_t *f = NewFaceFromFace( pFace ); + f->w = CopyWinding( pFace->w ); + + return f; +} + +//----------------------------------------------------------------------------- +// Purpose: Link this brush into the list for this leaf +// Input : *node - +// *brush - +//----------------------------------------------------------------------------- +void AddBrushToLeaf( node_t *node, bspbrush_t *brush ) +{ + brush->next = node->brushlist; + node->brushlist = brush; +} + +//----------------------------------------------------------------------------- +// Purpose: Recursively filter a brush through the tree +// Input : *node - +// *brush - +//----------------------------------------------------------------------------- +void MergeBrush_r( node_t *node, bspbrush_t *brush ) +{ + if ( node->planenum == PLANENUM_LEAF ) + { + if ( node->contents & CONTENTS_SOLID ) + { + FreeBrush( brush ); + } + else + { + AddBrushToLeaf( node, brush ); + } + return; + } + + bspbrush_t *front, *back; + SplitBrush( brush, node->planenum, &front, &back ); + FreeBrush( brush ); + + if ( front ) + { + MergeBrush_r( node->children[0], front ); + } + if ( back ) + { + MergeBrush_r( node->children[1], back ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Recursively filter a face into the tree leaving references to the +// original face in any visible leaves that a clipped fragment falls +// into. +// Input : *node - current head of tree +// *face - clipped face fragment +// *original - unclipped original face +// Output : Returns true if any references were left +//----------------------------------------------------------------------------- +bool MergeFace_r( node_t *node, face_t *face, face_t *original ) +{ + bool referenced = false; + + if ( node->planenum == PLANENUM_LEAF ) + { + if ( node->contents & CONTENTS_SOLID ) + { + FreeFace( face ); + return false; + } + + leafface_t *plist = new leafface_t; + plist->pFace = original; + plist->pNext = node->leaffacelist; + node->leaffacelist = plist; + + referenced = true; + } + else + { + // UNDONE: Don't copy the faces each time unless it's necessary!?!?! + plane_t *plane = &g_MainMap->mapplanes[node->planenum]; + winding_t *frontwinding, *backwinding, *onwinding; + + Vector offset; + WindingCenter( face->w, offset ); + + // UNDONE: Export epsilon from original face clipping code + ClassifyWindingEpsilon_Offset(face->w, plane->normal, plane->dist, 0.001, &frontwinding, &backwinding, &onwinding, -offset); + + if ( onwinding ) + { + // face is in the split plane, go down the appropriate side according to the facing direction + assert( frontwinding == NULL ); + assert( backwinding == NULL ); + + if ( DotProduct( g_MainMap->mapplanes[face->planenum].normal, g_MainMap->mapplanes[node->planenum].normal ) > 0 ) + { + frontwinding = onwinding; + } + else + { + backwinding = onwinding; + } + } + + if ( frontwinding ) + { + face_t *tmp = NewFaceFromFace( face ); + tmp->w = frontwinding; + referenced = MergeFace_r( node->children[0], tmp, original ); + } + if ( backwinding ) + { + face_t *tmp = NewFaceFromFace( face ); + tmp->w = backwinding; + bool test = MergeFace_r( node->children[1], tmp, original ); + referenced = referenced || test; + } + } + FreeFace( face ); + + return referenced; +} + +//----------------------------------------------------------------------------- +// Purpose: Loop through each face and filter it into the tree +// Input : *out - +// *pFaces - +//----------------------------------------------------------------------------- +face_t *FilterFacesIntoTree( tree_t *out, face_t *pFaces ) +{ + face_t *pLeafFaceList = NULL; + for ( face_t *f = pFaces; f; f = f->next ) + { + if( f->merged || f->split[0] || f->split[1] ) + continue; + + face_t *tmp = CopyFace( f ); + face_t *original = CopyFace( f ); + + if ( MergeFace_r( out->headnode, tmp, original ) ) + { + // clear out portal (comes from a different tree) + original->portal = NULL; + original->next = pLeafFaceList; + pLeafFaceList = original; + } + else + { + FreeFace( original ); + } + } + + return pLeafFaceList; +} + + +//----------------------------------------------------------------------------- +// Purpose: Splits the face list into faces from the same plane and tries to merge +// them if possible +// Input : **pFaceList - +//----------------------------------------------------------------------------- +void TryMergeFaceList( face_t **pFaceList ) +{ + face_t **pPlaneList = NULL; + + // divide the list into buckets by plane number + pPlaneList = new face_t *[g_MainMap->nummapplanes]; + memset( pPlaneList, 0, sizeof(face_t *) * g_MainMap->nummapplanes ); + + face_t *pFaces = *pFaceList; + face_t *pOutput = NULL; + + while ( pFaces ) + { + face_t *next = pFaces->next; + + // go ahead and delete the old split/merged faces + if ( pFaces->merged || pFaces->split[0] || pFaces->split[1] ) + { + Error("Split face in merge list!"); + } + else + { + // add to the list for this plane + pFaces->next = pPlaneList[pFaces->planenum]; + pPlaneList[pFaces->planenum] = pFaces; + } + + pFaces = next; + } + + // now merge each plane's list of faces + int merged = 0; + for ( int i = 0; i < g_MainMap->nummapplanes; i++ ) + { + if ( pPlaneList[i] ) + { + MergeFaceList( &pPlaneList[i] ); + } + + // move these over to the output face list + face_t *list = pPlaneList[i]; + while ( list ) + { + face_t *next = list->next; + + if ( list->merged ) + merged++; + + list->next = pOutput; + pOutput = list; + list = next; + } + } + + if ( merged ) + { + Msg("\nMerged %d detail faces...", merged ); + } + delete[] pPlaneList; + + *pFaceList = pOutput; +} + + +//----------------------------------------------------------------------------- +// Purpose: filter each brush in the list into the tree +// Input : *out - +// *brushes - +//----------------------------------------------------------------------------- +void FilterBrushesIntoTree( tree_t *out, bspbrush_t *brushes ) +{ + // Merge all of the brushes into the world tree + for ( bspbrush_t *plist = brushes; plist; plist = plist->next ) + { + MergeBrush_r( out->headnode, CopyBrush(plist) ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Build faces for the detail brushes and merge them into the BSP +// Input : *worldtree - +// brush_start - +// brush_end - +//----------------------------------------------------------------------------- +face_t *MergeDetailTree( tree_t *worldtree, int brush_start, int brush_end ) +{ + int start; + bspbrush_t *detailbrushes = NULL; + face_t *pFaces = NULL; + face_t *pLeafFaceList = NULL; + + // Grab the list of detail brushes + detailbrushes = MakeBspBrushList (brush_start, brush_end, g_MainMap->map_mins, g_MainMap->map_maxs, ONLY_DETAIL ); + if (detailbrushes) + { + start = Plat_FloatTime(); + Msg("Chop Details..."); + // if there are detail brushes, chop them against each other + if (!nocsg) + detailbrushes = ChopBrushes (detailbrushes); + + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); + // Now mark the visible sides so we can eliminate all detail brush sides + // that are covered by other detail brush sides + // NOTE: This still leaves detail brush sides that are covered by the world. (these are removed in the merge operation) + Msg("Find Visible Detail Sides..."); + pFaces = ComputeVisibleBrushSides( detailbrushes ); + TryMergeFaceList( &pFaces ); + SubdivideFaceList( &pFaces ); + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); + + start = Plat_FloatTime(); + Msg("Merging details..."); + // Merge the detail solids and faces into the world tree + // Merge all of the faces into the world tree + pLeafFaceList = FilterFacesIntoTree( worldtree, pFaces ); + FilterBrushesIntoTree( worldtree, detailbrushes ); + + FreeFaceList( pFaces ); + FreeBrushList(detailbrushes); + + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); + } + + return pLeafFaceList; +} + + +//----------------------------------------------------------------------------- +// Purpose: Quick overlap test for brushes +// Input : *p1 - +// *p2 - +// Output : Returns false if the brushes cannot intersect +//----------------------------------------------------------------------------- +bool BrushBoxOverlap( bspbrush_t *p1, bspbrush_t *p2 ) +{ + if ( p1 == p2 ) + return false; + + for ( int i = 0; i < 3; i++ ) + { + if ( p1->mins[i] > p2->maxs[i] || p1->maxs[i] < p2->mins[i] ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFace - input face to test +// *pbrush - brush to clip face against +// **pOutputList - list of faces clipped from pFace +// Output : Returns true if the brush completely clips the face +//----------------------------------------------------------------------------- +// NOTE: This assumes the brushes have already been chopped so that no solid space +// is enclosed by more than one brush!! +bool ClipFaceToBrush( face_t *pFace, bspbrush_t *pbrush, face_t **pOutputList ) +{ + int planenum = pFace->planenum & (~1); + int foundSide = -1; + + CUtlVector sortedSides; + + int i; + for ( i = 0; i < pbrush->numsides && foundSide < 0; i++ ) + { + int bplane = pbrush->sides[i].planenum & (~1); + if ( bplane == planenum ) + foundSide = i; + } + + Vector offset = -0.5f * (pbrush->maxs + pbrush->mins); + face_t *currentface = CopyFace( pFace ); + + if ( foundSide >= 0 ) + { + sortedSides.RemoveAll(); + for ( i = 0; i < pbrush->numsides; i++ ) + { + // don't clip to bevels + if ( pbrush->sides[i].bevel ) + continue; + + if ( g_MainMap->mapplanes[pbrush->sides[i].planenum].type <= PLANE_Z ) + { + sortedSides.AddToHead( i ); + } + else + { + sortedSides.AddToTail( i ); + } + } + + for ( i = 0; i < sortedSides.Size(); i++ ) + { + int index = sortedSides[i]; + if ( index == foundSide ) + continue; + + plane_t *plane = &g_MainMap->mapplanes[pbrush->sides[index].planenum]; + winding_t *frontwinding, *backwinding; + ClipWindingEpsilon_Offset(currentface->w, plane->normal, plane->dist, 0.001, &frontwinding, &backwinding, offset); + + // only clip if some part of this face is on the back side of all brush sides + if ( !backwinding || WindingIsTiny(backwinding)) + { + FreeFaceList( *pOutputList ); + *pOutputList = NULL; + break; + } + if ( frontwinding && !WindingIsTiny(frontwinding) ) + { + // add this fragment to the return list + // make a face for the fragment + face_t *f = NewFaceFromFace( pFace ); + f->w = frontwinding; + + // link the fragment in + f->next = *pOutputList; + *pOutputList = f; + } + + // update the current winding to be the part behind each plane + FreeWinding( currentface->w ); + currentface->w = backwinding; + } + + // free the bit that is left in solid or not clipped (if we broke out early) + FreeFace( currentface ); + + // if we made it all the way through and didn't produce any fragments then the whole face was clipped away + if ( !*pOutputList && i == sortedSides.Size() ) + { + return true; + } + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Given an original side and chopped winding, make a face_t +// Input : *side - side of the original brush +// *winding - winding for this face (portion of the side) +// Output : face_t +//----------------------------------------------------------------------------- +face_t *MakeBrushFace( side_t *originalSide, winding_t *winding ) +{ + face_t *f = AllocFace(); + f->merged = NULL; + f->split[0] = f->split[1] = NULL; + f->w = CopyWinding( winding ); + f->originalface = originalSide; + // + // save material info + // + f->texinfo = originalSide->texinfo; + f->dispinfo = -1; + + // save plane info + f->planenum = originalSide->planenum; + f->contents = originalSide->contents; + + return f; +} + + +//----------------------------------------------------------------------------- +// Purpose: Chop away sides that are inside other brushes. +// Brushes have already been chopped up so that they do not overlap, +// they merely touch. +// Input : *list - list of brushes +// Output : face_t * - list of visible faces (some marked bad/split) +//----------------------------------------------------------------------------- +// assumes brushes were chopped! + + +side_t *FindOriginalSide( mapbrush_t *mb, side_t *pBspSide ) +{ + side_t *bestside = NULL; + float bestdot = 0; + + plane_t *p1 = g_MainMap->mapplanes + pBspSide->planenum; + + for (int i=0 ; inumsides ; i++) + { + side_t *side = &mb->original_sides[i]; + if (side->bevel) + continue; + if (side->texinfo == TEXINFO_NODE) + continue; // non-visible + if ((side->planenum&~1) == (pBspSide->planenum&~1)) + { // exact match + return mb->original_sides + i; + } + // see how close the match is + plane_t *p2 = &g_MainMap->mapplanes[side->planenum&~1]; + float dot = DotProduct (p1->normal, p2->normal); + if (dot > bestdot) + { + bestdot = dot; + bestside = side; + } + } + + if ( !bestside ) + { + Error( "Bad detail brush side\n" ); + } + return bestside; +} + +// Get a list of brushes from pBrushList that could cut faces on the source brush +int GetListOfCutBrushes( CUtlVector &out, bspbrush_t *pSourceBrush, bspbrush_t *pBrushList ) +{ + mapbrush_t *mb = pSourceBrush->original; + for ( bspbrush_t *walk = pBrushList; walk; walk = walk->next ) + { + if ( walk == pSourceBrush ) + continue; + + // only clip to transparent brushes if the original brush is transparent + if ( walk->original->contents & TRANSPARENT_CONTENTS ) + { + if ( !(mb->contents & TRANSPARENT_CONTENTS) ) + continue; + } + + // don't clip to clip brushes, etc. + if ( !(walk->original->contents & ALL_VISIBLE_CONTENTS) ) + continue; + + // brushes overlap, test faces + if ( !BrushBoxOverlap( pSourceBrush, walk ) ) + continue; + + out.AddToTail( walk ); + } + return out.Count(); +} + +// Count the number of real (unsplit) faces in the list +static int CountFaceList( face_t *f ) +{ + int count = 0; + for ( ; f; f = f->next ) + { + if ( f->split[0] ) + continue; + count++; + } + + return count; +} + +// Clips f to a list of potential cutting brushes +// If f clips into new faces, returns the list of new faces in pOutputList +static void ClipFaceToBrushList( face_t *f, const CUtlVector &cutBrushes, face_t **pOutputList ) +{ + *pOutputList = NULL; + + if ( f->split[0] ) + return; + + face_t *pClipList = CopyFace( f ); + pClipList->next = NULL; + bool clipped = false; + for ( int i = 0; i < cutBrushes.Count(); i++ ) + { + bspbrush_t *cut = cutBrushes[i]; + for ( face_t *pCutFace = pClipList; pCutFace; pCutFace = pCutFace->next ) + { + face_t *pClip = NULL; + // already split, no need to clip + if ( pCutFace->split[0] ) + continue; + + if ( ClipFaceToBrush( pCutFace, cut, &pClip ) ) + { + clipped = true; + // mark face bad, the brush clipped it away + pCutFace->split[0] = pCutFace; + } + else if ( pClip ) + { + clipped = true; + // mark this face as split + pCutFace->split[0] = pCutFace; + + // insert face fragments at head of list (UNDONE: reverses order, do we care?) + while ( pClip ) + { + face_t *next = pClip->next; + pClip->next = pClipList; + pClipList = pClip; + pClip = next; + } + } + } + } + if ( clipped ) + { + *pOutputList = pClipList; + } + else + { + // didn't do any clipping, go ahead and free the copy of the face here. + FreeFaceList( pClipList ); + } +} + +// Compute a list of faces that are visible on the detail brush sides +face_t *ComputeVisibleBrushSides( bspbrush_t *list ) +{ + face_t *pTotalFaces = NULL; + CUtlVector cutBrushes; + + // Go through the whole brush list + for ( bspbrush_t *pbrush = list; pbrush; pbrush = pbrush->next ) + { + face_t *pFaces = NULL; + mapbrush_t *mb = pbrush->original; + + if ( !(mb->contents & ALL_VISIBLE_CONTENTS) ) + continue; + + // Make a face for each brush side, then clip it by the other + // details to see if any fragments are visible + for ( int i = 0; i < pbrush->numsides; i++ ) + { + winding_t *winding = pbrush->sides[i].winding; + if ( !winding ) + continue; + + if (! (pbrush->sides[i].contents & ALL_VISIBLE_CONTENTS) ) + continue; + + side_t *side = FindOriginalSide( mb, pbrush->sides + i ); + face_t *f = MakeBrushFace( side, winding ); + + // link to head of face list + f->next = pFaces; + pFaces = f; + } + + // Make a list of brushes that can cut the face list for this brush + cutBrushes.RemoveAll(); + if ( GetListOfCutBrushes( cutBrushes, pbrush, list ) ) + { + // now cut each face to find visible fragments + for ( face_t *f = pFaces; f; f = f->next ) + { + // this will be a new list of faces that this face cuts into + face_t *pClip = NULL; + ClipFaceToBrushList( f, cutBrushes, &pClip ); + if ( pClip ) + { + int outCount = CountFaceList(pClip); + // it cut into more faces (or it was completely cut away) + if ( outCount <= 1 ) + { + // was removed or cut down, mark as split + f->split[0] = f; + // insert face fragments at head of list (UNDONE: reverses order, do we care?) + while ( pClip ) + { + face_t *next = pClip->next; + pClip->next = pFaces; + pFaces = pClip; + pClip = next; + } + } + else + { + // it cut into more than one visible fragment + // Don't fragment details + // UNDONE: Build 2d convex hull of this list and swap face winding + // with that polygon? That would fix the remaining issues. + FreeFaceList( pClip ); + pClip = NULL; + } + } + } + } + + // move visible fragments to global face list + while ( pFaces ) + { + face_t *next = pFaces->next; + if ( pFaces->split[0] ) + { + FreeFace( pFaces ); + } + else + { + pFaces->next = pTotalFaces; + pTotalFaces = pFaces; + } + pFaces = next; + } + } + + return pTotalFaces; +} diff --git a/mp/src/utils/vbsp/detail.h b/mp/src/utils/vbsp/detail.h index aa0147d1..64f02b33 100644 --- a/mp/src/utils/vbsp/detail.h +++ b/mp/src/utils/vbsp/detail.h @@ -1,18 +1,18 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef DETAIL_H -#define DETAIL_H - -#ifdef _WIN32 -#pragma once -#endif - -face_t *MergeDetailTree( tree_t *worldtree, int brush_start, int brush_end ); - - -#endif // DETAIL_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef DETAIL_H +#define DETAIL_H + +#ifdef _WIN32 +#pragma once +#endif + +face_t *MergeDetailTree( tree_t *worldtree, int brush_start, int brush_end ); + + +#endif // DETAIL_H diff --git a/mp/src/utils/vbsp/detailobjects.cpp b/mp/src/utils/vbsp/detailobjects.cpp index b110534c..e7475d94 100644 --- a/mp/src/utils/vbsp/detailobjects.cpp +++ b/mp/src/utils/vbsp/detailobjects.cpp @@ -1,966 +1,966 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: Places "detail" objects which are client-only renderable things -// -// $Revision: $ -// $NoKeywords: $ -//=============================================================================// - -#include -#include "vbsp.h" -#include "bsplib.h" -#include "KeyValues.h" -#include "utlsymbol.h" -#include "utlvector.h" -#include -#include "bspfile.h" -#include "utilmatlib.h" -#include "gamebspfile.h" -#include "mathlib/VMatrix.h" -#include "materialpatch.h" -#include "pacifier.h" -#include "vstdlib/random.h" -#include "builddisp.h" -#include "disp_vbsp.h" -#include "UtlBuffer.h" -#include "CollisionUtils.h" -#include -#include "UtlLinkedList.h" -#include "byteswap.h" -#include "writebsp.h" - -//----------------------------------------------------------------------------- -// Information about particular detail object types -//----------------------------------------------------------------------------- -enum -{ - MODELFLAG_UPRIGHT = 0x1, -}; - -struct DetailModel_t -{ - CUtlSymbol m_ModelName; - float m_Amount; - float m_MinCosAngle; - float m_MaxCosAngle; - int m_Flags; - int m_Orientation; - int m_Type; - Vector2D m_Pos[2]; - Vector2D m_Tex[2]; - float m_flRandomScaleStdDev; - unsigned char m_ShapeSize; - unsigned char m_ShapeAngle; - unsigned char m_SwayAmount; -}; - -struct DetailObjectGroup_t -{ - float m_Alpha; - CUtlVector< DetailModel_t > m_Models; -}; - -struct DetailObject_t -{ - CUtlSymbol m_Name; - float m_Density; - CUtlVector< DetailObjectGroup_t > m_Groups; - - bool operator==(const DetailObject_t& src ) const - { - return src.m_Name == m_Name; - } -}; - -static CUtlVector s_DetailObjectDict; - - -//----------------------------------------------------------------------------- -// Error checking.. make sure the model is valid + is a static prop -//----------------------------------------------------------------------------- -struct StaticPropLookup_t -{ - CUtlSymbol m_ModelName; - bool m_IsValid; -}; - -static bool StaticLess( StaticPropLookup_t const& src1, StaticPropLookup_t const& src2 ) -{ - return src1.m_ModelName < src2.m_ModelName; -} - -static CUtlRBTree< StaticPropLookup_t, unsigned short > s_StaticPropLookup( 0, 32, StaticLess ); - - -//----------------------------------------------------------------------------- -// These puppies are used to construct the game lumps -//----------------------------------------------------------------------------- -static CUtlVector s_DetailObjectDictLump; -static CUtlVector s_DetailObjectLump; -static CUtlVector s_DetailSpriteDictLump; - - -//----------------------------------------------------------------------------- -// Parses the key-value pairs in the detail.rad file -//----------------------------------------------------------------------------- -static void ParseDetailGroup( int detailId, KeyValues* pGroupKeyValues ) -{ - // Sort the group by alpha - float alpha = pGroupKeyValues->GetFloat( "alpha", 1.0f ); - - int i = s_DetailObjectDict[detailId].m_Groups.Count(); - while ( --i >= 0 ) - { - if (alpha > s_DetailObjectDict[detailId].m_Groups[i].m_Alpha) - break; - } - - // Insert after the first guy who's more transparent that we are! - i = s_DetailObjectDict[detailId].m_Groups.InsertAfter(i); - DetailObjectGroup_t& group = s_DetailObjectDict[detailId].m_Groups[i]; - - group.m_Alpha = alpha; - - // Add in all the model groups - KeyValues* pIter = pGroupKeyValues->GetFirstSubKey(); - float totalAmount = 0.0f; - while( pIter ) - { - if (pIter->GetFirstSubKey()) - { - int i = group.m_Models.AddToTail(); - - DetailModel_t &model = group.m_Models[i]; - - model.m_ModelName = pIter->GetString( "model", 0 ); - if (model.m_ModelName != UTL_INVAL_SYMBOL) - { - model.m_Type = DETAIL_PROP_TYPE_MODEL; - } - else - { - const char *pSpriteData = pIter->GetString( "sprite", 0 ); - if (pSpriteData) - { - const char *pProcModelType = pIter->GetString( "sprite_shape", 0 ); - - if ( pProcModelType ) - { - if ( !Q_stricmp( pProcModelType, "cross" ) ) - { - model.m_Type = DETAIL_PROP_TYPE_SHAPE_CROSS; - } - else if ( !Q_stricmp( pProcModelType, "tri" ) ) - { - model.m_Type = DETAIL_PROP_TYPE_SHAPE_TRI; - } - else - model.m_Type = DETAIL_PROP_TYPE_SPRITE; - } - else - { - // card sprite - model.m_Type = DETAIL_PROP_TYPE_SPRITE; - } - - model.m_Tex[0].Init(); - model.m_Tex[1].Init(); - - float x = 0, y = 0, flWidth = 64, flHeight = 64, flTextureSize = 512; - int nValid = sscanf( pSpriteData, "%f %f %f %f %f", &x, &y, &flWidth, &flHeight, &flTextureSize ); - if ( (nValid != 5) || (flTextureSize == 0) ) - { - Error( "Invalid arguments to \"sprite\" in detail.vbsp (model %s)!\n", model.m_ModelName.String() ); - } - - model.m_Tex[0].x = ( x + 0.5f ) / flTextureSize; - model.m_Tex[0].y = ( y + 0.5f ) / flTextureSize; - model.m_Tex[1].x = ( x + flWidth - 0.5f ) / flTextureSize; - model.m_Tex[1].y = ( y + flHeight - 0.5f ) / flTextureSize; - - model.m_Pos[0].Init( -10, 20 ); - model.m_Pos[1].Init( 10, 0 ); - - pSpriteData = pIter->GetString( "spritesize", 0 ); - if (pSpriteData) - { - sscanf( pSpriteData, "%f %f %f %f", &x, &y, &flWidth, &flHeight ); - - float ox = flWidth * x; - float oy = flHeight * y; - - model.m_Pos[0].x = -ox; - model.m_Pos[0].y = flHeight - oy; - model.m_Pos[1].x = flWidth - ox; - model.m_Pos[1].y = -oy; - } - - model.m_flRandomScaleStdDev = pIter->GetFloat( "spriterandomscale", 0.0f ); - - // sway is a percent of max sway, cl_detail_max_sway - float flSway = clamp( pIter->GetFloat( "sway", 0.0f ), 0.0, 1.0 ); - model.m_SwayAmount = (unsigned char)( 255.0 * flSway ); - - // shape angle - // for the tri shape, this is the angle each side is fanned out - model.m_ShapeAngle = pIter->GetInt( "shape_angle", 0 ); - - // shape size - // for the tri shape, this is the distance from the origin to the center of a side - float flShapeSize = clamp( pIter->GetFloat( "shape_size", 0.0f ), 0.0, 1.0 ); - model.m_ShapeSize = (unsigned char)( 255.0 * flShapeSize ); - } - } - - model.m_Amount = pIter->GetFloat( "amount", 1.0 ) + totalAmount; - totalAmount = model.m_Amount; - - model.m_Flags = 0; - if (pIter->GetInt( "upright", 0 )) - { - model.m_Flags |= MODELFLAG_UPRIGHT; - } - - // These are used to prevent emission on steep surfaces - float minAngle = pIter->GetFloat( "minAngle", 180 ); - float maxAngle = pIter->GetFloat( "maxAngle", 180 ); - model.m_MinCosAngle = cos(minAngle * M_PI / 180.f); - model.m_MaxCosAngle = cos(maxAngle * M_PI / 180.f); - model.m_Orientation = pIter->GetInt( "detailOrientation", 0 ); - - // Make sure minAngle < maxAngle - if ( model.m_MinCosAngle < model.m_MaxCosAngle) - { - model.m_MinCosAngle = model.m_MaxCosAngle; - } - } - pIter = pIter->GetNextKey(); - } - - // renormalize the amount if the total > 1 - if (totalAmount > 1.0f) - { - for (i = 0; i < group.m_Models.Count(); ++i) - { - group.m_Models[i].m_Amount /= totalAmount; - } - } -} - - -//----------------------------------------------------------------------------- -// Parses the key-value pairs in the detail.vbsp file -//----------------------------------------------------------------------------- -static void ParseDetailObjectFile( KeyValues& keyValues ) -{ - // Iterate over all detail object groups... - KeyValues* pIter; - for( pIter = keyValues.GetFirstSubKey(); pIter; pIter = pIter->GetNextKey() ) - { - if (!pIter->GetFirstSubKey()) - continue; - - int i = s_DetailObjectDict.AddToTail( ); - s_DetailObjectDict[i].m_Name = pIter->GetName() ; - s_DetailObjectDict[i].m_Density = pIter->GetFloat( "density", 0.0f ); - - // Iterate over all detail object groups... - KeyValues* pIterGroups = pIter->GetFirstSubKey(); - while( pIterGroups ) - { - if (pIterGroups->GetFirstSubKey()) - { - ParseDetailGroup( i, pIterGroups ); - } - pIterGroups = pIterGroups->GetNextKey(); - } - } -} - - -//----------------------------------------------------------------------------- -// Finds the name of the detail.vbsp file to use -//----------------------------------------------------------------------------- -static const char *FindDetailVBSPName( void ) -{ - for( int i = 0; i < num_entities; i++ ) - { - char* pEntity = ValueForKey( &entities[i], "classname" ); - if ( !strcmp( pEntity, "worldspawn" ) ) - { - const char *pDetailVBSP = ValueForKey( &entities[i], "detailvbsp" ); - if ( !pDetailVBSP || !pDetailVBSP[0] ) - { - pDetailVBSP = "detail.vbsp"; - } - return pDetailVBSP; - } - } - return "detail.vbsp"; -} - - -//----------------------------------------------------------------------------- -// Loads up the detail object dictionary -//----------------------------------------------------------------------------- -void LoadEmitDetailObjectDictionary( const char* pGameDir ) -{ - // Set the required global lights filename and try looking in qproject - const char *pDetailVBSP = FindDetailVBSPName(); - KeyValues * values = new KeyValues( pDetailVBSP ); - if ( values->LoadFromFile( g_pFileSystem, pDetailVBSP ) ) - { - ParseDetailObjectFile( *values ); - } - values->deleteThis(); -} - - -//----------------------------------------------------------------------------- -// Selects a detail group -//----------------------------------------------------------------------------- -static int SelectGroup( const DetailObject_t& detail, float alpha ) -{ - // Find the two groups whose alpha we're between... - int start, end; - for ( start = 0; start < detail.m_Groups.Count() - 1; ++start ) - { - if (alpha < detail.m_Groups[start+1].m_Alpha) - break; - } - - end = start + 1; - if (end >= detail.m_Groups.Count()) - --end; - - if (start == end) - return start; - - // Figure out how far we are between start and end... - float dist = 0.0f; - float dAlpha = (detail.m_Groups[end].m_Alpha - detail.m_Groups[start].m_Alpha); - if (dAlpha != 0.0f) - { - dist = (alpha - detail.m_Groups[start].m_Alpha) / dAlpha; - } - - // Pick a number, any number... - float r = rand() / (float)VALVE_RAND_MAX; - - // When dist == 0, we *always* want start. - // When dist == 1, we *always* want end - // That's why this logic looks a little reversed - return (r > dist) ? start : end; -} - - -//----------------------------------------------------------------------------- -// Selects a detail object -//----------------------------------------------------------------------------- -static int SelectDetail( DetailObjectGroup_t const& group ) -{ - // Pick a number, any number... - float r = rand() / (float)VALVE_RAND_MAX; - - // Look through the list of models + pick the one associated with this number - for ( int i = 0; i < group.m_Models.Count(); ++i ) - { - if (r <= group.m_Models[i].m_Amount) - return i; - } - - return -1; -} - - -//----------------------------------------------------------------------------- -// Adds a detail dictionary element (expected to oftentimes be shared) -//----------------------------------------------------------------------------- -static int AddDetailDictLump( const char* pModelName ) -{ - DetailObjectDictLump_t dictLump; - Q_strncpy( dictLump.m_Name, pModelName, DETAIL_NAME_LENGTH ); - - for (int i = s_DetailObjectDictLump.Count(); --i >= 0; ) - { - if (!memcmp(&s_DetailObjectDictLump[i], &dictLump, sizeof(dictLump) )) - return i; - } - - return s_DetailObjectDictLump.AddToTail( dictLump ); -} - -static int AddDetailSpriteDictLump( const Vector2D *pPos, const Vector2D *pTex ) -{ - DetailSpriteDictLump_t dictLump; - dictLump.m_UL = pPos[0]; - dictLump.m_LR = pPos[1]; - dictLump.m_TexUL = pTex[0]; - dictLump.m_TexLR = pTex[1]; - - for (int i = s_DetailSpriteDictLump.Count(); --i >= 0; ) - { - if (!memcmp(&s_DetailSpriteDictLump[i], &dictLump, sizeof(dictLump) )) - return i; - } - - return s_DetailSpriteDictLump.AddToTail( dictLump ); -} - - -//----------------------------------------------------------------------------- -// Computes the leaf that the detail lies in -//----------------------------------------------------------------------------- -static int ComputeDetailLeaf( const Vector& pt ) -{ - int node = 0; - while( node >= 0 ) - { - dnode_t* pNode = &dnodes[node]; - dplane_t* pPlane = &dplanes[pNode->planenum]; - - if (DotProduct(pt, pPlane->normal) < pPlane->dist) - node = pNode->children[1]; - else - node = pNode->children[0]; - } - - return - node - 1; -} - - -//----------------------------------------------------------------------------- -// Make sure the details are compiled with static prop -//----------------------------------------------------------------------------- -static bool IsModelValid( const char* pModelName ) -{ - StaticPropLookup_t lookup; - lookup.m_ModelName = pModelName; - - int i = s_StaticPropLookup.Find( lookup ); - if (i != s_StaticPropLookup.InvalidIndex() ) - return s_StaticPropLookup[i].m_IsValid; - - CUtlBuffer buf; - lookup.m_IsValid = LoadStudioModel( pModelName, "detail_prop", buf ); - if (!lookup.m_IsValid) - { - Warning("Error loading studio model \"%s\"!\n", pModelName ); - } - - s_StaticPropLookup.Insert( lookup ); - return lookup.m_IsValid; -} - - -//----------------------------------------------------------------------------- -// Add a detail to the lump. -//----------------------------------------------------------------------------- -static int s_nDetailOverflow = 0; -static void AddDetailToLump( const char* pModelName, const Vector& pt, const QAngle& angles, int nOrientation ) -{ - Assert( pt.IsValid() && angles.IsValid() ); - - // Make sure the model is valid... - if (!IsModelValid(pModelName)) - return; - - if (s_DetailObjectLump.Count() == 65535) - { - ++s_nDetailOverflow; - return; - } - - // Insert an element into the object dictionary if it aint there... - int i = s_DetailObjectLump.AddToTail( ); - - DetailObjectLump_t& objectLump = s_DetailObjectLump[i]; - objectLump.m_DetailModel = AddDetailDictLump( pModelName ); - VectorCopy( angles, objectLump.m_Angles ); - VectorCopy( pt, objectLump.m_Origin ); - objectLump.m_Leaf = ComputeDetailLeaf(pt); - objectLump.m_Lighting.r = 255; - objectLump.m_Lighting.g = 255; - objectLump.m_Lighting.b = 255; - objectLump.m_Lighting.exponent = 0; - objectLump.m_LightStyles = 0; - objectLump.m_LightStyleCount = 0; - objectLump.m_Orientation = nOrientation; - objectLump.m_Type = DETAIL_PROP_TYPE_MODEL; -} - - -//----------------------------------------------------------------------------- -// Add a detail sprite to the lump. -//----------------------------------------------------------------------------- -static void AddDetailSpriteToLump( const Vector &vecOrigin, const QAngle &vecAngles, int nOrientation, - const Vector2D *pPos, const Vector2D *pTex, float flScale, int iType, - int iShapeAngle = 0, int iShapeSize = 0, int iSwayAmount = 0 ) -{ - // Insert an element into the object dictionary if it aint there... - int i = s_DetailObjectLump.AddToTail( ); - - if (i >= 65535) - { - Error( "Error! Too many detail props emitted on this map! (64K max!)n" ); - } - - DetailObjectLump_t& objectLump = s_DetailObjectLump[i]; - objectLump.m_DetailModel = AddDetailSpriteDictLump( pPos, pTex ); - VectorCopy( vecAngles, objectLump.m_Angles ); - VectorCopy( vecOrigin, objectLump.m_Origin ); - objectLump.m_Leaf = ComputeDetailLeaf(vecOrigin); - objectLump.m_Lighting.r = 255; - objectLump.m_Lighting.g = 255; - objectLump.m_Lighting.b = 255; - objectLump.m_Lighting.exponent = 0; - objectLump.m_LightStyles = 0; - objectLump.m_LightStyleCount = 0; - objectLump.m_Orientation = nOrientation; - objectLump.m_Type = iType; - objectLump.m_flScale = flScale; - objectLump.m_ShapeAngle = iShapeAngle; - objectLump.m_ShapeSize = iShapeSize; - objectLump.m_SwayAmount = iSwayAmount; -} - -static void AddDetailSpriteToLump( const Vector &vecOrigin, const QAngle &vecAngles, DetailModel_t const& model, float flScale ) -{ - AddDetailSpriteToLump( vecOrigin, - vecAngles, - model.m_Orientation, - model.m_Pos, - model.m_Tex, - flScale, - model.m_Type, - model.m_ShapeAngle, - model.m_ShapeSize, - model.m_SwayAmount ); -} - -//----------------------------------------------------------------------------- -// Got a detail! Place it on the surface... -//----------------------------------------------------------------------------- -// BUGBUG: When the global optimizer is on, "normal" gets trashed in this function -// (only when not in the debugger?) -// Printing the values of normal at the bottom of the function fixes it as does -// disabling global optimizations. -static void PlaceDetail( DetailModel_t const& model, const Vector& pt, const Vector& normal ) -{ - // But only place it on the surface if it meets the angle constraints... - float cosAngle = normal.z; - - // Never emit if the angle's too steep - if (cosAngle < model.m_MaxCosAngle) - return; - - // If it's between min + max, flip a coin... - if (cosAngle < model.m_MinCosAngle) - { - float probability = (cosAngle - model.m_MaxCosAngle) / - (model.m_MinCosAngle - model.m_MaxCosAngle); - - float t = rand() / (float)VALVE_RAND_MAX; - if (t > probability) - return; - } - - // Compute the orientation of the detail - QAngle angles; - if (model.m_Flags & MODELFLAG_UPRIGHT) - { - // If it's upright, we just select a random yaw - angles.Init( 0, 360.0f * rand() / (float)VALVE_RAND_MAX, 0.0f ); - } - else - { - // It's not upright, so it must conform to the ground. Choose - // a random orientation based on the surface normal - - Vector zaxis; - VectorCopy( normal, zaxis ); - VectorNormalize( zaxis ); - - // Choose any two arbitrary axes which are perpendicular to the normal - Vector xaxis( 1, 0, 0 ); - if (fabs(xaxis.Dot(zaxis)) - 1.0 > -1e-3) - xaxis.Init( 0, 1, 0 ); - Vector yaxis; - CrossProduct( zaxis, xaxis, yaxis ); - VectorNormalize( yaxis ); - CrossProduct( yaxis, zaxis, xaxis ); - VectorNormalize( xaxis ); - VMatrix matrix; - matrix.SetBasisVectors( xaxis, yaxis, zaxis ); - matrix.SetTranslation( vec3_origin ); - - float rotAngle = 360.0f * rand() / (float)VALVE_RAND_MAX; - VMatrix rot = SetupMatrixAxisRot( Vector( 0, 0, 1 ), rotAngle ); - matrix = matrix * rot; - - MatrixToAngles( matrix, angles ); - } - - // FIXME: We may also want a purely random rotation too - - // Insert an element into the object dictionary if it aint there... - switch ( model.m_Type ) - { - case DETAIL_PROP_TYPE_MODEL: - AddDetailToLump( model.m_ModelName.String(), pt, angles, model.m_Orientation ); - break; - - // Sprites and procedural models made from sprites - case DETAIL_PROP_TYPE_SPRITE: - default: - { - float flScale = 1.0f; - if ( model.m_flRandomScaleStdDev != 0.0f ) - { - flScale = fabs( RandomGaussianFloat( 1.0f, model.m_flRandomScaleStdDev ) ); - } - - AddDetailSpriteToLump( pt, angles, model, flScale ); - } - break; - } -} - - -//----------------------------------------------------------------------------- -// Places Detail Objects on a face -//----------------------------------------------------------------------------- -static void EmitDetailObjectsOnFace( dface_t* pFace, DetailObject_t& detail ) -{ - if (pFace->numedges < 3) - return; - - // We're going to pick a bunch of random points, and then probabilistically - // decide whether or not to plant a detail object there. - - // Turn the face into a bunch of polygons, and compute the area of each - int* pSurfEdges = &dsurfedges[pFace->firstedge]; - int vertexIdx = (pSurfEdges[0] < 0); - int firstVertexIndex = dedges[abs(pSurfEdges[0])].v[vertexIdx]; - dvertex_t* pFirstVertex = &dvertexes[firstVertexIndex]; - for (int i = 1; i < pFace->numedges - 1; ++i ) - { - int vertexIdx = (pSurfEdges[i] < 0); - dedge_t* pEdge = &dedges[abs(pSurfEdges[i])]; - - // Compute two triangle edges - Vector e1, e2; - VectorSubtract( dvertexes[pEdge->v[vertexIdx]].point, pFirstVertex->point, e1 ); - VectorSubtract( dvertexes[pEdge->v[1 - vertexIdx]].point, pFirstVertex->point, e2 ); - - // Compute the triangle area - Vector areaVec; - CrossProduct( e1, e2, areaVec ); - float normalLength = areaVec.Length(); - float area = 0.5f * normalLength; - - // Compute the number of samples to take - int numSamples = area * detail.m_Density * 0.000001; - - // Now take a sample, and randomly place an object there - for (int i = 0; i < numSamples; ++i ) - { - // Create a random sample... - float u = rand() / (float)VALVE_RAND_MAX; - float v = rand() / (float)VALVE_RAND_MAX; - if (v > 1.0f - u) - { - u = 1.0f - u; - v = 1.0f - v; - assert( u + v <= 1.0f ); - } - - // Compute alpha - float alpha = 1.0f; - - // Select a group based on the alpha value - int group = SelectGroup( detail, alpha ); - - // Now that we've got a group, choose a detail - int model = SelectDetail( detail.m_Groups[group] ); - if (model < 0) - continue; - - // Got a detail! Place it on the surface... - Vector pt, normal; - VectorMA( pFirstVertex->point, u, e1, pt ); - VectorMA( pt, v, e2, pt ); - VectorDivide( areaVec, -normalLength, normal ); - - PlaceDetail( detail.m_Groups[group].m_Models[model], pt, normal ); - } - } -} - - -//----------------------------------------------------------------------------- -// Places Detail Objects on a face -//----------------------------------------------------------------------------- -static float ComputeDisplacementFaceArea( dface_t* pFace ) -{ - float area = 0.0f; - - // Compute the area of the base face - int* pSurfEdges = &dsurfedges[pFace->firstedge]; - int vertexIdx = (pSurfEdges[0] < 0); - int firstVertexIndex = dedges[abs(pSurfEdges[0])].v[vertexIdx]; - dvertex_t* pFirstVertex = &dvertexes[firstVertexIndex]; - for (int i = 1; i <= 2; ++i ) - { - int vertexIdx = (pSurfEdges[i] < 0); - dedge_t* pEdge = &dedges[abs(pSurfEdges[i])]; - - // Compute two triangle edges - Vector e1, e2; - VectorSubtract( dvertexes[pEdge->v[vertexIdx]].point, pFirstVertex->point, e1 ); - VectorSubtract( dvertexes[pEdge->v[1 - vertexIdx]].point, pFirstVertex->point, e2 ); - - // Compute the triangle area - Vector areaVec; - CrossProduct( e1, e2, areaVec ); - float normalLength = areaVec.Length(); - area += 0.5f * normalLength; - } - - return area; -} - - -//----------------------------------------------------------------------------- -// Places Detail Objects on a face -//----------------------------------------------------------------------------- -static void EmitDetailObjectsOnDisplacementFace( dface_t* pFace, - DetailObject_t& detail, CCoreDispInfo& coreDispInfo ) -{ - assert(pFace->numedges == 4); - - // We're going to pick a bunch of random points, and then probabilistically - // decide whether or not to plant a detail object there. - - // Compute the area of the base face - float area = ComputeDisplacementFaceArea( pFace ); - - // Compute the number of samples to take - int numSamples = area * detail.m_Density * 0.000001; - - // Now take a sample, and randomly place an object there - for (int i = 0; i < numSamples; ++i ) - { - // Create a random sample... - float u = rand() / (float)VALVE_RAND_MAX; - float v = rand() / (float)VALVE_RAND_MAX; - - // Compute alpha - float alpha; - Vector pt, normal; - coreDispInfo.GetPositionOnSurface( u, v, pt, &normal, &alpha ); - alpha /= 255.0f; - - // Select a group based on the alpha value - int group = SelectGroup( detail, alpha ); - - // Now that we've got a group, choose a detail - int model = SelectDetail( detail.m_Groups[group] ); - if (model < 0) - continue; - - // Got a detail! Place it on the surface... - PlaceDetail( detail.m_Groups[group].m_Models[model], pt, normal ); - } -} - - -//----------------------------------------------------------------------------- -// Sort detail objects by leaf -//----------------------------------------------------------------------------- -static int SortFunc( const void *arg1, const void *arg2 ) -{ - int nDelta = ((DetailObjectLump_t*)arg1)->m_Leaf - ((DetailObjectLump_t*)arg2)->m_Leaf; - if ( nDelta < 0 ) - return -1; - if ( nDelta > 0 ) - return 1; - return 0; -} - - -//----------------------------------------------------------------------------- -// Places Detail Objects in the lump -//----------------------------------------------------------------------------- -static void SetLumpData( ) -{ - // Sort detail props by leaf - qsort( s_DetailObjectLump.Base(), s_DetailObjectLump.Count(), sizeof(DetailObjectLump_t), SortFunc ); - - GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(GAMELUMP_DETAIL_PROPS); - if (handle != g_GameLumps.InvalidGameLump()) - { - g_GameLumps.DestroyGameLump(handle); - } - int nDictSize = s_DetailObjectDictLump.Count() * sizeof(DetailObjectDictLump_t); - int nSpriteDictSize = s_DetailSpriteDictLump.Count() * sizeof(DetailSpriteDictLump_t); - int nObjSize = s_DetailObjectLump.Count() * sizeof(DetailObjectLump_t); - int nSize = nDictSize + nSpriteDictSize + nObjSize + (3 * sizeof(int)); - - handle = g_GameLumps.CreateGameLump( GAMELUMP_DETAIL_PROPS, nSize, 0, GAMELUMP_DETAIL_PROPS_VERSION ); - - // Serialize the data - CUtlBuffer buf( g_GameLumps.GetGameLump(handle), nSize ); - buf.PutInt( s_DetailObjectDictLump.Count() ); - if (nDictSize) - { - buf.Put( s_DetailObjectDictLump.Base(), nDictSize ); - } - buf.PutInt( s_DetailSpriteDictLump.Count() ); - if (nSpriteDictSize) - { - buf.Put( s_DetailSpriteDictLump.Base(), nSpriteDictSize ); - } - buf.PutInt( s_DetailObjectLump.Count() ); - if (nObjSize) - { - buf.Put( s_DetailObjectLump.Base(), nObjSize ); - } -} - - -//----------------------------------------------------------------------------- -// Places Detail Objects in the level -//----------------------------------------------------------------------------- -void EmitDetailModels() -{ - StartPacifier("Placing detail props : "); - - // Place stuff on each face - dface_t* pFace = dfaces; - for (int j = 0; j < numfaces; ++j) - { - UpdatePacifier( (float)j / (float)numfaces ); - - // Get at the material associated with this face - texinfo_t* pTexInfo = &texinfo[pFace[j].texinfo]; - dtexdata_t* pTexData = GetTexData( pTexInfo->texdata ); - - // Try to get at the material - bool found; - MaterialSystemMaterial_t handle = - FindOriginalMaterial( TexDataStringTable_GetString( pTexData->nameStringTableID ), - &found, false ); - if (!found) - continue; - - // See if its got any detail objects on it - const char* pDetailType = GetMaterialVar( handle, "%detailtype" ); - if (!pDetailType) - continue; - - // Get the detail type... - DetailObject_t search; - search.m_Name = pDetailType; - int objectType = s_DetailObjectDict.Find(search); - if (objectType < 0) - { - Warning("Material %s uses unknown detail object type %s!\n", - TexDataStringTable_GetString( pTexData->nameStringTableID ), - pDetailType); - continue; - } - - // Emit objects on a particular face - DetailObject_t& detail = s_DetailObjectDict[objectType]; - - // Initialize the Random Number generators for detail prop placement based on the hammer Face num. - int detailpropseed = dfaceids[j].hammerfaceid; -#ifdef WARNSEEDNUMBER - Warning( "[%d]\n",detailpropseed ); -#endif - srand( detailpropseed ); - RandomSeed( detailpropseed ); - - if (pFace[j].dispinfo < 0) - { - EmitDetailObjectsOnFace( &pFace[j], detail ); - } - else - { - // Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates. - mapdispinfo_t *pMapDisp = &mapdispinfo[pFace[j].dispinfo]; - CCoreDispInfo coreDispInfo; - DispMapToCoreDispInfo( pMapDisp, &coreDispInfo, NULL, NULL ); - - EmitDetailObjectsOnDisplacementFace( &pFace[j], detail, coreDispInfo ); - } - } - - // Emit specifically specified detail props - Vector origin; - QAngle angles; - Vector2D pos[2]; - Vector2D tex[2]; - for (int i = 0; i < num_entities; ++i) - { - char* pEntity = ValueForKey(&entities[i], "classname"); - if (!strcmp(pEntity, "detail_prop") || !strcmp(pEntity, "prop_detail")) - { - GetVectorForKey( &entities[i], "origin", origin ); - GetAnglesForKey( &entities[i], "angles", angles ); - char* pModelName = ValueForKey( &entities[i], "model" ); - int nOrientation = IntForKey( &entities[i], "detailOrientation" ); - - AddDetailToLump( pModelName, origin, angles, nOrientation ); - - // strip this ent from the .bsp file - entities[i].epairs = 0; - continue; - } - - if (!strcmp(pEntity, "prop_detail_sprite")) - { - GetVectorForKey( &entities[i], "origin", origin ); - GetAnglesForKey( &entities[i], "angles", angles ); - int nOrientation = IntForKey( &entities[i], "detailOrientation" ); - GetVector2DForKey( &entities[i], "position_ul", pos[0] ); - GetVector2DForKey( &entities[i], "position_lr", pos[1] ); - GetVector2DForKey( &entities[i], "tex_ul", tex[0] ); - GetVector2DForKey( &entities[i], "tex_size", tex[1] ); - float flTextureSize = FloatForKey( &entities[i], "tex_total_size" ); - - tex[1].x += tex[0].x - 0.5f; - tex[1].y += tex[0].y - 0.5f; - tex[0].x += 0.5f; - tex[0].y += 0.5f; - tex[0] /= flTextureSize; - tex[1] /= flTextureSize; - - AddDetailSpriteToLump( origin, angles, nOrientation, pos, tex, 1.0f, DETAIL_PROP_TYPE_SPRITE ); - - // strip this ent from the .bsp file - entities[i].epairs = 0; - continue; - } - } - - EndPacifier( true ); -} - - -//----------------------------------------------------------------------------- -// Places Detail Objects in the level -//----------------------------------------------------------------------------- -void EmitDetailObjects() -{ - EmitDetailModels(); - - // Done! Now lets add the lumps (destroy previous ones) - SetLumpData( ); - - if ( s_nDetailOverflow != 0 ) - { - Warning( "Error! Too many detail props on this map. %d were not emitted!\n", s_nDetailOverflow ); - } -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Places "detail" objects which are client-only renderable things +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#include +#include "vbsp.h" +#include "bsplib.h" +#include "KeyValues.h" +#include "utlsymbol.h" +#include "utlvector.h" +#include +#include "bspfile.h" +#include "utilmatlib.h" +#include "gamebspfile.h" +#include "mathlib/VMatrix.h" +#include "materialpatch.h" +#include "pacifier.h" +#include "vstdlib/random.h" +#include "builddisp.h" +#include "disp_vbsp.h" +#include "UtlBuffer.h" +#include "CollisionUtils.h" +#include +#include "UtlLinkedList.h" +#include "byteswap.h" +#include "writebsp.h" + +//----------------------------------------------------------------------------- +// Information about particular detail object types +//----------------------------------------------------------------------------- +enum +{ + MODELFLAG_UPRIGHT = 0x1, +}; + +struct DetailModel_t +{ + CUtlSymbol m_ModelName; + float m_Amount; + float m_MinCosAngle; + float m_MaxCosAngle; + int m_Flags; + int m_Orientation; + int m_Type; + Vector2D m_Pos[2]; + Vector2D m_Tex[2]; + float m_flRandomScaleStdDev; + unsigned char m_ShapeSize; + unsigned char m_ShapeAngle; + unsigned char m_SwayAmount; +}; + +struct DetailObjectGroup_t +{ + float m_Alpha; + CUtlVector< DetailModel_t > m_Models; +}; + +struct DetailObject_t +{ + CUtlSymbol m_Name; + float m_Density; + CUtlVector< DetailObjectGroup_t > m_Groups; + + bool operator==(const DetailObject_t& src ) const + { + return src.m_Name == m_Name; + } +}; + +static CUtlVector s_DetailObjectDict; + + +//----------------------------------------------------------------------------- +// Error checking.. make sure the model is valid + is a static prop +//----------------------------------------------------------------------------- +struct StaticPropLookup_t +{ + CUtlSymbol m_ModelName; + bool m_IsValid; +}; + +static bool StaticLess( StaticPropLookup_t const& src1, StaticPropLookup_t const& src2 ) +{ + return src1.m_ModelName < src2.m_ModelName; +} + +static CUtlRBTree< StaticPropLookup_t, unsigned short > s_StaticPropLookup( 0, 32, StaticLess ); + + +//----------------------------------------------------------------------------- +// These puppies are used to construct the game lumps +//----------------------------------------------------------------------------- +static CUtlVector s_DetailObjectDictLump; +static CUtlVector s_DetailObjectLump; +static CUtlVector s_DetailSpriteDictLump; + + +//----------------------------------------------------------------------------- +// Parses the key-value pairs in the detail.rad file +//----------------------------------------------------------------------------- +static void ParseDetailGroup( int detailId, KeyValues* pGroupKeyValues ) +{ + // Sort the group by alpha + float alpha = pGroupKeyValues->GetFloat( "alpha", 1.0f ); + + int i = s_DetailObjectDict[detailId].m_Groups.Count(); + while ( --i >= 0 ) + { + if (alpha > s_DetailObjectDict[detailId].m_Groups[i].m_Alpha) + break; + } + + // Insert after the first guy who's more transparent that we are! + i = s_DetailObjectDict[detailId].m_Groups.InsertAfter(i); + DetailObjectGroup_t& group = s_DetailObjectDict[detailId].m_Groups[i]; + + group.m_Alpha = alpha; + + // Add in all the model groups + KeyValues* pIter = pGroupKeyValues->GetFirstSubKey(); + float totalAmount = 0.0f; + while( pIter ) + { + if (pIter->GetFirstSubKey()) + { + int i = group.m_Models.AddToTail(); + + DetailModel_t &model = group.m_Models[i]; + + model.m_ModelName = pIter->GetString( "model", 0 ); + if (model.m_ModelName != UTL_INVAL_SYMBOL) + { + model.m_Type = DETAIL_PROP_TYPE_MODEL; + } + else + { + const char *pSpriteData = pIter->GetString( "sprite", 0 ); + if (pSpriteData) + { + const char *pProcModelType = pIter->GetString( "sprite_shape", 0 ); + + if ( pProcModelType ) + { + if ( !Q_stricmp( pProcModelType, "cross" ) ) + { + model.m_Type = DETAIL_PROP_TYPE_SHAPE_CROSS; + } + else if ( !Q_stricmp( pProcModelType, "tri" ) ) + { + model.m_Type = DETAIL_PROP_TYPE_SHAPE_TRI; + } + else + model.m_Type = DETAIL_PROP_TYPE_SPRITE; + } + else + { + // card sprite + model.m_Type = DETAIL_PROP_TYPE_SPRITE; + } + + model.m_Tex[0].Init(); + model.m_Tex[1].Init(); + + float x = 0, y = 0, flWidth = 64, flHeight = 64, flTextureSize = 512; + int nValid = sscanf( pSpriteData, "%f %f %f %f %f", &x, &y, &flWidth, &flHeight, &flTextureSize ); + if ( (nValid != 5) || (flTextureSize == 0) ) + { + Error( "Invalid arguments to \"sprite\" in detail.vbsp (model %s)!\n", model.m_ModelName.String() ); + } + + model.m_Tex[0].x = ( x + 0.5f ) / flTextureSize; + model.m_Tex[0].y = ( y + 0.5f ) / flTextureSize; + model.m_Tex[1].x = ( x + flWidth - 0.5f ) / flTextureSize; + model.m_Tex[1].y = ( y + flHeight - 0.5f ) / flTextureSize; + + model.m_Pos[0].Init( -10, 20 ); + model.m_Pos[1].Init( 10, 0 ); + + pSpriteData = pIter->GetString( "spritesize", 0 ); + if (pSpriteData) + { + sscanf( pSpriteData, "%f %f %f %f", &x, &y, &flWidth, &flHeight ); + + float ox = flWidth * x; + float oy = flHeight * y; + + model.m_Pos[0].x = -ox; + model.m_Pos[0].y = flHeight - oy; + model.m_Pos[1].x = flWidth - ox; + model.m_Pos[1].y = -oy; + } + + model.m_flRandomScaleStdDev = pIter->GetFloat( "spriterandomscale", 0.0f ); + + // sway is a percent of max sway, cl_detail_max_sway + float flSway = clamp( pIter->GetFloat( "sway", 0.0f ), 0.0, 1.0 ); + model.m_SwayAmount = (unsigned char)( 255.0 * flSway ); + + // shape angle + // for the tri shape, this is the angle each side is fanned out + model.m_ShapeAngle = pIter->GetInt( "shape_angle", 0 ); + + // shape size + // for the tri shape, this is the distance from the origin to the center of a side + float flShapeSize = clamp( pIter->GetFloat( "shape_size", 0.0f ), 0.0, 1.0 ); + model.m_ShapeSize = (unsigned char)( 255.0 * flShapeSize ); + } + } + + model.m_Amount = pIter->GetFloat( "amount", 1.0 ) + totalAmount; + totalAmount = model.m_Amount; + + model.m_Flags = 0; + if (pIter->GetInt( "upright", 0 )) + { + model.m_Flags |= MODELFLAG_UPRIGHT; + } + + // These are used to prevent emission on steep surfaces + float minAngle = pIter->GetFloat( "minAngle", 180 ); + float maxAngle = pIter->GetFloat( "maxAngle", 180 ); + model.m_MinCosAngle = cos(minAngle * M_PI / 180.f); + model.m_MaxCosAngle = cos(maxAngle * M_PI / 180.f); + model.m_Orientation = pIter->GetInt( "detailOrientation", 0 ); + + // Make sure minAngle < maxAngle + if ( model.m_MinCosAngle < model.m_MaxCosAngle) + { + model.m_MinCosAngle = model.m_MaxCosAngle; + } + } + pIter = pIter->GetNextKey(); + } + + // renormalize the amount if the total > 1 + if (totalAmount > 1.0f) + { + for (i = 0; i < group.m_Models.Count(); ++i) + { + group.m_Models[i].m_Amount /= totalAmount; + } + } +} + + +//----------------------------------------------------------------------------- +// Parses the key-value pairs in the detail.vbsp file +//----------------------------------------------------------------------------- +static void ParseDetailObjectFile( KeyValues& keyValues ) +{ + // Iterate over all detail object groups... + KeyValues* pIter; + for( pIter = keyValues.GetFirstSubKey(); pIter; pIter = pIter->GetNextKey() ) + { + if (!pIter->GetFirstSubKey()) + continue; + + int i = s_DetailObjectDict.AddToTail( ); + s_DetailObjectDict[i].m_Name = pIter->GetName() ; + s_DetailObjectDict[i].m_Density = pIter->GetFloat( "density", 0.0f ); + + // Iterate over all detail object groups... + KeyValues* pIterGroups = pIter->GetFirstSubKey(); + while( pIterGroups ) + { + if (pIterGroups->GetFirstSubKey()) + { + ParseDetailGroup( i, pIterGroups ); + } + pIterGroups = pIterGroups->GetNextKey(); + } + } +} + + +//----------------------------------------------------------------------------- +// Finds the name of the detail.vbsp file to use +//----------------------------------------------------------------------------- +static const char *FindDetailVBSPName( void ) +{ + for( int i = 0; i < num_entities; i++ ) + { + char* pEntity = ValueForKey( &entities[i], "classname" ); + if ( !strcmp( pEntity, "worldspawn" ) ) + { + const char *pDetailVBSP = ValueForKey( &entities[i], "detailvbsp" ); + if ( !pDetailVBSP || !pDetailVBSP[0] ) + { + pDetailVBSP = "detail.vbsp"; + } + return pDetailVBSP; + } + } + return "detail.vbsp"; +} + + +//----------------------------------------------------------------------------- +// Loads up the detail object dictionary +//----------------------------------------------------------------------------- +void LoadEmitDetailObjectDictionary( const char* pGameDir ) +{ + // Set the required global lights filename and try looking in qproject + const char *pDetailVBSP = FindDetailVBSPName(); + KeyValues * values = new KeyValues( pDetailVBSP ); + if ( values->LoadFromFile( g_pFileSystem, pDetailVBSP ) ) + { + ParseDetailObjectFile( *values ); + } + values->deleteThis(); +} + + +//----------------------------------------------------------------------------- +// Selects a detail group +//----------------------------------------------------------------------------- +static int SelectGroup( const DetailObject_t& detail, float alpha ) +{ + // Find the two groups whose alpha we're between... + int start, end; + for ( start = 0; start < detail.m_Groups.Count() - 1; ++start ) + { + if (alpha < detail.m_Groups[start+1].m_Alpha) + break; + } + + end = start + 1; + if (end >= detail.m_Groups.Count()) + --end; + + if (start == end) + return start; + + // Figure out how far we are between start and end... + float dist = 0.0f; + float dAlpha = (detail.m_Groups[end].m_Alpha - detail.m_Groups[start].m_Alpha); + if (dAlpha != 0.0f) + { + dist = (alpha - detail.m_Groups[start].m_Alpha) / dAlpha; + } + + // Pick a number, any number... + float r = rand() / (float)VALVE_RAND_MAX; + + // When dist == 0, we *always* want start. + // When dist == 1, we *always* want end + // That's why this logic looks a little reversed + return (r > dist) ? start : end; +} + + +//----------------------------------------------------------------------------- +// Selects a detail object +//----------------------------------------------------------------------------- +static int SelectDetail( DetailObjectGroup_t const& group ) +{ + // Pick a number, any number... + float r = rand() / (float)VALVE_RAND_MAX; + + // Look through the list of models + pick the one associated with this number + for ( int i = 0; i < group.m_Models.Count(); ++i ) + { + if (r <= group.m_Models[i].m_Amount) + return i; + } + + return -1; +} + + +//----------------------------------------------------------------------------- +// Adds a detail dictionary element (expected to oftentimes be shared) +//----------------------------------------------------------------------------- +static int AddDetailDictLump( const char* pModelName ) +{ + DetailObjectDictLump_t dictLump; + Q_strncpy( dictLump.m_Name, pModelName, DETAIL_NAME_LENGTH ); + + for (int i = s_DetailObjectDictLump.Count(); --i >= 0; ) + { + if (!memcmp(&s_DetailObjectDictLump[i], &dictLump, sizeof(dictLump) )) + return i; + } + + return s_DetailObjectDictLump.AddToTail( dictLump ); +} + +static int AddDetailSpriteDictLump( const Vector2D *pPos, const Vector2D *pTex ) +{ + DetailSpriteDictLump_t dictLump; + dictLump.m_UL = pPos[0]; + dictLump.m_LR = pPos[1]; + dictLump.m_TexUL = pTex[0]; + dictLump.m_TexLR = pTex[1]; + + for (int i = s_DetailSpriteDictLump.Count(); --i >= 0; ) + { + if (!memcmp(&s_DetailSpriteDictLump[i], &dictLump, sizeof(dictLump) )) + return i; + } + + return s_DetailSpriteDictLump.AddToTail( dictLump ); +} + + +//----------------------------------------------------------------------------- +// Computes the leaf that the detail lies in +//----------------------------------------------------------------------------- +static int ComputeDetailLeaf( const Vector& pt ) +{ + int node = 0; + while( node >= 0 ) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + if (DotProduct(pt, pPlane->normal) < pPlane->dist) + node = pNode->children[1]; + else + node = pNode->children[0]; + } + + return - node - 1; +} + + +//----------------------------------------------------------------------------- +// Make sure the details are compiled with static prop +//----------------------------------------------------------------------------- +static bool IsModelValid( const char* pModelName ) +{ + StaticPropLookup_t lookup; + lookup.m_ModelName = pModelName; + + int i = s_StaticPropLookup.Find( lookup ); + if (i != s_StaticPropLookup.InvalidIndex() ) + return s_StaticPropLookup[i].m_IsValid; + + CUtlBuffer buf; + lookup.m_IsValid = LoadStudioModel( pModelName, "detail_prop", buf ); + if (!lookup.m_IsValid) + { + Warning("Error loading studio model \"%s\"!\n", pModelName ); + } + + s_StaticPropLookup.Insert( lookup ); + return lookup.m_IsValid; +} + + +//----------------------------------------------------------------------------- +// Add a detail to the lump. +//----------------------------------------------------------------------------- +static int s_nDetailOverflow = 0; +static void AddDetailToLump( const char* pModelName, const Vector& pt, const QAngle& angles, int nOrientation ) +{ + Assert( pt.IsValid() && angles.IsValid() ); + + // Make sure the model is valid... + if (!IsModelValid(pModelName)) + return; + + if (s_DetailObjectLump.Count() == 65535) + { + ++s_nDetailOverflow; + return; + } + + // Insert an element into the object dictionary if it aint there... + int i = s_DetailObjectLump.AddToTail( ); + + DetailObjectLump_t& objectLump = s_DetailObjectLump[i]; + objectLump.m_DetailModel = AddDetailDictLump( pModelName ); + VectorCopy( angles, objectLump.m_Angles ); + VectorCopy( pt, objectLump.m_Origin ); + objectLump.m_Leaf = ComputeDetailLeaf(pt); + objectLump.m_Lighting.r = 255; + objectLump.m_Lighting.g = 255; + objectLump.m_Lighting.b = 255; + objectLump.m_Lighting.exponent = 0; + objectLump.m_LightStyles = 0; + objectLump.m_LightStyleCount = 0; + objectLump.m_Orientation = nOrientation; + objectLump.m_Type = DETAIL_PROP_TYPE_MODEL; +} + + +//----------------------------------------------------------------------------- +// Add a detail sprite to the lump. +//----------------------------------------------------------------------------- +static void AddDetailSpriteToLump( const Vector &vecOrigin, const QAngle &vecAngles, int nOrientation, + const Vector2D *pPos, const Vector2D *pTex, float flScale, int iType, + int iShapeAngle = 0, int iShapeSize = 0, int iSwayAmount = 0 ) +{ + // Insert an element into the object dictionary if it aint there... + int i = s_DetailObjectLump.AddToTail( ); + + if (i >= 65535) + { + Error( "Error! Too many detail props emitted on this map! (64K max!)n" ); + } + + DetailObjectLump_t& objectLump = s_DetailObjectLump[i]; + objectLump.m_DetailModel = AddDetailSpriteDictLump( pPos, pTex ); + VectorCopy( vecAngles, objectLump.m_Angles ); + VectorCopy( vecOrigin, objectLump.m_Origin ); + objectLump.m_Leaf = ComputeDetailLeaf(vecOrigin); + objectLump.m_Lighting.r = 255; + objectLump.m_Lighting.g = 255; + objectLump.m_Lighting.b = 255; + objectLump.m_Lighting.exponent = 0; + objectLump.m_LightStyles = 0; + objectLump.m_LightStyleCount = 0; + objectLump.m_Orientation = nOrientation; + objectLump.m_Type = iType; + objectLump.m_flScale = flScale; + objectLump.m_ShapeAngle = iShapeAngle; + objectLump.m_ShapeSize = iShapeSize; + objectLump.m_SwayAmount = iSwayAmount; +} + +static void AddDetailSpriteToLump( const Vector &vecOrigin, const QAngle &vecAngles, DetailModel_t const& model, float flScale ) +{ + AddDetailSpriteToLump( vecOrigin, + vecAngles, + model.m_Orientation, + model.m_Pos, + model.m_Tex, + flScale, + model.m_Type, + model.m_ShapeAngle, + model.m_ShapeSize, + model.m_SwayAmount ); +} + +//----------------------------------------------------------------------------- +// Got a detail! Place it on the surface... +//----------------------------------------------------------------------------- +// BUGBUG: When the global optimizer is on, "normal" gets trashed in this function +// (only when not in the debugger?) +// Printing the values of normal at the bottom of the function fixes it as does +// disabling global optimizations. +static void PlaceDetail( DetailModel_t const& model, const Vector& pt, const Vector& normal ) +{ + // But only place it on the surface if it meets the angle constraints... + float cosAngle = normal.z; + + // Never emit if the angle's too steep + if (cosAngle < model.m_MaxCosAngle) + return; + + // If it's between min + max, flip a coin... + if (cosAngle < model.m_MinCosAngle) + { + float probability = (cosAngle - model.m_MaxCosAngle) / + (model.m_MinCosAngle - model.m_MaxCosAngle); + + float t = rand() / (float)VALVE_RAND_MAX; + if (t > probability) + return; + } + + // Compute the orientation of the detail + QAngle angles; + if (model.m_Flags & MODELFLAG_UPRIGHT) + { + // If it's upright, we just select a random yaw + angles.Init( 0, 360.0f * rand() / (float)VALVE_RAND_MAX, 0.0f ); + } + else + { + // It's not upright, so it must conform to the ground. Choose + // a random orientation based on the surface normal + + Vector zaxis; + VectorCopy( normal, zaxis ); + VectorNormalize( zaxis ); + + // Choose any two arbitrary axes which are perpendicular to the normal + Vector xaxis( 1, 0, 0 ); + if (fabs(xaxis.Dot(zaxis)) - 1.0 > -1e-3) + xaxis.Init( 0, 1, 0 ); + Vector yaxis; + CrossProduct( zaxis, xaxis, yaxis ); + VectorNormalize( yaxis ); + CrossProduct( yaxis, zaxis, xaxis ); + VectorNormalize( xaxis ); + VMatrix matrix; + matrix.SetBasisVectors( xaxis, yaxis, zaxis ); + matrix.SetTranslation( vec3_origin ); + + float rotAngle = 360.0f * rand() / (float)VALVE_RAND_MAX; + VMatrix rot = SetupMatrixAxisRot( Vector( 0, 0, 1 ), rotAngle ); + matrix = matrix * rot; + + MatrixToAngles( matrix, angles ); + } + + // FIXME: We may also want a purely random rotation too + + // Insert an element into the object dictionary if it aint there... + switch ( model.m_Type ) + { + case DETAIL_PROP_TYPE_MODEL: + AddDetailToLump( model.m_ModelName.String(), pt, angles, model.m_Orientation ); + break; + + // Sprites and procedural models made from sprites + case DETAIL_PROP_TYPE_SPRITE: + default: + { + float flScale = 1.0f; + if ( model.m_flRandomScaleStdDev != 0.0f ) + { + flScale = fabs( RandomGaussianFloat( 1.0f, model.m_flRandomScaleStdDev ) ); + } + + AddDetailSpriteToLump( pt, angles, model, flScale ); + } + break; + } +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects on a face +//----------------------------------------------------------------------------- +static void EmitDetailObjectsOnFace( dface_t* pFace, DetailObject_t& detail ) +{ + if (pFace->numedges < 3) + return; + + // We're going to pick a bunch of random points, and then probabilistically + // decide whether or not to plant a detail object there. + + // Turn the face into a bunch of polygons, and compute the area of each + int* pSurfEdges = &dsurfedges[pFace->firstedge]; + int vertexIdx = (pSurfEdges[0] < 0); + int firstVertexIndex = dedges[abs(pSurfEdges[0])].v[vertexIdx]; + dvertex_t* pFirstVertex = &dvertexes[firstVertexIndex]; + for (int i = 1; i < pFace->numedges - 1; ++i ) + { + int vertexIdx = (pSurfEdges[i] < 0); + dedge_t* pEdge = &dedges[abs(pSurfEdges[i])]; + + // Compute two triangle edges + Vector e1, e2; + VectorSubtract( dvertexes[pEdge->v[vertexIdx]].point, pFirstVertex->point, e1 ); + VectorSubtract( dvertexes[pEdge->v[1 - vertexIdx]].point, pFirstVertex->point, e2 ); + + // Compute the triangle area + Vector areaVec; + CrossProduct( e1, e2, areaVec ); + float normalLength = areaVec.Length(); + float area = 0.5f * normalLength; + + // Compute the number of samples to take + int numSamples = area * detail.m_Density * 0.000001; + + // Now take a sample, and randomly place an object there + for (int i = 0; i < numSamples; ++i ) + { + // Create a random sample... + float u = rand() / (float)VALVE_RAND_MAX; + float v = rand() / (float)VALVE_RAND_MAX; + if (v > 1.0f - u) + { + u = 1.0f - u; + v = 1.0f - v; + assert( u + v <= 1.0f ); + } + + // Compute alpha + float alpha = 1.0f; + + // Select a group based on the alpha value + int group = SelectGroup( detail, alpha ); + + // Now that we've got a group, choose a detail + int model = SelectDetail( detail.m_Groups[group] ); + if (model < 0) + continue; + + // Got a detail! Place it on the surface... + Vector pt, normal; + VectorMA( pFirstVertex->point, u, e1, pt ); + VectorMA( pt, v, e2, pt ); + VectorDivide( areaVec, -normalLength, normal ); + + PlaceDetail( detail.m_Groups[group].m_Models[model], pt, normal ); + } + } +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects on a face +//----------------------------------------------------------------------------- +static float ComputeDisplacementFaceArea( dface_t* pFace ) +{ + float area = 0.0f; + + // Compute the area of the base face + int* pSurfEdges = &dsurfedges[pFace->firstedge]; + int vertexIdx = (pSurfEdges[0] < 0); + int firstVertexIndex = dedges[abs(pSurfEdges[0])].v[vertexIdx]; + dvertex_t* pFirstVertex = &dvertexes[firstVertexIndex]; + for (int i = 1; i <= 2; ++i ) + { + int vertexIdx = (pSurfEdges[i] < 0); + dedge_t* pEdge = &dedges[abs(pSurfEdges[i])]; + + // Compute two triangle edges + Vector e1, e2; + VectorSubtract( dvertexes[pEdge->v[vertexIdx]].point, pFirstVertex->point, e1 ); + VectorSubtract( dvertexes[pEdge->v[1 - vertexIdx]].point, pFirstVertex->point, e2 ); + + // Compute the triangle area + Vector areaVec; + CrossProduct( e1, e2, areaVec ); + float normalLength = areaVec.Length(); + area += 0.5f * normalLength; + } + + return area; +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects on a face +//----------------------------------------------------------------------------- +static void EmitDetailObjectsOnDisplacementFace( dface_t* pFace, + DetailObject_t& detail, CCoreDispInfo& coreDispInfo ) +{ + assert(pFace->numedges == 4); + + // We're going to pick a bunch of random points, and then probabilistically + // decide whether or not to plant a detail object there. + + // Compute the area of the base face + float area = ComputeDisplacementFaceArea( pFace ); + + // Compute the number of samples to take + int numSamples = area * detail.m_Density * 0.000001; + + // Now take a sample, and randomly place an object there + for (int i = 0; i < numSamples; ++i ) + { + // Create a random sample... + float u = rand() / (float)VALVE_RAND_MAX; + float v = rand() / (float)VALVE_RAND_MAX; + + // Compute alpha + float alpha; + Vector pt, normal; + coreDispInfo.GetPositionOnSurface( u, v, pt, &normal, &alpha ); + alpha /= 255.0f; + + // Select a group based on the alpha value + int group = SelectGroup( detail, alpha ); + + // Now that we've got a group, choose a detail + int model = SelectDetail( detail.m_Groups[group] ); + if (model < 0) + continue; + + // Got a detail! Place it on the surface... + PlaceDetail( detail.m_Groups[group].m_Models[model], pt, normal ); + } +} + + +//----------------------------------------------------------------------------- +// Sort detail objects by leaf +//----------------------------------------------------------------------------- +static int SortFunc( const void *arg1, const void *arg2 ) +{ + int nDelta = ((DetailObjectLump_t*)arg1)->m_Leaf - ((DetailObjectLump_t*)arg2)->m_Leaf; + if ( nDelta < 0 ) + return -1; + if ( nDelta > 0 ) + return 1; + return 0; +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects in the lump +//----------------------------------------------------------------------------- +static void SetLumpData( ) +{ + // Sort detail props by leaf + qsort( s_DetailObjectLump.Base(), s_DetailObjectLump.Count(), sizeof(DetailObjectLump_t), SortFunc ); + + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(GAMELUMP_DETAIL_PROPS); + if (handle != g_GameLumps.InvalidGameLump()) + { + g_GameLumps.DestroyGameLump(handle); + } + int nDictSize = s_DetailObjectDictLump.Count() * sizeof(DetailObjectDictLump_t); + int nSpriteDictSize = s_DetailSpriteDictLump.Count() * sizeof(DetailSpriteDictLump_t); + int nObjSize = s_DetailObjectLump.Count() * sizeof(DetailObjectLump_t); + int nSize = nDictSize + nSpriteDictSize + nObjSize + (3 * sizeof(int)); + + handle = g_GameLumps.CreateGameLump( GAMELUMP_DETAIL_PROPS, nSize, 0, GAMELUMP_DETAIL_PROPS_VERSION ); + + // Serialize the data + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), nSize ); + buf.PutInt( s_DetailObjectDictLump.Count() ); + if (nDictSize) + { + buf.Put( s_DetailObjectDictLump.Base(), nDictSize ); + } + buf.PutInt( s_DetailSpriteDictLump.Count() ); + if (nSpriteDictSize) + { + buf.Put( s_DetailSpriteDictLump.Base(), nSpriteDictSize ); + } + buf.PutInt( s_DetailObjectLump.Count() ); + if (nObjSize) + { + buf.Put( s_DetailObjectLump.Base(), nObjSize ); + } +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects in the level +//----------------------------------------------------------------------------- +void EmitDetailModels() +{ + StartPacifier("Placing detail props : "); + + // Place stuff on each face + dface_t* pFace = dfaces; + for (int j = 0; j < numfaces; ++j) + { + UpdatePacifier( (float)j / (float)numfaces ); + + // Get at the material associated with this face + texinfo_t* pTexInfo = &texinfo[pFace[j].texinfo]; + dtexdata_t* pTexData = GetTexData( pTexInfo->texdata ); + + // Try to get at the material + bool found; + MaterialSystemMaterial_t handle = + FindOriginalMaterial( TexDataStringTable_GetString( pTexData->nameStringTableID ), + &found, false ); + if (!found) + continue; + + // See if its got any detail objects on it + const char* pDetailType = GetMaterialVar( handle, "%detailtype" ); + if (!pDetailType) + continue; + + // Get the detail type... + DetailObject_t search; + search.m_Name = pDetailType; + int objectType = s_DetailObjectDict.Find(search); + if (objectType < 0) + { + Warning("Material %s uses unknown detail object type %s!\n", + TexDataStringTable_GetString( pTexData->nameStringTableID ), + pDetailType); + continue; + } + + // Emit objects on a particular face + DetailObject_t& detail = s_DetailObjectDict[objectType]; + + // Initialize the Random Number generators for detail prop placement based on the hammer Face num. + int detailpropseed = dfaceids[j].hammerfaceid; +#ifdef WARNSEEDNUMBER + Warning( "[%d]\n",detailpropseed ); +#endif + srand( detailpropseed ); + RandomSeed( detailpropseed ); + + if (pFace[j].dispinfo < 0) + { + EmitDetailObjectsOnFace( &pFace[j], detail ); + } + else + { + // Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates. + mapdispinfo_t *pMapDisp = &mapdispinfo[pFace[j].dispinfo]; + CCoreDispInfo coreDispInfo; + DispMapToCoreDispInfo( pMapDisp, &coreDispInfo, NULL, NULL ); + + EmitDetailObjectsOnDisplacementFace( &pFace[j], detail, coreDispInfo ); + } + } + + // Emit specifically specified detail props + Vector origin; + QAngle angles; + Vector2D pos[2]; + Vector2D tex[2]; + for (int i = 0; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!strcmp(pEntity, "detail_prop") || !strcmp(pEntity, "prop_detail")) + { + GetVectorForKey( &entities[i], "origin", origin ); + GetAnglesForKey( &entities[i], "angles", angles ); + char* pModelName = ValueForKey( &entities[i], "model" ); + int nOrientation = IntForKey( &entities[i], "detailOrientation" ); + + AddDetailToLump( pModelName, origin, angles, nOrientation ); + + // strip this ent from the .bsp file + entities[i].epairs = 0; + continue; + } + + if (!strcmp(pEntity, "prop_detail_sprite")) + { + GetVectorForKey( &entities[i], "origin", origin ); + GetAnglesForKey( &entities[i], "angles", angles ); + int nOrientation = IntForKey( &entities[i], "detailOrientation" ); + GetVector2DForKey( &entities[i], "position_ul", pos[0] ); + GetVector2DForKey( &entities[i], "position_lr", pos[1] ); + GetVector2DForKey( &entities[i], "tex_ul", tex[0] ); + GetVector2DForKey( &entities[i], "tex_size", tex[1] ); + float flTextureSize = FloatForKey( &entities[i], "tex_total_size" ); + + tex[1].x += tex[0].x - 0.5f; + tex[1].y += tex[0].y - 0.5f; + tex[0].x += 0.5f; + tex[0].y += 0.5f; + tex[0] /= flTextureSize; + tex[1] /= flTextureSize; + + AddDetailSpriteToLump( origin, angles, nOrientation, pos, tex, 1.0f, DETAIL_PROP_TYPE_SPRITE ); + + // strip this ent from the .bsp file + entities[i].epairs = 0; + continue; + } + } + + EndPacifier( true ); +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects in the level +//----------------------------------------------------------------------------- +void EmitDetailObjects() +{ + EmitDetailModels(); + + // Done! Now lets add the lumps (destroy previous ones) + SetLumpData( ); + + if ( s_nDetailOverflow != 0 ) + { + Warning( "Error! Too many detail props on this map. %d were not emitted!\n", s_nDetailOverflow ); + } +} diff --git a/mp/src/utils/vbsp/disp_ivp.cpp b/mp/src/utils/vbsp/disp_ivp.cpp index 3fe50799..1ed16518 100644 --- a/mp/src/utils/vbsp/disp_ivp.cpp +++ b/mp/src/utils/vbsp/disp_ivp.cpp @@ -1,359 +1,359 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// -#include "vbsp.h" -#include "disp_vbsp.h" -#include "builddisp.h" -#include "disp_common.h" -#include "ivp.h" -#include "disp_ivp.h" -#include "vphysics_interface.h" -#include "vphysics/virtualmesh.h" -#include "utlrbtree.h" -#include "tier1/utlbuffer.h" -#include "materialpatch.h" - -struct disp_grid_t -{ - int gridIndex; - CUtlVector dispList; -}; - -static CUtlVector gDispGridList; - - - -disp_grid_t &FindOrInsertGrid( int gridIndex ) -{ - // linear search is slow, but only a few grids will be present - for ( int i = gDispGridList.Count()-1; i >= 0; i-- ) - { - if ( gDispGridList[i].gridIndex == gridIndex ) - { - return gDispGridList[i]; - } - } - int index = gDispGridList.AddToTail(); - gDispGridList[index].gridIndex = gridIndex; - - // must be empty - Assert( gDispGridList[index].dispList.Count() == 0 ); - - return gDispGridList[index]; -} - -// UNDONE: Tune these or adapt them to map size or triangle count? -#define DISP_GRID_SIZEX 4096 -#define DISP_GRID_SIZEY 4096 -#define DISP_GRID_SIZEZ 8192 - -int Disp_GridIndex( CCoreDispInfo *pDispInfo ) -{ - // quick hash the center into the grid and put the whole terrain in that grid - Vector mins, maxs; - pDispInfo->GetNode(0)->GetBoundingBox( mins, maxs ); - Vector center; - center = 0.5 * (mins + maxs); - // make sure it's positive - center += Vector(MAX_COORD_INTEGER,MAX_COORD_INTEGER,MAX_COORD_INTEGER); - int gridX = center.x / DISP_GRID_SIZEX; - int gridY = center.y / DISP_GRID_SIZEY; - int gridZ = center.z / DISP_GRID_SIZEZ; - - gridX &= 0xFF; - gridY &= 0xFF; - gridZ &= 0xFF; - return MAKEID( gridX, gridY, gridZ, 0 ); -} - -void AddToGrid( int gridIndex, int dispIndex ) -{ - disp_grid_t &grid = FindOrInsertGrid( gridIndex ); - grid.dispList.AddToTail( dispIndex ); -} - -MaterialSystemMaterial_t GetMatIDFromDisp( mapdispinfo_t *pMapDisp ) -{ - texinfo_t *pTexInfo = &texinfo[pMapDisp->face.texinfo]; - dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); - MaterialSystemMaterial_t matID = FindOriginalMaterial( TexDataStringTable_GetString( pTexData->nameStringTableID ), NULL, true ); - return matID; -} - -// check this and disable virtual mesh if in use -bool Disp_HasPower4Displacements() -{ - for ( int i = 0; i < g_CoreDispInfos.Count(); i++ ) - { - if ( g_CoreDispInfos[i]->GetPower() > 3 ) - { - return true; - } - } - return false; -} - -// adds all displacement faces as a series of convex objects -// UNDONE: Only add the displacements for this model? -void Disp_AddCollisionModels( CUtlVector &collisionList, dmodel_t *pModel, int contentsMask) -{ - int dispIndex; - - // Add each displacement to the grid hash - for ( dispIndex = 0; dispIndex < g_CoreDispInfos.Count(); dispIndex++ ) - { - CCoreDispInfo *pDispInfo = g_CoreDispInfos[ dispIndex ]; - mapdispinfo_t *pMapDisp = &mapdispinfo[ dispIndex ]; - - // not solid for this pass - if ( !(pMapDisp->contents & contentsMask) ) - continue; - - int gridIndex = Disp_GridIndex( pDispInfo ); - AddToGrid( gridIndex, dispIndex ); - } - - // now make a polysoup for the terrain in each grid - for ( int grid = 0; grid < gDispGridList.Count(); grid++ ) - { - int triCount = 0; - CPhysPolysoup *pTerrainPhysics = physcollision->PolysoupCreate(); - - // iterate the displacements in this grid - for ( int listIndex = 0; listIndex < gDispGridList[grid].dispList.Count(); listIndex++ ) - { - dispIndex = gDispGridList[grid].dispList[listIndex]; - CCoreDispInfo *pDispInfo = g_CoreDispInfos[ dispIndex ]; - mapdispinfo_t *pMapDisp = &mapdispinfo[ dispIndex ]; - - // Get the material id. - MaterialSystemMaterial_t matID = GetMatIDFromDisp( pMapDisp ); - - // Build a triangle list. This shares the tesselation code with the engine. - CUtlVector indices; - CVBSPTesselateHelper helper; - helper.m_pIndices = &indices; - helper.m_pActiveVerts = pDispInfo->GetAllowedVerts().Base(); - helper.m_pPowerInfo = pDispInfo->GetPowerInfo(); - - ::TesselateDisplacement( &helper ); - - Assert( indices.Count() > 0 ); - Assert( indices.Count() % 3 == 0 ); // Make sure indices are a multiple of 3. - int nTriCount = indices.Count() / 3; - triCount += nTriCount; - if ( triCount >= 65536 ) - { - // don't put more than 64K tris in any single collision model - CPhysCollide *pCollide = physcollision->ConvertPolysoupToCollide( pTerrainPhysics, false ); - if ( pCollide ) - { - collisionList.AddToTail( new CPhysCollisionEntryStaticMesh( pCollide, NULL ) ); - } - // Throw this polysoup away and start over for the remaining triangles - physcollision->PolysoupDestroy( pTerrainPhysics ); - pTerrainPhysics = physcollision->PolysoupCreate(); - triCount = nTriCount; - } - Vector tmpVerts[3]; - for ( int iTri = 0; iTri < nTriCount; ++iTri ) - { - float flAlphaTotal = 0.0f; - for ( int iTriVert = 0; iTriVert < 3; ++iTriVert ) - { - pDispInfo->GetVert( indices[iTri*3+iTriVert], tmpVerts[iTriVert] ); - flAlphaTotal += pDispInfo->GetAlpha( indices[iTri*3+iTriVert] ); - } - - int nProp = g_SurfaceProperties[texinfo[pMapDisp->face.texinfo].texdata]; - if ( flAlphaTotal > DISP_ALPHA_PROP_DELTA ) - { - int nProp2 = GetSurfaceProperties2( matID, "surfaceprop2" ); - if ( nProp2 != -1 ) - { - nProp = nProp2; - } - } - int nMaterialIndex = RemapWorldMaterial( nProp ); - physcollision->PolysoupAddTriangle( pTerrainPhysics, tmpVerts[0], tmpVerts[1], tmpVerts[2], nMaterialIndex ); - } - } - - // convert the whole grid's polysoup to a collide and store in the collision list - CPhysCollide *pCollide = physcollision->ConvertPolysoupToCollide( pTerrainPhysics, false ); - if ( pCollide ) - { - collisionList.AddToTail( new CPhysCollisionEntryStaticMesh( pCollide, NULL ) ); - } - // now that we have the collide, we're done with the soup - physcollision->PolysoupDestroy( pTerrainPhysics ); - } -} - - -class CDispMeshEvent : public IVirtualMeshEvent -{ -public: - CDispMeshEvent( unsigned short *pIndices, int indexCount, CCoreDispInfo *pDispInfo ); - virtual void GetVirtualMesh( void *userData, virtualmeshlist_t *pList ); - virtual void GetWorldspaceBounds( void *userData, Vector *pMins, Vector *pMaxs ); - virtual void GetTrianglesInSphere( void *userData, const Vector ¢er, float radius, virtualmeshtrianglelist_t *pList ); - - CUtlVector m_verts; - unsigned short *m_pIndices; - int m_indexCount; -}; - -CDispMeshEvent::CDispMeshEvent( unsigned short *pIndices, int indexCount, CCoreDispInfo *pDispInfo ) -{ - m_pIndices = pIndices; - m_indexCount = indexCount; - int maxIndex = 0; - for ( int i = 0; i < indexCount; i++ ) - { - if ( pIndices[i] > maxIndex ) - { - maxIndex = pIndices[i]; - } - } - for ( int i = 0; i < indexCount/2; i++ ) - { - V_swap( pIndices[i], pIndices[(indexCount-i)-1] ); - } - int count = maxIndex + 1; - m_verts.SetCount( count ); - for ( int i = 0; i < count; i++ ) - { - m_verts[i] = pDispInfo->GetVert(i); - } -} - -void CDispMeshEvent::GetVirtualMesh( void *userData, virtualmeshlist_t *pList ) -{ - Assert(userData==((void *)this)); - pList->pVerts = m_verts.Base(); - pList->indexCount = m_indexCount; - pList->triangleCount = m_indexCount/3; - pList->vertexCount = m_verts.Count(); - pList->surfacePropsIndex = 0; // doesn't matter here, reset at runtime - pList->pHull = NULL; - int indexMax = ARRAYSIZE(pList->indices); - int indexCount = min(m_indexCount, indexMax); - Assert(m_indexCount < indexMax); - Q_memcpy( pList->indices, m_pIndices, sizeof(*m_pIndices) * indexCount ); -} - -void CDispMeshEvent::GetWorldspaceBounds( void *userData, Vector *pMins, Vector *pMaxs ) -{ - Assert(userData==((void *)this)); - ClearBounds( *pMins, *pMaxs ); - for ( int i = 0; i < m_verts.Count(); i++ ) - { - AddPointToBounds( m_verts[i], *pMins, *pMaxs ); - } -} - -void CDispMeshEvent::GetTrianglesInSphere( void *userData, const Vector ¢er, float radius, virtualmeshtrianglelist_t *pList ) -{ - Assert(userData==((void *)this)); - pList->triangleCount = m_indexCount/3; - int indexMax = ARRAYSIZE(pList->triangleIndices); - int indexCount = min(m_indexCount, indexMax); - Assert(m_indexCount < MAX_VIRTUAL_TRIANGLES*3); - Q_memcpy( pList->triangleIndices, m_pIndices, sizeof(*m_pIndices) * indexCount ); -} - -void Disp_BuildVirtualMesh( int contentsMask ) -{ - CUtlVector virtualMeshes; - virtualMeshes.EnsureCount( g_CoreDispInfos.Count() ); - for ( int i = 0; i < g_CoreDispInfos.Count(); i++ ) - { - CCoreDispInfo *pDispInfo = g_CoreDispInfos[ i ]; - mapdispinfo_t *pMapDisp = &mapdispinfo[ i ]; - - virtualMeshes[i] = NULL; - // not solid for this pass - if ( !(pMapDisp->contents & contentsMask) ) - continue; - - // Build a triangle list. This shares the tesselation code with the engine. - CUtlVector indices; - CVBSPTesselateHelper helper; - helper.m_pIndices = &indices; - helper.m_pActiveVerts = pDispInfo->GetAllowedVerts().Base(); - helper.m_pPowerInfo = pDispInfo->GetPowerInfo(); - - ::TesselateDisplacement( &helper ); - - // validate the collision data - if ( 1 ) - { - int triCount = indices.Count() / 3; - for ( int j = 0; j < triCount; j++ ) - { - int index = j * 3; - Vector v0 = pDispInfo->GetVert( indices[index+0] ); - Vector v1 = pDispInfo->GetVert( indices[index+1] ); - Vector v2 = pDispInfo->GetVert( indices[index+2] ); - if ( v0 == v1 || v1 == v2 || v2 == v0 ) - { - Warning( "Displacement %d has bad geometry near %.2f %.2f %.2f\n", i, v0.x, v0.y, v0.z ); - texinfo_t *pTexInfo = &texinfo[pMapDisp->face.texinfo]; - dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); - const char *pMatName = TexDataStringTable_GetString( pTexData->nameStringTableID ); - - Error( "Can't compile displacement physics, exiting. Texture is %s\n", pMatName ); - } - } - - } - CDispMeshEvent meshHandler( indices.Base(), indices.Count(), pDispInfo ); - virtualmeshparams_t params; - params.buildOuterHull = true; - params.pMeshEventHandler = &meshHandler; - params.userData = &meshHandler; - virtualMeshes[i] = physcollision->CreateVirtualMesh( params ); - } - unsigned int totalSize = 0; - CUtlBuffer buf; - dphysdisp_t header; - header.numDisplacements = g_CoreDispInfos.Count(); - buf.PutObjects( &header ); - - CUtlVector dispBuf; - for ( int i = 0; i < header.numDisplacements; i++ ) - { - if ( virtualMeshes[i] ) - { - unsigned int testSize = physcollision->CollideSize( virtualMeshes[i] ); - totalSize += testSize; - buf.PutShort( testSize ); - } - else - { - buf.PutShort( -1 ); - } - } - for ( int i = 0; i < header.numDisplacements; i++ ) - { - if ( virtualMeshes[i] ) - { - unsigned int testSize = physcollision->CollideSize( virtualMeshes[i] ); - dispBuf.RemoveAll(); - dispBuf.EnsureCount(testSize); - - unsigned int outSize = physcollision->CollideWrite( dispBuf.Base(), virtualMeshes[i], false ); - Assert( outSize == testSize ); - buf.Put( dispBuf.Base(), outSize ); - } - } - g_PhysDispSize = totalSize + sizeof(dphysdisp_t) + (sizeof(unsigned short) * header.numDisplacements); - Assert( buf.TellMaxPut() == g_PhysDispSize ); - g_PhysDispSize = buf.TellMaxPut(); - g_pPhysDisp = new byte[g_PhysDispSize]; - Q_memcpy( g_pPhysDisp, buf.Base(), g_PhysDispSize ); -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "vbsp.h" +#include "disp_vbsp.h" +#include "builddisp.h" +#include "disp_common.h" +#include "ivp.h" +#include "disp_ivp.h" +#include "vphysics_interface.h" +#include "vphysics/virtualmesh.h" +#include "utlrbtree.h" +#include "tier1/utlbuffer.h" +#include "materialpatch.h" + +struct disp_grid_t +{ + int gridIndex; + CUtlVector dispList; +}; + +static CUtlVector gDispGridList; + + + +disp_grid_t &FindOrInsertGrid( int gridIndex ) +{ + // linear search is slow, but only a few grids will be present + for ( int i = gDispGridList.Count()-1; i >= 0; i-- ) + { + if ( gDispGridList[i].gridIndex == gridIndex ) + { + return gDispGridList[i]; + } + } + int index = gDispGridList.AddToTail(); + gDispGridList[index].gridIndex = gridIndex; + + // must be empty + Assert( gDispGridList[index].dispList.Count() == 0 ); + + return gDispGridList[index]; +} + +// UNDONE: Tune these or adapt them to map size or triangle count? +#define DISP_GRID_SIZEX 4096 +#define DISP_GRID_SIZEY 4096 +#define DISP_GRID_SIZEZ 8192 + +int Disp_GridIndex( CCoreDispInfo *pDispInfo ) +{ + // quick hash the center into the grid and put the whole terrain in that grid + Vector mins, maxs; + pDispInfo->GetNode(0)->GetBoundingBox( mins, maxs ); + Vector center; + center = 0.5 * (mins + maxs); + // make sure it's positive + center += Vector(MAX_COORD_INTEGER,MAX_COORD_INTEGER,MAX_COORD_INTEGER); + int gridX = center.x / DISP_GRID_SIZEX; + int gridY = center.y / DISP_GRID_SIZEY; + int gridZ = center.z / DISP_GRID_SIZEZ; + + gridX &= 0xFF; + gridY &= 0xFF; + gridZ &= 0xFF; + return MAKEID( gridX, gridY, gridZ, 0 ); +} + +void AddToGrid( int gridIndex, int dispIndex ) +{ + disp_grid_t &grid = FindOrInsertGrid( gridIndex ); + grid.dispList.AddToTail( dispIndex ); +} + +MaterialSystemMaterial_t GetMatIDFromDisp( mapdispinfo_t *pMapDisp ) +{ + texinfo_t *pTexInfo = &texinfo[pMapDisp->face.texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + MaterialSystemMaterial_t matID = FindOriginalMaterial( TexDataStringTable_GetString( pTexData->nameStringTableID ), NULL, true ); + return matID; +} + +// check this and disable virtual mesh if in use +bool Disp_HasPower4Displacements() +{ + for ( int i = 0; i < g_CoreDispInfos.Count(); i++ ) + { + if ( g_CoreDispInfos[i]->GetPower() > 3 ) + { + return true; + } + } + return false; +} + +// adds all displacement faces as a series of convex objects +// UNDONE: Only add the displacements for this model? +void Disp_AddCollisionModels( CUtlVector &collisionList, dmodel_t *pModel, int contentsMask) +{ + int dispIndex; + + // Add each displacement to the grid hash + for ( dispIndex = 0; dispIndex < g_CoreDispInfos.Count(); dispIndex++ ) + { + CCoreDispInfo *pDispInfo = g_CoreDispInfos[ dispIndex ]; + mapdispinfo_t *pMapDisp = &mapdispinfo[ dispIndex ]; + + // not solid for this pass + if ( !(pMapDisp->contents & contentsMask) ) + continue; + + int gridIndex = Disp_GridIndex( pDispInfo ); + AddToGrid( gridIndex, dispIndex ); + } + + // now make a polysoup for the terrain in each grid + for ( int grid = 0; grid < gDispGridList.Count(); grid++ ) + { + int triCount = 0; + CPhysPolysoup *pTerrainPhysics = physcollision->PolysoupCreate(); + + // iterate the displacements in this grid + for ( int listIndex = 0; listIndex < gDispGridList[grid].dispList.Count(); listIndex++ ) + { + dispIndex = gDispGridList[grid].dispList[listIndex]; + CCoreDispInfo *pDispInfo = g_CoreDispInfos[ dispIndex ]; + mapdispinfo_t *pMapDisp = &mapdispinfo[ dispIndex ]; + + // Get the material id. + MaterialSystemMaterial_t matID = GetMatIDFromDisp( pMapDisp ); + + // Build a triangle list. This shares the tesselation code with the engine. + CUtlVector indices; + CVBSPTesselateHelper helper; + helper.m_pIndices = &indices; + helper.m_pActiveVerts = pDispInfo->GetAllowedVerts().Base(); + helper.m_pPowerInfo = pDispInfo->GetPowerInfo(); + + ::TesselateDisplacement( &helper ); + + Assert( indices.Count() > 0 ); + Assert( indices.Count() % 3 == 0 ); // Make sure indices are a multiple of 3. + int nTriCount = indices.Count() / 3; + triCount += nTriCount; + if ( triCount >= 65536 ) + { + // don't put more than 64K tris in any single collision model + CPhysCollide *pCollide = physcollision->ConvertPolysoupToCollide( pTerrainPhysics, false ); + if ( pCollide ) + { + collisionList.AddToTail( new CPhysCollisionEntryStaticMesh( pCollide, NULL ) ); + } + // Throw this polysoup away and start over for the remaining triangles + physcollision->PolysoupDestroy( pTerrainPhysics ); + pTerrainPhysics = physcollision->PolysoupCreate(); + triCount = nTriCount; + } + Vector tmpVerts[3]; + for ( int iTri = 0; iTri < nTriCount; ++iTri ) + { + float flAlphaTotal = 0.0f; + for ( int iTriVert = 0; iTriVert < 3; ++iTriVert ) + { + pDispInfo->GetVert( indices[iTri*3+iTriVert], tmpVerts[iTriVert] ); + flAlphaTotal += pDispInfo->GetAlpha( indices[iTri*3+iTriVert] ); + } + + int nProp = g_SurfaceProperties[texinfo[pMapDisp->face.texinfo].texdata]; + if ( flAlphaTotal > DISP_ALPHA_PROP_DELTA ) + { + int nProp2 = GetSurfaceProperties2( matID, "surfaceprop2" ); + if ( nProp2 != -1 ) + { + nProp = nProp2; + } + } + int nMaterialIndex = RemapWorldMaterial( nProp ); + physcollision->PolysoupAddTriangle( pTerrainPhysics, tmpVerts[0], tmpVerts[1], tmpVerts[2], nMaterialIndex ); + } + } + + // convert the whole grid's polysoup to a collide and store in the collision list + CPhysCollide *pCollide = physcollision->ConvertPolysoupToCollide( pTerrainPhysics, false ); + if ( pCollide ) + { + collisionList.AddToTail( new CPhysCollisionEntryStaticMesh( pCollide, NULL ) ); + } + // now that we have the collide, we're done with the soup + physcollision->PolysoupDestroy( pTerrainPhysics ); + } +} + + +class CDispMeshEvent : public IVirtualMeshEvent +{ +public: + CDispMeshEvent( unsigned short *pIndices, int indexCount, CCoreDispInfo *pDispInfo ); + virtual void GetVirtualMesh( void *userData, virtualmeshlist_t *pList ); + virtual void GetWorldspaceBounds( void *userData, Vector *pMins, Vector *pMaxs ); + virtual void GetTrianglesInSphere( void *userData, const Vector ¢er, float radius, virtualmeshtrianglelist_t *pList ); + + CUtlVector m_verts; + unsigned short *m_pIndices; + int m_indexCount; +}; + +CDispMeshEvent::CDispMeshEvent( unsigned short *pIndices, int indexCount, CCoreDispInfo *pDispInfo ) +{ + m_pIndices = pIndices; + m_indexCount = indexCount; + int maxIndex = 0; + for ( int i = 0; i < indexCount; i++ ) + { + if ( pIndices[i] > maxIndex ) + { + maxIndex = pIndices[i]; + } + } + for ( int i = 0; i < indexCount/2; i++ ) + { + V_swap( pIndices[i], pIndices[(indexCount-i)-1] ); + } + int count = maxIndex + 1; + m_verts.SetCount( count ); + for ( int i = 0; i < count; i++ ) + { + m_verts[i] = pDispInfo->GetVert(i); + } +} + +void CDispMeshEvent::GetVirtualMesh( void *userData, virtualmeshlist_t *pList ) +{ + Assert(userData==((void *)this)); + pList->pVerts = m_verts.Base(); + pList->indexCount = m_indexCount; + pList->triangleCount = m_indexCount/3; + pList->vertexCount = m_verts.Count(); + pList->surfacePropsIndex = 0; // doesn't matter here, reset at runtime + pList->pHull = NULL; + int indexMax = ARRAYSIZE(pList->indices); + int indexCount = min(m_indexCount, indexMax); + Assert(m_indexCount < indexMax); + Q_memcpy( pList->indices, m_pIndices, sizeof(*m_pIndices) * indexCount ); +} + +void CDispMeshEvent::GetWorldspaceBounds( void *userData, Vector *pMins, Vector *pMaxs ) +{ + Assert(userData==((void *)this)); + ClearBounds( *pMins, *pMaxs ); + for ( int i = 0; i < m_verts.Count(); i++ ) + { + AddPointToBounds( m_verts[i], *pMins, *pMaxs ); + } +} + +void CDispMeshEvent::GetTrianglesInSphere( void *userData, const Vector ¢er, float radius, virtualmeshtrianglelist_t *pList ) +{ + Assert(userData==((void *)this)); + pList->triangleCount = m_indexCount/3; + int indexMax = ARRAYSIZE(pList->triangleIndices); + int indexCount = min(m_indexCount, indexMax); + Assert(m_indexCount < MAX_VIRTUAL_TRIANGLES*3); + Q_memcpy( pList->triangleIndices, m_pIndices, sizeof(*m_pIndices) * indexCount ); +} + +void Disp_BuildVirtualMesh( int contentsMask ) +{ + CUtlVector virtualMeshes; + virtualMeshes.EnsureCount( g_CoreDispInfos.Count() ); + for ( int i = 0; i < g_CoreDispInfos.Count(); i++ ) + { + CCoreDispInfo *pDispInfo = g_CoreDispInfos[ i ]; + mapdispinfo_t *pMapDisp = &mapdispinfo[ i ]; + + virtualMeshes[i] = NULL; + // not solid for this pass + if ( !(pMapDisp->contents & contentsMask) ) + continue; + + // Build a triangle list. This shares the tesselation code with the engine. + CUtlVector indices; + CVBSPTesselateHelper helper; + helper.m_pIndices = &indices; + helper.m_pActiveVerts = pDispInfo->GetAllowedVerts().Base(); + helper.m_pPowerInfo = pDispInfo->GetPowerInfo(); + + ::TesselateDisplacement( &helper ); + + // validate the collision data + if ( 1 ) + { + int triCount = indices.Count() / 3; + for ( int j = 0; j < triCount; j++ ) + { + int index = j * 3; + Vector v0 = pDispInfo->GetVert( indices[index+0] ); + Vector v1 = pDispInfo->GetVert( indices[index+1] ); + Vector v2 = pDispInfo->GetVert( indices[index+2] ); + if ( v0 == v1 || v1 == v2 || v2 == v0 ) + { + Warning( "Displacement %d has bad geometry near %.2f %.2f %.2f\n", i, v0.x, v0.y, v0.z ); + texinfo_t *pTexInfo = &texinfo[pMapDisp->face.texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + const char *pMatName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + + Error( "Can't compile displacement physics, exiting. Texture is %s\n", pMatName ); + } + } + + } + CDispMeshEvent meshHandler( indices.Base(), indices.Count(), pDispInfo ); + virtualmeshparams_t params; + params.buildOuterHull = true; + params.pMeshEventHandler = &meshHandler; + params.userData = &meshHandler; + virtualMeshes[i] = physcollision->CreateVirtualMesh( params ); + } + unsigned int totalSize = 0; + CUtlBuffer buf; + dphysdisp_t header; + header.numDisplacements = g_CoreDispInfos.Count(); + buf.PutObjects( &header ); + + CUtlVector dispBuf; + for ( int i = 0; i < header.numDisplacements; i++ ) + { + if ( virtualMeshes[i] ) + { + unsigned int testSize = physcollision->CollideSize( virtualMeshes[i] ); + totalSize += testSize; + buf.PutShort( testSize ); + } + else + { + buf.PutShort( -1 ); + } + } + for ( int i = 0; i < header.numDisplacements; i++ ) + { + if ( virtualMeshes[i] ) + { + unsigned int testSize = physcollision->CollideSize( virtualMeshes[i] ); + dispBuf.RemoveAll(); + dispBuf.EnsureCount(testSize); + + unsigned int outSize = physcollision->CollideWrite( dispBuf.Base(), virtualMeshes[i], false ); + Assert( outSize == testSize ); + buf.Put( dispBuf.Base(), outSize ); + } + } + g_PhysDispSize = totalSize + sizeof(dphysdisp_t) + (sizeof(unsigned short) * header.numDisplacements); + Assert( buf.TellMaxPut() == g_PhysDispSize ); + g_PhysDispSize = buf.TellMaxPut(); + g_pPhysDisp = new byte[g_PhysDispSize]; + Q_memcpy( g_pPhysDisp, buf.Base(), g_PhysDispSize ); +} + diff --git a/mp/src/utils/vbsp/disp_ivp.h b/mp/src/utils/vbsp/disp_ivp.h index 2c473f9a..c89daceb 100644 --- a/mp/src/utils/vbsp/disp_ivp.h +++ b/mp/src/utils/vbsp/disp_ivp.h @@ -1,49 +1,49 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef DISP_IVP_H -#define DISP_IVP_H -#ifdef _WIN32 -#pragma once -#endif - -#include "utlvector.h" -#include "../../public/disp_tesselate.h" - - -class CPhysCollisionEntry; -struct dmodel_t; - - -// This provides the template functions that the engine's tesselation code needs -// so we can share the code in VBSP. -class CVBSPTesselateHelper : public CBaseTesselateHelper -{ -public: - void EndTriangle() - { - m_pIndices->AddToTail( m_TempIndices[0] ); - m_pIndices->AddToTail( m_TempIndices[1] ); - m_pIndices->AddToTail( m_TempIndices[2] ); - } - - DispNodeInfo_t& GetNodeInfo( int iNodeBit ) - { - // VBSP doesn't care about these. Give it back something to play with. - static DispNodeInfo_t dummy; - return dummy; - } - -public: - CUtlVector *m_pIndices; -}; - - -extern void Disp_AddCollisionModels( CUtlVector &collisionList, dmodel_t *pModel, int contentsMask ); -extern void Disp_BuildVirtualMesh( int contentsMask ); -extern bool Disp_HasPower4Displacements(); - -#endif // DISP_IVP_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef DISP_IVP_H +#define DISP_IVP_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utlvector.h" +#include "../../public/disp_tesselate.h" + + +class CPhysCollisionEntry; +struct dmodel_t; + + +// This provides the template functions that the engine's tesselation code needs +// so we can share the code in VBSP. +class CVBSPTesselateHelper : public CBaseTesselateHelper +{ +public: + void EndTriangle() + { + m_pIndices->AddToTail( m_TempIndices[0] ); + m_pIndices->AddToTail( m_TempIndices[1] ); + m_pIndices->AddToTail( m_TempIndices[2] ); + } + + DispNodeInfo_t& GetNodeInfo( int iNodeBit ) + { + // VBSP doesn't care about these. Give it back something to play with. + static DispNodeInfo_t dummy; + return dummy; + } + +public: + CUtlVector *m_pIndices; +}; + + +extern void Disp_AddCollisionModels( CUtlVector &collisionList, dmodel_t *pModel, int contentsMask ); +extern void Disp_BuildVirtualMesh( int contentsMask ); +extern bool Disp_HasPower4Displacements(); + +#endif // DISP_IVP_H diff --git a/mp/src/utils/vbsp/disp_vbsp.cpp b/mp/src/utils/vbsp/disp_vbsp.cpp index 82934f67..f3688595 100644 --- a/mp/src/utils/vbsp/disp_vbsp.cpp +++ b/mp/src/utils/vbsp/disp_vbsp.cpp @@ -1,675 +1,675 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//=============================================================================// - -#include "disp_vbsp.h" -#include "tier0/dbg.h" -#include "vbsp.h" -#include "mstristrip.h" -#include "writebsp.h" -#include "pacifier.h" -#include "disp_ivp.h" -#include "builddisp.h" -#include "mathlib/vector.h" - -// map displacement info -- runs parallel to the dispinfos struct -int nummapdispinfo = 0; -mapdispinfo_t mapdispinfo[MAX_MAP_DISPINFO]; - -CUtlVector g_CoreDispInfos; - -//----------------------------------------------------------------------------- -// Computes the bounds for a disp info -//----------------------------------------------------------------------------- -void ComputeDispInfoBounds( int dispinfo, Vector& mins, Vector& maxs ) -{ - CDispBox box; - - // Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates. - mapdispinfo_t *pMapDisp = &mapdispinfo[dispinfo]; - - CCoreDispInfo coreDispInfo; - DispMapToCoreDispInfo( pMapDisp, &coreDispInfo, NULL, NULL ); - - GetDispBox( &coreDispInfo, box ); - mins = box.m_Min; - maxs = box.m_Max; -} - -// Gets the barycentric coordinates of the position on the triangle where the lightmap -// coordinates are equal to lmCoords. This always generates the coordinates but it -// returns false if the point containing them does not lie inside the triangle. -bool GetBarycentricCoordsFromLightmapCoords( Vector2D tri[3], Vector2D const &lmCoords, float bcCoords[3] ) -{ - GetBarycentricCoords2D( tri[0], tri[1], tri[2], lmCoords, bcCoords ); - - return - (bcCoords[0] >= 0.0f && bcCoords[0] <= 1.0f) && - (bcCoords[1] >= 0.0f && bcCoords[1] <= 1.0f) && - (bcCoords[2] >= 0.0f && bcCoords[2] <= 1.0f); -} - - -bool FindTriIndexMapByUV( CCoreDispInfo *pCoreDisp, Vector2D const &lmCoords, - int &iTriangle, float flBarycentric[3] ) -{ - const CPowerInfo *pPowerInfo = GetPowerInfo( pCoreDisp->GetPower() ); - - // Search all the triangles.. - int nTriCount= pCoreDisp->GetTriCount(); - for ( int iTri = 0; iTri < nTriCount; ++iTri ) - { - unsigned short iVerts[3]; -// pCoreDisp->GetTriIndices( iTri, iVerts[0], iVerts[1], iVerts[2] ); - CTriInfo *pTri = &pPowerInfo->m_pTriInfos[iTri]; - iVerts[0] = pTri->m_Indices[0]; - iVerts[1] = pTri->m_Indices[1]; - iVerts[2] = pTri->m_Indices[2]; - - // Get this triangle's UVs. - Vector2D vecUV[3]; - for ( int iCoord = 0; iCoord < 3; ++iCoord ) - { - pCoreDisp->GetLuxelCoord( 0, iVerts[iCoord], vecUV[iCoord] ); - } - - // See if the passed-in UVs are in this triangle's UVs. - if( GetBarycentricCoordsFromLightmapCoords( vecUV, lmCoords, flBarycentric ) ) - { - iTriangle = iTri; - return true; - } - } - - return false; -} - - -void CalculateLightmapSamplePositions( CCoreDispInfo *pCoreDispInfo, const dface_t *pFace, CUtlVector &out ) -{ - int width = pFace->m_LightmapTextureSizeInLuxels[0] + 1; - int height = pFace->m_LightmapTextureSizeInLuxels[1] + 1; - - // For each lightmap sample, find the triangle it sits in. - Vector2D lmCoords; - for( int y=0; y < height; y++ ) - { - lmCoords.y = y + 0.5f; - - for( int x=0; x < width; x++ ) - { - lmCoords.x = x + 0.5f; - - float flBarycentric[3]; - int iTri; - - if( FindTriIndexMapByUV( pCoreDispInfo, lmCoords, iTri, flBarycentric ) ) - { - if( iTri < 255 ) - { - out.AddToTail( iTri ); - } - else - { - out.AddToTail( 255 ); - out.AddToTail( iTri - 255 ); - } - - out.AddToTail( (unsigned char)( flBarycentric[0] * 255.9f ) ); - out.AddToTail( (unsigned char)( flBarycentric[1] * 255.9f ) ); - out.AddToTail( (unsigned char)( flBarycentric[2] * 255.9f ) ); - } - else - { - out.AddToTail( 0 ); - out.AddToTail( 0 ); - out.AddToTail( 0 ); - out.AddToTail( 0 ); - } - } - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int GetDispInfoEntityNum( mapdispinfo_t *pDisp ) -{ - return pDisp->entitynum; -} - -// Setup a CCoreDispInfo given a mapdispinfo_t. -// If pFace is non-NULL, then lightmap texture coordinates will be generated. -void DispMapToCoreDispInfo( mapdispinfo_t *pMapDisp, CCoreDispInfo *pCoreDispInfo, dface_t *pFace, int *pSwappedTexInfos ) -{ - winding_t *pWinding = pMapDisp->face.originalface->winding; - - Assert( pWinding->numpoints == 4 ); - - // - // set initial surface data - // - CCoreDispSurface *pSurf = pCoreDispInfo->GetSurface(); - - texinfo_t *pTexInfo = &texinfo[ pMapDisp->face.texinfo ]; - Assert( pTexInfo != NULL ); - - // init material contents - pMapDisp->contents = pMapDisp->face.contents; - if (!(pMapDisp->contents & (ALL_VISIBLE_CONTENTS | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) ) - { - pMapDisp->contents |= CONTENTS_SOLID; - } - - pSurf->SetContents( pMapDisp->contents ); - - // Calculate the lightmap coordinates. - Vector2D tCoords[4] = {Vector2D(0,0),Vector2D(0,1),Vector2D(1,0),Vector2D(1,1)}; - if( pFace ) - { - Assert( pFace->numedges == 4 ); - - Vector pt[4]; - for( int i=0; i < 4; i++ ) - pt[i] = pWinding->p[i]; - - int zeroOffset[2] = {0,0}; - CalcTextureCoordsAtPoints( - pTexInfo->textureVecsTexelsPerWorldUnits, - zeroOffset, - pt, - 4, - tCoords ); - } - - // - // set face point data ... - // - pSurf->SetPointCount( 4 ); - for( int i = 0; i < 4; i++ ) - { - // position - pSurf->SetPoint( i, pWinding->p[i] ); - pSurf->SetTexCoord( i, tCoords[i] ); - } - - // reset surface given start info - pSurf->SetPointStart( pMapDisp->startPosition ); - pSurf->FindSurfPointStartIndex(); - pSurf->AdjustSurfPointData(); - - // Set the luxel coordinates on the base displacement surface. - Vector vecTmp( pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][0], - pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][1], - pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); - int nLuxelsPerWorldUnit = static_cast( 1.0f / VectorLength( vecTmp ) ); - Vector vecU( pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][0], - pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][1], - pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); - Vector vecV( pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][0], - pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][1], - pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][2] ); - bool bSwap = pSurf->CalcLuxelCoords( nLuxelsPerWorldUnit, false, vecU, vecV ); - - // Set the face m_LightmapExtents - if ( pFace ) - { - pFace->m_LightmapTextureSizeInLuxels[0] = pSurf->GetLuxelU(); - pFace->m_LightmapTextureSizeInLuxels[1] = pSurf->GetLuxelV(); - if ( bSwap ) - { - if ( pSwappedTexInfos[ pMapDisp->face.texinfo ] < 0 ) - { - // Create a new texinfo to hold the swapped data. - // We must do this because other surfaces may want the non-swapped data - // This fixes a lighting bug in d2_prison_08 where many non-displacement surfaces - // were pitch black, in addition to bugs in other maps I bet. - - // NOTE: Copy here because adding a texinfo could realloc. - texinfo_t temp = *pTexInfo; - memcpy( temp.lightmapVecsLuxelsPerWorldUnits[0], pTexInfo->lightmapVecsLuxelsPerWorldUnits[1], 4 * sizeof(float) ); - memcpy( temp.lightmapVecsLuxelsPerWorldUnits[1], pTexInfo->lightmapVecsLuxelsPerWorldUnits[0], 4 * sizeof(float) ); - temp.lightmapVecsLuxelsPerWorldUnits[1][0] *= -1.0f; - temp.lightmapVecsLuxelsPerWorldUnits[1][1] *= -1.0f; - temp.lightmapVecsLuxelsPerWorldUnits[1][2] *= -1.0f; - temp.lightmapVecsLuxelsPerWorldUnits[1][3] *= -1.0f; - pSwappedTexInfos[ pMapDisp->face.texinfo ] = texinfo.AddToTail( temp ); - } - pMapDisp->face.texinfo = pSwappedTexInfos[ pMapDisp->face.texinfo ]; - } - - // NOTE: This is here to help future-proof code, since there are codepaths where - // pTexInfo can be made invalid (texinfo.AddToTail above). - pTexInfo = NULL; - } - - // Setup the displacement vectors and offsets. - int size = ( ( ( 1 << pMapDisp->power ) + 1 ) * ( ( 1 << pMapDisp->power ) + 1 ) ); - - Vector vectorDisps[2048]; - float dispDists[2048]; - Assert( size < sizeof(vectorDisps)/sizeof(vectorDisps[0]) ); - - for( int j = 0; j < size; j++ ) - { - Vector v; - float dist; - - VectorScale( pMapDisp->vectorDisps[j], pMapDisp->dispDists[j], v ); - VectorAdd( v, pMapDisp->vectorOffsets[j], v ); - - dist = VectorLength( v ); - VectorNormalize( v ); - - vectorDisps[j] = v; - dispDists[j] = dist; - } - - - // Use CCoreDispInfo to setup the actual vertex positions. - pCoreDispInfo->InitDispInfo( pMapDisp->power, pMapDisp->minTess, pMapDisp->smoothingAngle, - pMapDisp->alphaValues, vectorDisps, dispDists ); - pCoreDispInfo->Create(); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void EmitInitialDispInfos( void ) -{ - int i; - mapdispinfo_t *pMapDisp; - ddispinfo_t *pDisp; - Vector v; - - // Calculate the total number of verts. - int nTotalVerts = 0; - int nTotalTris = 0; - for ( i=0; i < nummapdispinfo; i++ ) - { - nTotalVerts += NUM_DISP_POWER_VERTS( mapdispinfo[i].power ); - nTotalTris += NUM_DISP_POWER_TRIS( mapdispinfo[i].power ); - } - - // Clear the output arrays.. - g_dispinfo.Purge(); - g_dispinfo.SetSize( nummapdispinfo ); - g_DispVerts.SetSize( nTotalVerts ); - g_DispTris.SetSize( nTotalTris ); - - int iCurVert = 0; - int iCurTri = 0; - for( i = 0; i < nummapdispinfo; i++ ) - { - pDisp = &g_dispinfo[i]; - pMapDisp = &mapdispinfo[i]; - - CDispVert *pOutVerts = &g_DispVerts[iCurVert]; - CDispTri *pOutTris = &g_DispTris[iCurTri]; - - // Setup the vert pointers. - pDisp->m_iDispVertStart = iCurVert; - pDisp->m_iDispTriStart = iCurTri; - iCurVert += NUM_DISP_POWER_VERTS( pMapDisp->power ); - iCurTri += NUM_DISP_POWER_TRIS( pMapDisp->power ); - - // - // save power, minimum tesselation, and smoothing angle - // - pDisp->power = pMapDisp->power; - - // If the high bit is set - this is FLAGS! - pDisp->minTess = pMapDisp->flags; - pDisp->minTess |= 0x80000000; -// pDisp->minTess = pMapDisp->minTess; - pDisp->smoothingAngle = pMapDisp->smoothingAngle; - pDisp->m_iMapFace = (unsigned short)-2; - - // get surface contents - pDisp->contents = pMapDisp->face.contents; - - pDisp->startPosition = pMapDisp->startPosition; - // - // add up the vectorOffsets and displacements, save alphas (per vertex) - // - int size = ( ( ( 1 << pDisp->power ) + 1 ) * ( ( 1 << pDisp->power ) + 1 ) ); - for( int j = 0; j < size; j++ ) - { - VectorScale( pMapDisp->vectorDisps[j], pMapDisp->dispDists[j], v ); - VectorAdd( v, pMapDisp->vectorOffsets[j], v ); - - float dist = VectorLength( v ); - VectorNormalize( v ); - - VectorCopy( v, pOutVerts[j].m_vVector ); - pOutVerts[j].m_flDist = dist; - - pOutVerts[j].m_flAlpha = pMapDisp->alphaValues[j]; - } - - int nTriCount = ( (1 << (pDisp->power)) * (1 << (pDisp->power)) * 2 ); - for ( int iTri = 0; iTri< nTriCount; ++iTri ) - { - pOutTris[iTri].m_uiTags = pMapDisp->triTags[iTri]; - } - //=================================================================== - //=================================================================== - - // save the index for face data reference - pMapDisp->face.dispinfo = i; - } -} - - -void ExportCoreDispNeighborData( const CCoreDispInfo *pIn, ddispinfo_t *pOut ) -{ - for ( int i=0; i < 4; i++ ) - { - pOut->m_EdgeNeighbors[i] = *pIn->GetEdgeNeighbor( i ); - pOut->m_CornerNeighbors[i] = *pIn->GetCornerNeighbors( i ); - } -} - -void ExportNeighborData( CCoreDispInfo **ppListBase, ddispinfo_t *pBSPDispInfos, int listSize ) -{ - FindNeighboringDispSurfs( ppListBase, listSize ); - - // Export the neighbor data. - for ( int i=0; i < nummapdispinfo; i++ ) - { - ExportCoreDispNeighborData( g_CoreDispInfos[i], &pBSPDispInfos[i] ); - } -} - - -void ExportCoreDispAllowedVertList( const CCoreDispInfo *pIn, ddispinfo_t *pOut ) -{ - ErrorIfNot( - pIn->GetAllowedVerts().GetNumDWords() == sizeof( pOut->m_AllowedVerts ) / 4, - ("ExportCoreDispAllowedVertList: size mismatch") - ); - for ( int i=0; i < pIn->GetAllowedVerts().GetNumDWords(); i++ ) - pOut->m_AllowedVerts[i] = pIn->GetAllowedVerts().GetDWord( i ); -} - - -void ExportAllowedVertLists( CCoreDispInfo **ppListBase, ddispinfo_t *pBSPDispInfos, int listSize ) -{ - SetupAllowedVerts( ppListBase, listSize ); - - for ( int i=0; i < listSize; i++ ) - { - ExportCoreDispAllowedVertList( ppListBase[i], &pBSPDispInfos[i] ); - } -} - -bool FindEnclosingTri( - const Vector2D &vert, - CUtlVector &vertCoords, - CUtlVector &indices, - int *pStartVert, - float bcCoords[3] ) -{ - for ( int i=0; i < indices.Count(); i += 3 ) - { - GetBarycentricCoords2D( - vertCoords[indices[i+0]], - vertCoords[indices[i+1]], - vertCoords[indices[i+2]], - vert, - bcCoords ); - - if ( bcCoords[0] >= 0 && bcCoords[0] <= 1 && - bcCoords[1] >= 0 && bcCoords[1] <= 1 && - bcCoords[2] >= 0 && bcCoords[2] <= 1 ) - { - *pStartVert = i; - return true; - } - } - - return false; -} - -void SnapRemainingVertsToSurface( CCoreDispInfo *pCoreDisp, ddispinfo_t *pDispInfo ) -{ - // First, tesselate the displacement. - CUtlVector indices; - CVBSPTesselateHelper helper; - helper.m_pIndices = &indices; - helper.m_pActiveVerts = pCoreDisp->GetAllowedVerts().Base(); - helper.m_pPowerInfo = pCoreDisp->GetPowerInfo(); - ::TesselateDisplacement( &helper ); - - // Figure out which verts are actually referenced in the tesselation. - CUtlVector vertsTouched; - vertsTouched.SetSize( pCoreDisp->GetSize() ); - memset( vertsTouched.Base(), 0, sizeof( bool ) * vertsTouched.Count() ); - - for ( int i=0; i < indices.Count(); i++ ) - vertsTouched[ indices[i] ] = true; - - // Generate 2D floating point coordinates for each vertex. We use these to generate - // barycentric coordinates, and the scale doesn't matter. - CUtlVector vertCoords; - vertCoords.SetSize( pCoreDisp->GetSize() ); - for ( int y=0; y < pCoreDisp->GetHeight(); y++ ) - { - for ( int x=0; x < pCoreDisp->GetWidth(); x++ ) - vertCoords[y*pCoreDisp->GetWidth()+x].Init( x, y ); - } - - // Now, for each vert not touched, snap its position to the main surface. - for ( int y=0; y < pCoreDisp->GetHeight(); y++ ) - { - for ( int x=0; x < pCoreDisp->GetWidth(); x++ ) - { - int index = y * pCoreDisp->GetWidth() + x; - if ( !( vertsTouched[index] ) ) - { - float bcCoords[3]; - int iStartVert = -1; - if ( FindEnclosingTri( vertCoords[index], vertCoords, indices, &iStartVert, bcCoords ) ) - { - const Vector &A = pCoreDisp->GetVert( indices[iStartVert+0] ); - const Vector &B = pCoreDisp->GetVert( indices[iStartVert+1] ); - const Vector &C = pCoreDisp->GetVert( indices[iStartVert+2] ); - Vector vNewPos = A*bcCoords[0] + B*bcCoords[1] + C*bcCoords[2]; - - // This is kind of cheesy, but it gets the job done. Since the CDispVerts store the - // verts relative to some other offset, we'll just offset their position instead - // of setting it directly. - Vector vOffset = vNewPos - pCoreDisp->GetVert( index ); - - // Modify the mapfile vert. - CDispVert *pVert = &g_DispVerts[pDispInfo->m_iDispVertStart + index]; - pVert->m_vVector = (pVert->m_vVector * pVert->m_flDist) + vOffset; - pVert->m_flDist = 1; - - // Modify the CCoreDispInfo vert (although it probably won't be used later). - pCoreDisp->SetVert( index, vNewPos ); - } - else - { - // This shouldn't happen because it would mean that the triangulation that - // disp_tesselation.h produced was missing a chunk of the space that the - // displacement covers. - // It also could indicate a floating-point epsilon error.. check to see if - // FindEnclosingTri finds a triangle that -almost- encloses the vert. - Assert( false ); - } - } - } - } -} - -void SnapRemainingVertsToSurface( CCoreDispInfo **ppListBase, ddispinfo_t *pBSPDispInfos, int listSize ) -{ -//g_pPad = ScratchPad3D_Create(); - for ( int i=0; i < listSize; i++ ) - { - SnapRemainingVertsToSurface( ppListBase[i], &pBSPDispInfos[i] ); - } -} - -void EmitDispLMAlphaAndNeighbors() -{ - int i; - - Msg( "Finding displacement neighbors...\n" ); - - // Build the CCoreDispInfos. - CUtlVector faces; - - // Create the core dispinfos and init them for use as CDispUtilsHelpers. - for ( int iDisp = 0; iDisp < nummapdispinfo; ++iDisp ) - { - CCoreDispInfo *pDisp = new CCoreDispInfo; - if ( !pDisp ) - { - g_CoreDispInfos.Purge(); - return; - } - - int nIndex = g_CoreDispInfos.AddToTail(); - pDisp->SetListIndex( nIndex ); - g_CoreDispInfos[nIndex] = pDisp; - } - - for ( i=0; i < nummapdispinfo; i++ ) - { - g_CoreDispInfos[i]->SetDispUtilsHelperInfo( g_CoreDispInfos.Base(), nummapdispinfo ); - } - - faces.SetSize( nummapdispinfo ); - - int nMemSize = texinfo.Count() * sizeof(int); - int *pSwappedTexInfos = (int*)stackalloc( nMemSize ); - memset( pSwappedTexInfos, 0xFF, nMemSize ); - for( i = 0; i < numfaces; i++ ) - { - dface_t *pFace = &dfaces[i]; - - if( pFace->dispinfo == -1 ) - continue; - - mapdispinfo_t *pMapDisp = &mapdispinfo[pFace->dispinfo]; - - // Set the displacement's face index. - ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; - pDisp->m_iMapFace = i; - - // Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates. - CCoreDispInfo *pCoreDispInfo = g_CoreDispInfos[pFace->dispinfo]; - DispMapToCoreDispInfo( pMapDisp, pCoreDispInfo, pFace, pSwappedTexInfos ); - - faces[pFace->dispinfo] = pFace; - } - stackfree( pSwappedTexInfos ); - - // Generate and export neighbor data. - ExportNeighborData( g_CoreDispInfos.Base(), g_dispinfo.Base(), nummapdispinfo ); - - // Generate and export the active vert lists. - ExportAllowedVertLists( g_CoreDispInfos.Base(), g_dispinfo.Base(), nummapdispinfo ); - - - // Now that we know which vertices are actually going to be around, snap the ones that won't - // be around onto the slightly-reduced mesh. This is so the engine's ray test code and - // overlay code works right. - SnapRemainingVertsToSurface( g_CoreDispInfos.Base(), g_dispinfo.Base(), nummapdispinfo ); - - Msg( "Finding lightmap sample positions...\n" ); - for ( i=0; i < nummapdispinfo; i++ ) - { - dface_t *pFace = faces[i]; - ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; - CCoreDispInfo *pCoreDispInfo = g_CoreDispInfos[i]; - - pDisp->m_iLightmapSamplePositionStart = g_DispLightmapSamplePositions.Count(); - - CalculateLightmapSamplePositions( pCoreDispInfo, pFace, g_DispLightmapSamplePositions ); - } - - StartPacifier( "Displacement Alpha : "); - - // Build lightmap alphas. - int dispCount = 0; // How many we've processed. - for( i = 0; i < nummapdispinfo; i++ ) - { - dface_t *pFace = faces[i]; - - Assert( pFace->dispinfo == i ); - ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; - - // Allocate space for the alpha values. - pDisp->m_iLightmapAlphaStart = 0; // not used anymore - - ++dispCount; - } - - EndPacifier(); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void DispGetFaceInfo( mapbrush_t *pBrush ) -{ - int i; - side_t *pSide; - - // we don't support displacement on entities at the moment!! - if( pBrush->entitynum != 0 ) - { - char* pszEntityName = ValueForKey( &g_LoadingMap->entities[pBrush->entitynum], "classname" ); - Error( "Error: displacement found on a(n) %s entity - not supported (entity %d, brush %d)\n", pszEntityName, pBrush->entitynum, pBrush->brushnum ); - } - - for( i = 0; i < pBrush->numsides; i++ ) - { - pSide = &pBrush->original_sides[i]; - if( pSide->pMapDisp ) - { - // error checking!! - if( pSide->winding->numpoints != 4 ) - Error( "Trying to create a non-quad displacement! (entity %d, brush %d)\n", pBrush->entitynum, pBrush->brushnum ); - pSide->pMapDisp->face.originalface = pSide; - pSide->pMapDisp->face.texinfo = pSide->texinfo; - pSide->pMapDisp->face.dispinfo = -1; - pSide->pMapDisp->face.planenum = pSide->planenum; - pSide->pMapDisp->face.numpoints = pSide->winding->numpoints; - pSide->pMapDisp->face.w = CopyWinding( pSide->winding ); - pSide->pMapDisp->face.contents = pBrush->contents; - - pSide->pMapDisp->face.merged = FALSE; - pSide->pMapDisp->face.split[0] = FALSE; - pSide->pMapDisp->face.split[1] = FALSE; - - pSide->pMapDisp->entitynum = pBrush->entitynum; - pSide->pMapDisp->brushSideID = pSide->id; - } - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool HasDispInfo( mapbrush_t *pBrush ) -{ - int i; - side_t *pSide; - - for( i = 0; i < pBrush->numsides; i++ ) - { - pSide = &pBrush->original_sides[i]; - if( pSide->pMapDisp ) - return true; - } - - return false; -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#include "disp_vbsp.h" +#include "tier0/dbg.h" +#include "vbsp.h" +#include "mstristrip.h" +#include "writebsp.h" +#include "pacifier.h" +#include "disp_ivp.h" +#include "builddisp.h" +#include "mathlib/vector.h" + +// map displacement info -- runs parallel to the dispinfos struct +int nummapdispinfo = 0; +mapdispinfo_t mapdispinfo[MAX_MAP_DISPINFO]; + +CUtlVector g_CoreDispInfos; + +//----------------------------------------------------------------------------- +// Computes the bounds for a disp info +//----------------------------------------------------------------------------- +void ComputeDispInfoBounds( int dispinfo, Vector& mins, Vector& maxs ) +{ + CDispBox box; + + // Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates. + mapdispinfo_t *pMapDisp = &mapdispinfo[dispinfo]; + + CCoreDispInfo coreDispInfo; + DispMapToCoreDispInfo( pMapDisp, &coreDispInfo, NULL, NULL ); + + GetDispBox( &coreDispInfo, box ); + mins = box.m_Min; + maxs = box.m_Max; +} + +// Gets the barycentric coordinates of the position on the triangle where the lightmap +// coordinates are equal to lmCoords. This always generates the coordinates but it +// returns false if the point containing them does not lie inside the triangle. +bool GetBarycentricCoordsFromLightmapCoords( Vector2D tri[3], Vector2D const &lmCoords, float bcCoords[3] ) +{ + GetBarycentricCoords2D( tri[0], tri[1], tri[2], lmCoords, bcCoords ); + + return + (bcCoords[0] >= 0.0f && bcCoords[0] <= 1.0f) && + (bcCoords[1] >= 0.0f && bcCoords[1] <= 1.0f) && + (bcCoords[2] >= 0.0f && bcCoords[2] <= 1.0f); +} + + +bool FindTriIndexMapByUV( CCoreDispInfo *pCoreDisp, Vector2D const &lmCoords, + int &iTriangle, float flBarycentric[3] ) +{ + const CPowerInfo *pPowerInfo = GetPowerInfo( pCoreDisp->GetPower() ); + + // Search all the triangles.. + int nTriCount= pCoreDisp->GetTriCount(); + for ( int iTri = 0; iTri < nTriCount; ++iTri ) + { + unsigned short iVerts[3]; +// pCoreDisp->GetTriIndices( iTri, iVerts[0], iVerts[1], iVerts[2] ); + CTriInfo *pTri = &pPowerInfo->m_pTriInfos[iTri]; + iVerts[0] = pTri->m_Indices[0]; + iVerts[1] = pTri->m_Indices[1]; + iVerts[2] = pTri->m_Indices[2]; + + // Get this triangle's UVs. + Vector2D vecUV[3]; + for ( int iCoord = 0; iCoord < 3; ++iCoord ) + { + pCoreDisp->GetLuxelCoord( 0, iVerts[iCoord], vecUV[iCoord] ); + } + + // See if the passed-in UVs are in this triangle's UVs. + if( GetBarycentricCoordsFromLightmapCoords( vecUV, lmCoords, flBarycentric ) ) + { + iTriangle = iTri; + return true; + } + } + + return false; +} + + +void CalculateLightmapSamplePositions( CCoreDispInfo *pCoreDispInfo, const dface_t *pFace, CUtlVector &out ) +{ + int width = pFace->m_LightmapTextureSizeInLuxels[0] + 1; + int height = pFace->m_LightmapTextureSizeInLuxels[1] + 1; + + // For each lightmap sample, find the triangle it sits in. + Vector2D lmCoords; + for( int y=0; y < height; y++ ) + { + lmCoords.y = y + 0.5f; + + for( int x=0; x < width; x++ ) + { + lmCoords.x = x + 0.5f; + + float flBarycentric[3]; + int iTri; + + if( FindTriIndexMapByUV( pCoreDispInfo, lmCoords, iTri, flBarycentric ) ) + { + if( iTri < 255 ) + { + out.AddToTail( iTri ); + } + else + { + out.AddToTail( 255 ); + out.AddToTail( iTri - 255 ); + } + + out.AddToTail( (unsigned char)( flBarycentric[0] * 255.9f ) ); + out.AddToTail( (unsigned char)( flBarycentric[1] * 255.9f ) ); + out.AddToTail( (unsigned char)( flBarycentric[2] * 255.9f ) ); + } + else + { + out.AddToTail( 0 ); + out.AddToTail( 0 ); + out.AddToTail( 0 ); + out.AddToTail( 0 ); + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int GetDispInfoEntityNum( mapdispinfo_t *pDisp ) +{ + return pDisp->entitynum; +} + +// Setup a CCoreDispInfo given a mapdispinfo_t. +// If pFace is non-NULL, then lightmap texture coordinates will be generated. +void DispMapToCoreDispInfo( mapdispinfo_t *pMapDisp, CCoreDispInfo *pCoreDispInfo, dface_t *pFace, int *pSwappedTexInfos ) +{ + winding_t *pWinding = pMapDisp->face.originalface->winding; + + Assert( pWinding->numpoints == 4 ); + + // + // set initial surface data + // + CCoreDispSurface *pSurf = pCoreDispInfo->GetSurface(); + + texinfo_t *pTexInfo = &texinfo[ pMapDisp->face.texinfo ]; + Assert( pTexInfo != NULL ); + + // init material contents + pMapDisp->contents = pMapDisp->face.contents; + if (!(pMapDisp->contents & (ALL_VISIBLE_CONTENTS | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) ) + { + pMapDisp->contents |= CONTENTS_SOLID; + } + + pSurf->SetContents( pMapDisp->contents ); + + // Calculate the lightmap coordinates. + Vector2D tCoords[4] = {Vector2D(0,0),Vector2D(0,1),Vector2D(1,0),Vector2D(1,1)}; + if( pFace ) + { + Assert( pFace->numedges == 4 ); + + Vector pt[4]; + for( int i=0; i < 4; i++ ) + pt[i] = pWinding->p[i]; + + int zeroOffset[2] = {0,0}; + CalcTextureCoordsAtPoints( + pTexInfo->textureVecsTexelsPerWorldUnits, + zeroOffset, + pt, + 4, + tCoords ); + } + + // + // set face point data ... + // + pSurf->SetPointCount( 4 ); + for( int i = 0; i < 4; i++ ) + { + // position + pSurf->SetPoint( i, pWinding->p[i] ); + pSurf->SetTexCoord( i, tCoords[i] ); + } + + // reset surface given start info + pSurf->SetPointStart( pMapDisp->startPosition ); + pSurf->FindSurfPointStartIndex(); + pSurf->AdjustSurfPointData(); + + // Set the luxel coordinates on the base displacement surface. + Vector vecTmp( pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][0], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][1], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); + int nLuxelsPerWorldUnit = static_cast( 1.0f / VectorLength( vecTmp ) ); + Vector vecU( pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][0], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][1], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); + Vector vecV( pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][0], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][1], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][2] ); + bool bSwap = pSurf->CalcLuxelCoords( nLuxelsPerWorldUnit, false, vecU, vecV ); + + // Set the face m_LightmapExtents + if ( pFace ) + { + pFace->m_LightmapTextureSizeInLuxels[0] = pSurf->GetLuxelU(); + pFace->m_LightmapTextureSizeInLuxels[1] = pSurf->GetLuxelV(); + if ( bSwap ) + { + if ( pSwappedTexInfos[ pMapDisp->face.texinfo ] < 0 ) + { + // Create a new texinfo to hold the swapped data. + // We must do this because other surfaces may want the non-swapped data + // This fixes a lighting bug in d2_prison_08 where many non-displacement surfaces + // were pitch black, in addition to bugs in other maps I bet. + + // NOTE: Copy here because adding a texinfo could realloc. + texinfo_t temp = *pTexInfo; + memcpy( temp.lightmapVecsLuxelsPerWorldUnits[0], pTexInfo->lightmapVecsLuxelsPerWorldUnits[1], 4 * sizeof(float) ); + memcpy( temp.lightmapVecsLuxelsPerWorldUnits[1], pTexInfo->lightmapVecsLuxelsPerWorldUnits[0], 4 * sizeof(float) ); + temp.lightmapVecsLuxelsPerWorldUnits[1][0] *= -1.0f; + temp.lightmapVecsLuxelsPerWorldUnits[1][1] *= -1.0f; + temp.lightmapVecsLuxelsPerWorldUnits[1][2] *= -1.0f; + temp.lightmapVecsLuxelsPerWorldUnits[1][3] *= -1.0f; + pSwappedTexInfos[ pMapDisp->face.texinfo ] = texinfo.AddToTail( temp ); + } + pMapDisp->face.texinfo = pSwappedTexInfos[ pMapDisp->face.texinfo ]; + } + + // NOTE: This is here to help future-proof code, since there are codepaths where + // pTexInfo can be made invalid (texinfo.AddToTail above). + pTexInfo = NULL; + } + + // Setup the displacement vectors and offsets. + int size = ( ( ( 1 << pMapDisp->power ) + 1 ) * ( ( 1 << pMapDisp->power ) + 1 ) ); + + Vector vectorDisps[2048]; + float dispDists[2048]; + Assert( size < sizeof(vectorDisps)/sizeof(vectorDisps[0]) ); + + for( int j = 0; j < size; j++ ) + { + Vector v; + float dist; + + VectorScale( pMapDisp->vectorDisps[j], pMapDisp->dispDists[j], v ); + VectorAdd( v, pMapDisp->vectorOffsets[j], v ); + + dist = VectorLength( v ); + VectorNormalize( v ); + + vectorDisps[j] = v; + dispDists[j] = dist; + } + + + // Use CCoreDispInfo to setup the actual vertex positions. + pCoreDispInfo->InitDispInfo( pMapDisp->power, pMapDisp->minTess, pMapDisp->smoothingAngle, + pMapDisp->alphaValues, vectorDisps, dispDists ); + pCoreDispInfo->Create(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void EmitInitialDispInfos( void ) +{ + int i; + mapdispinfo_t *pMapDisp; + ddispinfo_t *pDisp; + Vector v; + + // Calculate the total number of verts. + int nTotalVerts = 0; + int nTotalTris = 0; + for ( i=0; i < nummapdispinfo; i++ ) + { + nTotalVerts += NUM_DISP_POWER_VERTS( mapdispinfo[i].power ); + nTotalTris += NUM_DISP_POWER_TRIS( mapdispinfo[i].power ); + } + + // Clear the output arrays.. + g_dispinfo.Purge(); + g_dispinfo.SetSize( nummapdispinfo ); + g_DispVerts.SetSize( nTotalVerts ); + g_DispTris.SetSize( nTotalTris ); + + int iCurVert = 0; + int iCurTri = 0; + for( i = 0; i < nummapdispinfo; i++ ) + { + pDisp = &g_dispinfo[i]; + pMapDisp = &mapdispinfo[i]; + + CDispVert *pOutVerts = &g_DispVerts[iCurVert]; + CDispTri *pOutTris = &g_DispTris[iCurTri]; + + // Setup the vert pointers. + pDisp->m_iDispVertStart = iCurVert; + pDisp->m_iDispTriStart = iCurTri; + iCurVert += NUM_DISP_POWER_VERTS( pMapDisp->power ); + iCurTri += NUM_DISP_POWER_TRIS( pMapDisp->power ); + + // + // save power, minimum tesselation, and smoothing angle + // + pDisp->power = pMapDisp->power; + + // If the high bit is set - this is FLAGS! + pDisp->minTess = pMapDisp->flags; + pDisp->minTess |= 0x80000000; +// pDisp->minTess = pMapDisp->minTess; + pDisp->smoothingAngle = pMapDisp->smoothingAngle; + pDisp->m_iMapFace = (unsigned short)-2; + + // get surface contents + pDisp->contents = pMapDisp->face.contents; + + pDisp->startPosition = pMapDisp->startPosition; + // + // add up the vectorOffsets and displacements, save alphas (per vertex) + // + int size = ( ( ( 1 << pDisp->power ) + 1 ) * ( ( 1 << pDisp->power ) + 1 ) ); + for( int j = 0; j < size; j++ ) + { + VectorScale( pMapDisp->vectorDisps[j], pMapDisp->dispDists[j], v ); + VectorAdd( v, pMapDisp->vectorOffsets[j], v ); + + float dist = VectorLength( v ); + VectorNormalize( v ); + + VectorCopy( v, pOutVerts[j].m_vVector ); + pOutVerts[j].m_flDist = dist; + + pOutVerts[j].m_flAlpha = pMapDisp->alphaValues[j]; + } + + int nTriCount = ( (1 << (pDisp->power)) * (1 << (pDisp->power)) * 2 ); + for ( int iTri = 0; iTri< nTriCount; ++iTri ) + { + pOutTris[iTri].m_uiTags = pMapDisp->triTags[iTri]; + } + //=================================================================== + //=================================================================== + + // save the index for face data reference + pMapDisp->face.dispinfo = i; + } +} + + +void ExportCoreDispNeighborData( const CCoreDispInfo *pIn, ddispinfo_t *pOut ) +{ + for ( int i=0; i < 4; i++ ) + { + pOut->m_EdgeNeighbors[i] = *pIn->GetEdgeNeighbor( i ); + pOut->m_CornerNeighbors[i] = *pIn->GetCornerNeighbors( i ); + } +} + +void ExportNeighborData( CCoreDispInfo **ppListBase, ddispinfo_t *pBSPDispInfos, int listSize ) +{ + FindNeighboringDispSurfs( ppListBase, listSize ); + + // Export the neighbor data. + for ( int i=0; i < nummapdispinfo; i++ ) + { + ExportCoreDispNeighborData( g_CoreDispInfos[i], &pBSPDispInfos[i] ); + } +} + + +void ExportCoreDispAllowedVertList( const CCoreDispInfo *pIn, ddispinfo_t *pOut ) +{ + ErrorIfNot( + pIn->GetAllowedVerts().GetNumDWords() == sizeof( pOut->m_AllowedVerts ) / 4, + ("ExportCoreDispAllowedVertList: size mismatch") + ); + for ( int i=0; i < pIn->GetAllowedVerts().GetNumDWords(); i++ ) + pOut->m_AllowedVerts[i] = pIn->GetAllowedVerts().GetDWord( i ); +} + + +void ExportAllowedVertLists( CCoreDispInfo **ppListBase, ddispinfo_t *pBSPDispInfos, int listSize ) +{ + SetupAllowedVerts( ppListBase, listSize ); + + for ( int i=0; i < listSize; i++ ) + { + ExportCoreDispAllowedVertList( ppListBase[i], &pBSPDispInfos[i] ); + } +} + +bool FindEnclosingTri( + const Vector2D &vert, + CUtlVector &vertCoords, + CUtlVector &indices, + int *pStartVert, + float bcCoords[3] ) +{ + for ( int i=0; i < indices.Count(); i += 3 ) + { + GetBarycentricCoords2D( + vertCoords[indices[i+0]], + vertCoords[indices[i+1]], + vertCoords[indices[i+2]], + vert, + bcCoords ); + + if ( bcCoords[0] >= 0 && bcCoords[0] <= 1 && + bcCoords[1] >= 0 && bcCoords[1] <= 1 && + bcCoords[2] >= 0 && bcCoords[2] <= 1 ) + { + *pStartVert = i; + return true; + } + } + + return false; +} + +void SnapRemainingVertsToSurface( CCoreDispInfo *pCoreDisp, ddispinfo_t *pDispInfo ) +{ + // First, tesselate the displacement. + CUtlVector indices; + CVBSPTesselateHelper helper; + helper.m_pIndices = &indices; + helper.m_pActiveVerts = pCoreDisp->GetAllowedVerts().Base(); + helper.m_pPowerInfo = pCoreDisp->GetPowerInfo(); + ::TesselateDisplacement( &helper ); + + // Figure out which verts are actually referenced in the tesselation. + CUtlVector vertsTouched; + vertsTouched.SetSize( pCoreDisp->GetSize() ); + memset( vertsTouched.Base(), 0, sizeof( bool ) * vertsTouched.Count() ); + + for ( int i=0; i < indices.Count(); i++ ) + vertsTouched[ indices[i] ] = true; + + // Generate 2D floating point coordinates for each vertex. We use these to generate + // barycentric coordinates, and the scale doesn't matter. + CUtlVector vertCoords; + vertCoords.SetSize( pCoreDisp->GetSize() ); + for ( int y=0; y < pCoreDisp->GetHeight(); y++ ) + { + for ( int x=0; x < pCoreDisp->GetWidth(); x++ ) + vertCoords[y*pCoreDisp->GetWidth()+x].Init( x, y ); + } + + // Now, for each vert not touched, snap its position to the main surface. + for ( int y=0; y < pCoreDisp->GetHeight(); y++ ) + { + for ( int x=0; x < pCoreDisp->GetWidth(); x++ ) + { + int index = y * pCoreDisp->GetWidth() + x; + if ( !( vertsTouched[index] ) ) + { + float bcCoords[3]; + int iStartVert = -1; + if ( FindEnclosingTri( vertCoords[index], vertCoords, indices, &iStartVert, bcCoords ) ) + { + const Vector &A = pCoreDisp->GetVert( indices[iStartVert+0] ); + const Vector &B = pCoreDisp->GetVert( indices[iStartVert+1] ); + const Vector &C = pCoreDisp->GetVert( indices[iStartVert+2] ); + Vector vNewPos = A*bcCoords[0] + B*bcCoords[1] + C*bcCoords[2]; + + // This is kind of cheesy, but it gets the job done. Since the CDispVerts store the + // verts relative to some other offset, we'll just offset their position instead + // of setting it directly. + Vector vOffset = vNewPos - pCoreDisp->GetVert( index ); + + // Modify the mapfile vert. + CDispVert *pVert = &g_DispVerts[pDispInfo->m_iDispVertStart + index]; + pVert->m_vVector = (pVert->m_vVector * pVert->m_flDist) + vOffset; + pVert->m_flDist = 1; + + // Modify the CCoreDispInfo vert (although it probably won't be used later). + pCoreDisp->SetVert( index, vNewPos ); + } + else + { + // This shouldn't happen because it would mean that the triangulation that + // disp_tesselation.h produced was missing a chunk of the space that the + // displacement covers. + // It also could indicate a floating-point epsilon error.. check to see if + // FindEnclosingTri finds a triangle that -almost- encloses the vert. + Assert( false ); + } + } + } + } +} + +void SnapRemainingVertsToSurface( CCoreDispInfo **ppListBase, ddispinfo_t *pBSPDispInfos, int listSize ) +{ +//g_pPad = ScratchPad3D_Create(); + for ( int i=0; i < listSize; i++ ) + { + SnapRemainingVertsToSurface( ppListBase[i], &pBSPDispInfos[i] ); + } +} + +void EmitDispLMAlphaAndNeighbors() +{ + int i; + + Msg( "Finding displacement neighbors...\n" ); + + // Build the CCoreDispInfos. + CUtlVector faces; + + // Create the core dispinfos and init them for use as CDispUtilsHelpers. + for ( int iDisp = 0; iDisp < nummapdispinfo; ++iDisp ) + { + CCoreDispInfo *pDisp = new CCoreDispInfo; + if ( !pDisp ) + { + g_CoreDispInfos.Purge(); + return; + } + + int nIndex = g_CoreDispInfos.AddToTail(); + pDisp->SetListIndex( nIndex ); + g_CoreDispInfos[nIndex] = pDisp; + } + + for ( i=0; i < nummapdispinfo; i++ ) + { + g_CoreDispInfos[i]->SetDispUtilsHelperInfo( g_CoreDispInfos.Base(), nummapdispinfo ); + } + + faces.SetSize( nummapdispinfo ); + + int nMemSize = texinfo.Count() * sizeof(int); + int *pSwappedTexInfos = (int*)stackalloc( nMemSize ); + memset( pSwappedTexInfos, 0xFF, nMemSize ); + for( i = 0; i < numfaces; i++ ) + { + dface_t *pFace = &dfaces[i]; + + if( pFace->dispinfo == -1 ) + continue; + + mapdispinfo_t *pMapDisp = &mapdispinfo[pFace->dispinfo]; + + // Set the displacement's face index. + ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; + pDisp->m_iMapFace = i; + + // Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates. + CCoreDispInfo *pCoreDispInfo = g_CoreDispInfos[pFace->dispinfo]; + DispMapToCoreDispInfo( pMapDisp, pCoreDispInfo, pFace, pSwappedTexInfos ); + + faces[pFace->dispinfo] = pFace; + } + stackfree( pSwappedTexInfos ); + + // Generate and export neighbor data. + ExportNeighborData( g_CoreDispInfos.Base(), g_dispinfo.Base(), nummapdispinfo ); + + // Generate and export the active vert lists. + ExportAllowedVertLists( g_CoreDispInfos.Base(), g_dispinfo.Base(), nummapdispinfo ); + + + // Now that we know which vertices are actually going to be around, snap the ones that won't + // be around onto the slightly-reduced mesh. This is so the engine's ray test code and + // overlay code works right. + SnapRemainingVertsToSurface( g_CoreDispInfos.Base(), g_dispinfo.Base(), nummapdispinfo ); + + Msg( "Finding lightmap sample positions...\n" ); + for ( i=0; i < nummapdispinfo; i++ ) + { + dface_t *pFace = faces[i]; + ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; + CCoreDispInfo *pCoreDispInfo = g_CoreDispInfos[i]; + + pDisp->m_iLightmapSamplePositionStart = g_DispLightmapSamplePositions.Count(); + + CalculateLightmapSamplePositions( pCoreDispInfo, pFace, g_DispLightmapSamplePositions ); + } + + StartPacifier( "Displacement Alpha : "); + + // Build lightmap alphas. + int dispCount = 0; // How many we've processed. + for( i = 0; i < nummapdispinfo; i++ ) + { + dface_t *pFace = faces[i]; + + Assert( pFace->dispinfo == i ); + ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; + + // Allocate space for the alpha values. + pDisp->m_iLightmapAlphaStart = 0; // not used anymore + + ++dispCount; + } + + EndPacifier(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void DispGetFaceInfo( mapbrush_t *pBrush ) +{ + int i; + side_t *pSide; + + // we don't support displacement on entities at the moment!! + if( pBrush->entitynum != 0 ) + { + char* pszEntityName = ValueForKey( &g_LoadingMap->entities[pBrush->entitynum], "classname" ); + Error( "Error: displacement found on a(n) %s entity - not supported (entity %d, brush %d)\n", pszEntityName, pBrush->entitynum, pBrush->brushnum ); + } + + for( i = 0; i < pBrush->numsides; i++ ) + { + pSide = &pBrush->original_sides[i]; + if( pSide->pMapDisp ) + { + // error checking!! + if( pSide->winding->numpoints != 4 ) + Error( "Trying to create a non-quad displacement! (entity %d, brush %d)\n", pBrush->entitynum, pBrush->brushnum ); + pSide->pMapDisp->face.originalface = pSide; + pSide->pMapDisp->face.texinfo = pSide->texinfo; + pSide->pMapDisp->face.dispinfo = -1; + pSide->pMapDisp->face.planenum = pSide->planenum; + pSide->pMapDisp->face.numpoints = pSide->winding->numpoints; + pSide->pMapDisp->face.w = CopyWinding( pSide->winding ); + pSide->pMapDisp->face.contents = pBrush->contents; + + pSide->pMapDisp->face.merged = FALSE; + pSide->pMapDisp->face.split[0] = FALSE; + pSide->pMapDisp->face.split[1] = FALSE; + + pSide->pMapDisp->entitynum = pBrush->entitynum; + pSide->pMapDisp->brushSideID = pSide->id; + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool HasDispInfo( mapbrush_t *pBrush ) +{ + int i; + side_t *pSide; + + for( i = 0; i < pBrush->numsides; i++ ) + { + pSide = &pBrush->original_sides[i]; + if( pSide->pMapDisp ) + return true; + } + + return false; +} diff --git a/mp/src/utils/vbsp/disp_vbsp.h b/mp/src/utils/vbsp/disp_vbsp.h index 1c26775e..5af77601 100644 --- a/mp/src/utils/vbsp/disp_vbsp.h +++ b/mp/src/utils/vbsp/disp_vbsp.h @@ -1,46 +1,46 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef VBSP_DISPINFO_H -#define VBSP_DISPINFO_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "vbsp.h" - - - -class CCoreDispInfo; - - -extern CUtlVector g_CoreDispInfos; - - -// Setup initial entries in g_dispinfo with some of the vertex data from the mapdisps. -void EmitInitialDispInfos(); - -// Resample vertex alpha into lightmap alpha for displacement surfaces so LOD popping artifacts are -// less noticeable on the mid-to-high end. -// -// Also builds neighbor data. -void EmitDispLMAlphaAndNeighbors(); - -// Setup a CCoreDispInfo given a mapdispinfo_t. -// If pFace is non-NULL, then lightmap texture coordinates will be generated. -void DispMapToCoreDispInfo( mapdispinfo_t *pMapDisp, - CCoreDispInfo *pCoreDispInfo, dface_t *pFace, int *pSwappedTexInfos ); - - -void DispGetFaceInfo( mapbrush_t *pBrush ); -bool HasDispInfo( mapbrush_t *pBrush ); - -// Computes the bounds for a disp info -void ComputeDispInfoBounds( int dispinfo, Vector& mins, Vector& maxs ); - -#endif // VBSP_DISPINFO_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VBSP_DISPINFO_H +#define VBSP_DISPINFO_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "vbsp.h" + + + +class CCoreDispInfo; + + +extern CUtlVector g_CoreDispInfos; + + +// Setup initial entries in g_dispinfo with some of the vertex data from the mapdisps. +void EmitInitialDispInfos(); + +// Resample vertex alpha into lightmap alpha for displacement surfaces so LOD popping artifacts are +// less noticeable on the mid-to-high end. +// +// Also builds neighbor data. +void EmitDispLMAlphaAndNeighbors(); + +// Setup a CCoreDispInfo given a mapdispinfo_t. +// If pFace is non-NULL, then lightmap texture coordinates will be generated. +void DispMapToCoreDispInfo( mapdispinfo_t *pMapDisp, + CCoreDispInfo *pCoreDispInfo, dface_t *pFace, int *pSwappedTexInfos ); + + +void DispGetFaceInfo( mapbrush_t *pBrush ); +bool HasDispInfo( mapbrush_t *pBrush ); + +// Computes the bounds for a disp info +void ComputeDispInfoBounds( int dispinfo, Vector& mins, Vector& maxs ); + +#endif // VBSP_DISPINFO_H diff --git a/mp/src/utils/vbsp/faces.cpp b/mp/src/utils/vbsp/faces.cpp index 13ed9d61..f6ec3ee3 100644 --- a/mp/src/utils/vbsp/faces.cpp +++ b/mp/src/utils/vbsp/faces.cpp @@ -1,1810 +1,1810 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// faces.c - -#include "vbsp.h" -#include "utlvector.h" -#include "utilmatlib.h" -#include -#include "mstristrip.h" -#include "tier1/strtools.h" -#include "materialpatch.h" -/* - - some faces will be removed before saving, but still form nodes: - - the insides of sky volumes - meeting planes of different water current volumes - -*/ - -// undefine for dumb linear searches -#define USE_HASHING - -#define INTEGRAL_EPSILON 0.01 -#define POINT_EPSILON 0.1 -#define OFF_EPSILON 0.25 - -int c_merge; -int c_subdivide; - -int c_totalverts; -int c_uniqueverts; -int c_degenerate; -int c_tjunctions; -int c_faceoverflows; -int c_facecollapse; -int c_badstartverts; - -#define MAX_SUPERVERTS 512 -int superverts[MAX_SUPERVERTS]; -int numsuperverts; - -face_t *edgefaces[MAX_MAP_EDGES][2]; -int firstmodeledge = 1; -int firstmodelface; - -int c_tryedges; - -Vector edge_dir; -Vector edge_start; -vec_t edge_len; - -int num_edge_verts; -int edge_verts[MAX_MAP_VERTS]; - - -float g_maxLightmapDimension = 32; - - -face_t *NewFaceFromFace (face_t *f); - -// Used to speed up GetEdge2(). Holds a list of edges connected to each vert. -CUtlVector g_VertEdgeList[MAX_MAP_VERTS]; - - -//=========================================================================== - -typedef struct hashvert_s -{ - struct hashvert_s *next; - int num; -} hashvert_t; - -#define HASH_BITS 7 -#define HASH_SIZE (COORD_EXTENT>>HASH_BITS) - - -int vertexchain[MAX_MAP_VERTS]; // the next vertex in a hash chain -int hashverts[HASH_SIZE*HASH_SIZE]; // a vertex number, or 0 for no verts - -//face_t *edgefaces[MAX_MAP_EDGES][2]; - -//============================================================================ - - -unsigned HashVec (Vector& vec) -{ - int x, y; - - x = (MAX_COORD_INTEGER + (int)(vec[0]+0.5)) >> HASH_BITS; - y = (MAX_COORD_INTEGER + (int)(vec[1]+0.5)) >> HASH_BITS; - - if ( x < 0 || x >= HASH_SIZE || y < 0 || y >= HASH_SIZE ) - Error ("HashVec: point outside valid range"); - - return y*HASH_SIZE + x; -} - -#ifdef USE_HASHING -/* -============= -GetVertex - -Uses hashing -============= -*/ -int GetVertexnum (Vector& in) -{ - int h; - int i; - Vector vert; - int vnum; - - c_totalverts++; - - for (i=0 ; i<3 ; i++) - { - if ( fabs(in[i] - (int)(in[i]+0.5)) < INTEGRAL_EPSILON) - vert[i] = (int)(in[i]+0.5); - else - vert[i] = in[i]; - } - - h = HashVec (vert); - - for (vnum=hashverts[h] ; vnum ; vnum=vertexchain[vnum]) - { - Vector& p = dvertexes[vnum].point; - if ( fabs(p[0]-vert[0]) MAX_COORD_INTEGER) - Error ("GetVertexnum: outside world, vertex %.1f %.1f %.1f", v.x, v.y, v.z); - } - - // search for an existing vertex match - for (i=0, dv=dvertexes ; ipoint[j]; - if ( d > POINT_EPSILON || d < -POINT_EPSILON) - break; - } - if (j == 3) - return i; // a match - } - - // new point - if (numvertexes == MAX_MAP_VERTS) - Error ("Too many unique verts, max = %d (map has too much brush geometry)\n", MAX_MAP_VERTS); - VectorCopy (v, dv->point); - numvertexes++; - c_uniqueverts++; - - return numvertexes-1; -} -#endif - - -/* -================== -FaceFromSuperverts - -The faces vertexes have beeb added to the superverts[] array, -and there may be more there than can be held in a face (MAXEDGES). - -If less, the faces vertexnums[] will be filled in, otherwise -face will reference a tree of split[] faces until all of the -vertexnums can be added. - -superverts[base] will become face->vertexnums[0], and the others -will be circularly filled in. -================== -*/ -void FaceFromSuperverts (face_t **pListHead, face_t *f, int base) -{ - face_t *newf; - int remaining; - int i; - - remaining = numsuperverts; - while (remaining > MAXEDGES) - { // must split into two faces, because of vertex overload - c_faceoverflows++; - - newf = NewFaceFromFace (f); - f->split[0] = newf; - - newf->next = *pListHead; - *pListHead = newf; - - newf->numpoints = MAXEDGES; - for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; - - f->split[1] = NewFaceFromFace (f); - f = f->split[1]; - - f->next = *pListHead; - *pListHead = f; - - remaining -= (MAXEDGES-2); - base = (base+MAXEDGES-1)%numsuperverts; - } - - // copy the vertexes back to the face - f->numpoints = remaining; - for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; -} - - -/* -================== -EmitFaceVertexes -================== -*/ -void EmitFaceVertexes (face_t **pListHead, face_t *f) -{ - winding_t *w; - int i; - - if (f->merged || f->split[0] || f->split[1]) - return; - - w = f->w; - for (i=0 ; inumpoints ; i++) - { - if (noweld) - { // make every point unique - if (numvertexes == MAX_MAP_VERTS) - Error ("Too many unique verts, max = %d (map has too much brush geometry)\n", MAX_MAP_VERTS); - superverts[i] = numvertexes; - VectorCopy (w->p[i], dvertexes[numvertexes].point); - numvertexes++; - c_uniqueverts++; - c_totalverts++; - } - else - superverts[i] = GetVertexnum (w->p[i]); - } - numsuperverts = w->numpoints; - - // this may fragment the face if > MAXEDGES - FaceFromSuperverts (pListHead, f, 0); -} - -/* -================== -EmitNodeFaceVertexes_r -================== -*/ -void EmitNodeFaceVertexes_r (node_t *node) -{ - int i; - face_t *f; - - if (node->planenum == PLANENUM_LEAF) - { - // leaf faces are emitted in second pass - return; - } - - for (f=node->faces ; f ; f=f->next) - { - EmitFaceVertexes (&node->faces, f); - } - - for (i=0 ; i<2 ; i++) - { - EmitNodeFaceVertexes_r (node->children[i]); - } -} - -void EmitLeafFaceVertexes( face_t **ppLeafFaceList ) -{ - face_t *f = *ppLeafFaceList; - - while ( f ) - { - EmitFaceVertexes( ppLeafFaceList, f ); - f = f->next; - } -} - - -#ifdef USE_HASHING -/* -========== -FindEdgeVerts - -Uses the hash tables to cut down to a small number -========== -*/ -void FindEdgeVerts (Vector& v1, Vector& v2) -{ - int x1, x2, y1, y2, t; - int x, y; - int vnum; - -#if 0 -{ - int i; - num_edge_verts = numvertexes-1; - for (i=0 ; i> HASH_BITS; - y1 = (MAX_COORD_INTEGER + (int)(v1[1]+0.5)) >> HASH_BITS; - x2 = (MAX_COORD_INTEGER + (int)(v2[0]+0.5)) >> HASH_BITS; - y2 = (MAX_COORD_INTEGER + (int)(v2[1]+0.5)) >> HASH_BITS; - - if (x1 > x2) - { - t = x1; - x1 = x2; - x2 = t; - } - if (y1 > y2) - { - t = y1; - y1 = y2; - y2 = t; - } -#if 0 - x1--; - x2++; - y1--; - y2++; - if (x1 < 0) - x1 = 0; - if (x2 >= HASH_SIZE) - x2 = HASH_SIZE; - if (y1 < 0) - y1 = 0; - if (y2 >= HASH_SIZE) - y2 = HASH_SIZE; -#endif - num_edge_verts = 0; - for (x=x1 ; x <= x2 ; x++) - { - for (y=y1 ; y <= y2 ; y++) - { - for (vnum=hashverts[y*HASH_SIZE+x] ; vnum ; vnum=vertexchain[vnum]) - { - edge_verts[num_edge_verts++] = vnum; - } - } - } -} - -#else -/* -========== -FindEdgeVerts - -Forced a dumb check of everything -========== -*/ -void FindEdgeVerts (Vector& v1, Vector& v2) -{ - int i; - - num_edge_verts = numvertexes-1; - for (i=0 ; i= end) - continue; // off an end - VectorMA (edge_start, dist, edge_dir, exact); - VectorSubtract (p, exact, off); - error = off.Length(); - - if (error > OFF_EPSILON) - continue; // not on the edge - - // break the edge - c_tjunctions++; - TestEdge (start, dist, p1, j, k+1); - TestEdge (dist, end, j, p2, k+1); - return; - } - - // the edge p1 to p2 is now free of tjunctions - if (numsuperverts >= MAX_SUPERVERTS) - Error ("Edge with too many vertices due to t-junctions. Max %d verts along an edge!\n", MAX_SUPERVERTS); - superverts[numsuperverts] = p1; - numsuperverts++; -} - - -// stores the edges that each vert is part of -struct face_vert_table_t -{ - face_vert_table_t() - { - edge0 = -1; - edge1 = -1; - } - - void AddEdge( int edge ) - { - if ( edge0 == -1 ) - { - edge0 = edge; - } - else - { - // can only have two edges - Assert(edge1==-1); - edge1 = edge; - } - } - - bool HasEdge( int edge ) const - { - if ( edge >= 0 ) - { - if ( edge0 == edge || edge1 == edge ) - return true; - } - return false; - } - - int edge0; - int edge1; -}; - -// if these two verts share an edge, they must be collinear -bool IsDiagonal( const face_vert_table_t &v0, const face_vert_table_t &v1 ) -{ - if ( v1.HasEdge(v0.edge0) || v1.HasEdge(v0.edge1) ) - return false; - - return true; -} - - -void Triangulate_r( CUtlVector &out, const CUtlVector &inIndices, const CUtlVector &poly ) -{ - Assert( inIndices.Count() > 2 ); - - // one triangle left, return - if ( inIndices.Count() == 3 ) - { - for ( int i = 0; i < inIndices.Count(); i++ ) - { - out.AddToTail( inIndices[i] ); - } - return; - } - - // check each pair of verts and see if they are diagonal (not on a shared edge) - // if so, split & recurse - for ( int i = 0; i < inIndices.Count(); i++ ) - { - int count = inIndices.Count(); - - // i + count is myself, i + count-1 is previous, so we need to stop at i+count-2 - for ( int j = 2; j < count-1; j++ ) - { - // if these two form a diagonal, split the poly along - // the diagonal and triangulate the two sub-polys - int index = inIndices[i]; - int nextArray = (i+j)%count; - int nextIndex = inIndices[nextArray]; - if ( IsDiagonal(poly[index], poly[nextIndex]) ) - { - // add the poly up to the diagonal - CUtlVector in1; - for ( int k = i; k != nextArray; k = (k+1)%count ) - { - in1.AddToTail(inIndices[k]); - } - in1.AddToTail(nextIndex); - - // add the rest of the poly starting with the diagonal - CUtlVector in2; - in2.AddToTail(index); - for ( int l = nextArray; l != i; l = (l+1)%count ) - { - in2.AddToTail(inIndices[l]); - } - - // triangulate the sub-polys - Triangulate_r( out, in1, poly ); - Triangulate_r( out, in2, poly ); - return; - } - } - } - - // didn't find a diagonal - Assert(0); -} - -/* -================== -FixFaceEdges - -================== -*/ -void FixFaceEdges (face_t **pList, face_t *f) -{ - int p1, p2; - int i; - Vector e2; - vec_t len; - int count[MAX_SUPERVERTS], start[MAX_SUPERVERTS]; - int base; - - if (f->merged || f->split[0] || f->split[1]) - return; - - numsuperverts = 0; - - int originalPoints = f->numpoints; - for (i=0 ; inumpoints ; i++) - { - p1 = f->vertexnums[i]; - p2 = f->vertexnums[(i+1)%f->numpoints]; - - VectorCopy (dvertexes[p1].point, edge_start); - VectorCopy (dvertexes[p2].point, e2); - - FindEdgeVerts (edge_start, e2); - - VectorSubtract (e2, edge_start, edge_dir); - len = VectorNormalize (edge_dir); - - start[i] = numsuperverts; - TestEdge (0, len, p1, p2, 0); - - count[i] = numsuperverts - start[i]; - } - - if (numsuperverts < 3) - { // entire face collapsed - f->numpoints = 0; - c_facecollapse++; - return; - } - - // we want to pick a vertex that doesn't have tjunctions - // on either side, which can cause artifacts on trifans, - // especially underwater - for (i=0 ; inumpoints ; i++) - { - if (count[i] == 1 && count[(i+f->numpoints-1)%f->numpoints] == 1) - break; - } - if (i == f->numpoints) - { - f->badstartvert = true; - c_badstartverts++; - base = 0; - - } - else - { // rotate the vertex order - base = start[i]; - } - - // this may fragment the face if > MAXEDGES - FaceFromSuperverts (pList, f, base); - - // if this is the world, then re-triangulate to sew cracks - if ( f->badstartvert && entity_num == 0 ) - { - CUtlVector poly; - CUtlVector inIndices; - CUtlVector outIndices; - poly.AddMultipleToTail( numsuperverts ); - for ( i = 0; i < originalPoints; i++ ) - { - // edge may not have output any points. Don't mark - if ( !count[i] ) - continue; - // mark each edge the point is a member of - // we'll use this as a fast "is collinear" test - for ( int j = 0; j <= count[i]; j++ ) - { - int polyIndex = (start[i] + j) % numsuperverts; - poly[polyIndex].AddEdge( i ); - } - } - for ( i = 0; i < numsuperverts; i++ ) - { - inIndices.AddToTail( i ); - } - Triangulate_r( outIndices, inIndices, poly ); - dprimitive_t &newPrim = g_primitives[g_numprimitives]; - f->firstPrimID = g_numprimitives; - g_numprimitives++; - f->numPrims = 1; - newPrim.firstIndex = g_numprimindices; - newPrim.firstVert = g_numprimverts; - newPrim.indexCount = outIndices.Count(); - newPrim.vertCount = 0; - newPrim.type = PRIM_TRILIST; - g_numprimindices += newPrim.indexCount; - if ( g_numprimitives > MAX_MAP_PRIMITIVES || g_numprimindices > MAX_MAP_PRIMINDICES ) - { - Error("Too many t-junctions to fix up! (%d prims, max %d :: %d indices, max %d)\n", g_numprimitives, MAX_MAP_PRIMITIVES, g_numprimindices, MAX_MAP_PRIMINDICES ); - } - for ( i = 0; i < outIndices.Count(); i++ ) - { - g_primindices[newPrim.firstIndex + i] = outIndices[i]; - } - } -} - -/* -================== -FixEdges_r -================== -*/ -void FixEdges_r (node_t *node) -{ - int i; - face_t *f; - - if (node->planenum == PLANENUM_LEAF) - { - return; - } - - for (f=node->faces ; f ; f=f->next) - FixFaceEdges (&node->faces, f); - - for (i=0 ; i<2 ; i++) - FixEdges_r (node->children[i]); -} - - -//----------------------------------------------------------------------------- -// Purpose: Fix the t-junctions on detail faces -//----------------------------------------------------------------------------- -void FixLeafFaceEdges( face_t **ppLeafFaceList ) -{ - face_t *f; - - for ( f = *ppLeafFaceList; f; f = f->next ) - { - FixFaceEdges( ppLeafFaceList, f ); - } -} - -/* -=========== -FixTjuncs - -=========== -*/ - -face_t *FixTjuncs (node_t *headnode, face_t *pLeafFaceList) -{ - // snap and merge all vertexes - qprintf ("---- snap verts ----\n"); - memset (hashverts, 0, sizeof(hashverts)); - memset (vertexchain, 0, sizeof(vertexchain)); - c_totalverts = 0; - c_uniqueverts = 0; - c_faceoverflows = 0; - EmitNodeFaceVertexes_r (headnode); - - // UNDONE: This count is wrong with tjuncs off on details - since - - // break edges on tjunctions - qprintf ("---- tjunc ----\n"); - c_tryedges = 0; - c_degenerate = 0; - c_facecollapse = 0; - c_tjunctions = 0; - - if ( g_bAllowDetailCracks ) - { - FixEdges_r (headnode); - EmitLeafFaceVertexes( &pLeafFaceList ); - FixLeafFaceEdges( &pLeafFaceList ); - } - else - { - EmitLeafFaceVertexes( &pLeafFaceList ); - if (!notjunc) - { - FixEdges_r (headnode); - FixLeafFaceEdges( &pLeafFaceList ); - } - } - - - qprintf ("%i unique from %i\n", c_uniqueverts, c_totalverts); - qprintf ("%5i edges degenerated\n", c_degenerate); - qprintf ("%5i faces degenerated\n", c_facecollapse); - qprintf ("%5i edges added by tjunctions\n", c_tjunctions); - qprintf ("%5i faces added by tjunctions\n", c_faceoverflows); - qprintf ("%5i bad start verts\n", c_badstartverts); - - return pLeafFaceList; -} - - -//======================================================== - -int c_faces; - -face_t *AllocFace (void) -{ - static int s_FaceId = 0; - - face_t *f; - - f = (face_t*)malloc(sizeof(*f)); - memset (f, 0, sizeof(*f)); - f->id = s_FaceId; - ++s_FaceId; - - c_faces++; - - return f; -} - -face_t *NewFaceFromFace (face_t *f) -{ - face_t *newf; - - newf = AllocFace (); - *newf = *f; - newf->merged = NULL; - newf->split[0] = newf->split[1] = NULL; - newf->w = NULL; - return newf; -} - -void FreeFace (face_t *f) -{ - if (f->w) - FreeWinding (f->w); - free (f); - c_faces--; -} - - -void FreeFaceList( face_t *pFaces ) -{ - while ( pFaces ) - { - face_t *next = pFaces->next; - - FreeFace( pFaces ); - pFaces = next; - } -} - -//======================================================== - -void GetEdge2_InitOptimizedList() -{ - for( int i=0; i < MAX_MAP_VERTS; i++ ) - g_VertEdgeList[i].RemoveAll(); -} - - -void IntSort( CUtlVector &theList ) -{ - for( int i=0; i < theList.Size()-1; i++ ) - { - if( theList[i] > theList[i+1] ) - { - int temp = theList[i]; - theList[i] = theList[i+1]; - theList[i+1] = temp; - if( i > 0 ) - i -= 2; - else - i = -1; - } - } -} - - -int AddEdge( int v1, int v2, face_t *f ) -{ - if (numedges >= MAX_MAP_EDGES) - Error ("Too many edges in map, max == %d", MAX_MAP_EDGES); - - g_VertEdgeList[v1].AddToTail( numedges ); - g_VertEdgeList[v2].AddToTail( numedges ); - IntSort( g_VertEdgeList[v1] ); - IntSort( g_VertEdgeList[v2] ); - - dedge_t *edge = &dedges[numedges]; - numedges++; - - edge->v[0] = v1; - edge->v[1] = v2; - edgefaces[numedges-1][0] = f; - return numedges - 1; -} - - -/* -================== -GetEdge - -Called by writebsp. -Don't allow four way edges -================== -*/ -int GetEdge2 (int v1, int v2, face_t *f) -{ - dedge_t *edge; - - c_tryedges++; - - if (!noshare) - { - // Check all edges connected to v1. - CUtlVector &theList = g_VertEdgeList[v1]; - for( int i=0; i < theList.Size(); i++ ) - { - int iEdge = theList[i]; - edge = &dedges[iEdge]; - if (v1 == edge->v[1] && v2 == edge->v[0] && edgefaces[iEdge][0]->contents == f->contents) - { - if (edgefaces[iEdge][1]) - continue; - - edgefaces[iEdge][1] = f; - return -iEdge; - } - } - } - - return AddEdge( v1, v2, f ); -} - -/* -=========================================================================== - -FACE MERGING - -=========================================================================== -*/ - -#define CONTINUOUS_EPSILON 0.001 - -/* -============= -TryMergeWinding - -If two polygons share a common edge and the edges that meet at the -common points are both inside the other polygons, merge them - -Returns NULL if the faces couldn't be merged, or the new face. -The originals will NOT be freed. -============= -*/ -winding_t *TryMergeWinding (winding_t *f1, winding_t *f2, Vector& planenormal) -{ - Vector *p1, *p2, *p3, *p4, *back; - winding_t *newf; - int i, j, k, l; - Vector normal, delta; - vec_t dot; - qboolean keep1, keep2; - - - // - // find a common edge - // - p1 = p2 = NULL; // stop compiler warning - j = 0; // - - for (i=0 ; inumpoints ; i++) - { - p1 = &f1->p[i]; - p2 = &f1->p[(i+1)%f1->numpoints]; - for (j=0 ; jnumpoints ; j++) - { - p3 = &f2->p[j]; - p4 = &f2->p[(j+1)%f2->numpoints]; - for (k=0 ; k<3 ; k++) - { - if (fabs((*p1)[k] - (*p4)[k]) > EQUAL_EPSILON) - break; - if (fabs((*p2)[k] - (*p3)[k]) > EQUAL_EPSILON) - break; - } - if (k==3) - break; - } - if (j < f2->numpoints) - break; - } - - if (i == f1->numpoints) - return NULL; // no matching edges - - // - // check slope of connected lines - // if the slopes are colinear, the point can be removed - // - back = &f1->p[(i+f1->numpoints-1)%f1->numpoints]; - VectorSubtract (*p1, *back, delta); - CrossProduct (planenormal, delta, normal); - VectorNormalize (normal); - - back = &f2->p[(j+2)%f2->numpoints]; - VectorSubtract (*back, *p1, delta); - dot = DotProduct (delta, normal); - if (dot > CONTINUOUS_EPSILON) - return NULL; // not a convex polygon - keep1 = (qboolean)(dot < -CONTINUOUS_EPSILON); - - back = &f1->p[(i+2)%f1->numpoints]; - VectorSubtract (*back, *p2, delta); - CrossProduct (planenormal, delta, normal); - VectorNormalize (normal); - - back = &f2->p[(j+f2->numpoints-1)%f2->numpoints]; - VectorSubtract (*back, *p2, delta); - dot = DotProduct (delta, normal); - if (dot > CONTINUOUS_EPSILON) - return NULL; // not a convex polygon - keep2 = (qboolean)(dot < -CONTINUOUS_EPSILON); - - // - // build the new polygon - // - newf = AllocWinding (f1->numpoints + f2->numpoints); - - // copy first polygon - for (k=(i+1)%f1->numpoints ; k != i ; k=(k+1)%f1->numpoints) - { - if (k==(i+1)%f1->numpoints && !keep2) - continue; - - VectorCopy (f1->p[k], newf->p[newf->numpoints]); - newf->numpoints++; - } - - // copy second polygon - for (l= (j+1)%f2->numpoints ; l != j ; l=(l+1)%f2->numpoints) - { - if (l==(j+1)%f2->numpoints && !keep1) - continue; - VectorCopy (f2->p[l], newf->p[newf->numpoints]); - newf->numpoints++; - } - - return newf; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool OverlaysAreEqual( face_t *f1, face_t *f2 ) -{ - // Check the overlay ids - see if they are the same. - if ( f1->originalface->aOverlayIds.Count() != f2->originalface->aOverlayIds.Count() ) - return false; - - int nOverlayCount = f1->originalface->aOverlayIds.Count(); - for ( int iOverlay = 0; iOverlay < nOverlayCount; ++iOverlay ) - { - int nOverlayId = f1->originalface->aOverlayIds[iOverlay]; - if ( f2->originalface->aOverlayIds.Find( nOverlayId ) == -1 ) - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool FaceOnWaterBrush( face_t *face ) -{ - side_t *pSide = face->originalface; - if ( !pSide ) - return false; - - if ( pSide->contents & ( CONTENTS_WATER | CONTENTS_SLIME ) ) - return true; - - return false; -} - -/* -============= -TryMerge - -If two polygons share a common edge and the edges that meet at the -common points are both inside the other polygons, merge them - -Returns NULL if the faces couldn't be merged, or the new face. -The originals will NOT be freed. -============= -*/ -face_t *TryMerge (face_t *f1, face_t *f2, Vector& planenormal) -{ - face_t *newf; - winding_t *nw; - - if (!f1->w || !f2->w) - return NULL; - if (f1->texinfo != f2->texinfo) - return NULL; - if (f1->planenum != f2->planenum) // on front and back sides - return NULL; - if (f1->contents != f2->contents) - return NULL; - if ( f1->originalface->smoothingGroups != f2->originalface->smoothingGroups ) - return NULL; - if ( !OverlaysAreEqual( f1, f2 ) ) - return NULL; - if ( nomergewater && ( FaceOnWaterBrush( f1 ) || FaceOnWaterBrush( f2 ) ) ) - return NULL; - - nw = TryMergeWinding (f1->w, f2->w, planenormal); - if (!nw) - return NULL; - - c_merge++; - newf = NewFaceFromFace (f1); - newf->w = nw; - - f1->merged = newf; - f2->merged = newf; - - return newf; -} - -/* -=============== -MergeFaceList -=============== -*/ -void MergeFaceList(face_t **pList) -{ - face_t *f1, *f2, *end; - face_t *merged; - plane_t *plane; - - merged = NULL; - - for (f1 = *pList; f1 ; f1 = f1->next) - { - if (f1->merged || f1->split[0] || f1->split[1]) - continue; - for (f2 = *pList; f2 != f1 ; f2=f2->next) - { - if (f2->merged || f2->split[0] || f2->split[1]) - continue; - - plane = &g_MainMap->mapplanes[f1->planenum]; - merged = TryMerge (f1, f2, plane->normal); - if (!merged) - continue; - - // add merged to the end of the face list - // so it will be checked against all the faces again - for (end = *pList; end->next ; end = end->next) - ; - merged->next = NULL; - end->next = merged; - break; - } - } -} - -//===================================================================== - -/* -=============== -SubdivideFace - -Chop up faces that are larger than we want in the surface cache -=============== -*/ -void SubdivideFace (face_t **pFaceList, face_t *f) -{ - float mins, maxs; - vec_t v; - vec_t luxelsPerWorldUnit; - int axis, i; - texinfo_t *tex; - Vector temp; - vec_t dist; - winding_t *w, *frontw, *backw; - - if ( f->merged || f->split[0] || f->split[1] ) - return; - -// special (non-surface cached) faces don't need subdivision - tex = &texinfo[f->texinfo]; - - if( tex->flags & SURF_NOLIGHT ) - { - return; - } - - for (axis = 0 ; axis < 2 ; axis++) - { - while (1) - { - mins = 999999; - maxs = -999999; - - VECTOR_COPY (tex->lightmapVecsLuxelsPerWorldUnits[axis], temp); - w = f->w; - for (i=0 ; inumpoints ; i++) - { - v = DotProduct (w->p[i], temp); - if (v < mins) - mins = v; - if (v > maxs) - maxs = v; - } -#if 0 - if (maxs - mins <= 0) - Error ("zero extents"); -#endif - if (maxs - mins <= g_maxLightmapDimension) - break; - - // split it - c_subdivide++; - - luxelsPerWorldUnit = VectorNormalize (temp); - - dist = ( mins + g_maxLightmapDimension - 1 ) / luxelsPerWorldUnit; - - ClipWindingEpsilon (w, temp, dist, ON_EPSILON, &frontw, &backw); - if (!frontw || !backw) - Error ("SubdivideFace: didn't split the polygon"); - - f->split[0] = NewFaceFromFace (f); - f->split[0]->w = frontw; - f->split[0]->next = *pFaceList; - *pFaceList = f->split[0]; - - f->split[1] = NewFaceFromFace (f); - f->split[1]->w = backw; - f->split[1]->next = *pFaceList; - *pFaceList = f->split[1]; - - SubdivideFace (pFaceList, f->split[0]); - SubdivideFace (pFaceList, f->split[1]); - return; - } - } -} - -void SubdivideFaceList(face_t **pFaceList) -{ - face_t *f; - - for (f = *pFaceList ; f ; f=f->next) - { - SubdivideFace (pFaceList, f); - } -} - - -//----------------------------------------------------------------------------- -// Assigns the bottom material to the bottom face -//----------------------------------------------------------------------------- -static bool AssignBottomWaterMaterialToFace( face_t *f ) -{ - // NOTE: This happens *after* cubemap fixup occurs, so we need to get the - // fixed-up bottom material for this - texinfo_t *pTexInfo = &texinfo[f->texinfo]; - dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); - const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); - - char pBottomMatName[512]; - if ( !GetValueFromPatchedMaterial( pMaterialName, "$bottommaterial", pBottomMatName, 512 ) ) - { - if( !Q_stristr( pMaterialName, "nodraw" ) && !Q_stristr( pMaterialName, "toolsskip" ) ) - { - Warning("error: material %s doesn't have a $bottommaterial\n", pMaterialName ); - } - return false; - } - - //Assert( mapplanes[f->planenum].normal.z < 0 ); - texinfo_t newTexInfo; - newTexInfo.flags = pTexInfo->flags; - int j, k; - for (j=0 ; j<2 ; j++) - { - for (k=0 ; k<4 ; k++) - { - newTexInfo.textureVecsTexelsPerWorldUnits[j][k] = pTexInfo->textureVecsTexelsPerWorldUnits[j][k]; - newTexInfo.lightmapVecsLuxelsPerWorldUnits[j][k] = pTexInfo->lightmapVecsLuxelsPerWorldUnits[j][k]; - } - } - newTexInfo.texdata = FindOrCreateTexData( pBottomMatName ); - f->texinfo = FindOrCreateTexInfo( newTexInfo ); - - return true; -} - - -//=========================================================================== - -int c_nodefaces; - -static void SubdivideFaceBySubdivSize( face_t *f, float subdivsize ); -void SubdivideFaceBySubdivSize( face_t *f ); - -/* -============ -FaceFromPortal - -============ -*/ -extern int FindOrCreateTexInfo( const texinfo_t &searchTexInfo ); - -face_t *FaceFromPortal (portal_t *p, int pside) -{ - face_t *f; - side_t *side; - int deltaContents; - - // portal does not bridge different visible contents - side = p->side; - if (!side) - return NULL; - - // allocate a new face - f = AllocFace(); - - // save the original "side" from the map brush -- portal->side - // see FindPortalSide(...) - f->originalface = side; - - // - // save material info - // - f->texinfo = side->texinfo; - f->dispinfo = -1; // all faces with displacement info are created elsewhere - f->smoothingGroups = side->smoothingGroups; - - // save plane info - f->planenum = (side->planenum & ~1) | pside; - if ( entity_num != 0 ) - { - // the brush model renderer doesn't use PLANEBACK, so write the real plane - // inside water faces can be flipped because they are generated on the inside of the brush - if ( p->nodes[pside]->contents & (CONTENTS_WATER|CONTENTS_SLIME) ) - { - f->planenum = (side->planenum & ~1) | pside; - } - else - { - f->planenum = side->planenum; - } - } - - // save portal info - f->portal = p; - f->fogVolumeLeaf = NULL; - - deltaContents = VisibleContents(p->nodes[!pside]->contents^p->nodes[pside]->contents); - - // don't show insides of windows or grates - if ( ((p->nodes[pside]->contents & CONTENTS_WINDOW) && deltaContents == CONTENTS_WINDOW) || - ((p->nodes[pside]->contents & CONTENTS_GRATE) && deltaContents == CONTENTS_GRATE) ) - { - FreeFace( f ); - return NULL; - } - - if ( p->nodes[pside]->contents & MASK_WATER ) - { - f->fogVolumeLeaf = p->nodes[pside]; - } - else if ( p->nodes[!pside]->contents & MASK_WATER ) - { - f->fogVolumeLeaf = p->nodes[!pside]; - } - - // If it's the underside of water, we need to figure out what material to use, etc. - if( ( p->nodes[pside]->contents & CONTENTS_WATER ) && deltaContents == CONTENTS_WATER ) - { - if ( !AssignBottomWaterMaterialToFace( f ) ) - { - FreeFace( f ); - return NULL; - } - } - - // - // generate the winding for the face and save face contents - // - if( pside ) - { - f->w = ReverseWinding(p->winding); - f->contents = p->nodes[1]->contents; - } - else - { - f->w = CopyWinding(p->winding); - f->contents = p->nodes[0]->contents; - } - - f->numPrims = 0; - f->firstPrimID = 0; - - // return the created face - return f; -} - -/* -=============== -MakeFaces_r - -If a portal will make a visible face, -mark the side that originally created it - - solid / empty : solid - solid / water : solid - water / empty : water - water / water : none -=============== -*/ -void MakeFaces_r (node_t *node) -{ - portal_t *p; - int s; - - // recurse down to leafs - if (node->planenum != PLANENUM_LEAF) - { - MakeFaces_r (node->children[0]); - MakeFaces_r (node->children[1]); - - // merge together all visible faces on the node - if (!nomerge) - MergeFaceList(&node->faces); - if (!nosubdiv) - SubdivideFaceList(&node->faces); - - return; - } - - // solid leafs never have visible faces - if (node->contents & CONTENTS_SOLID) - return; - - // see which portals are valid - for (p=node->portals ; p ; p = p->next[s]) - { - s = (p->nodes[1] == node); - - p->face[s] = FaceFromPortal (p, s); - if (p->face[s]) - { - c_nodefaces++; - p->face[s]->next = p->onnode->faces; - p->onnode->faces = p->face[s]; - } - } -} - -typedef winding_t *pwinding_t; - -static void PrintWinding( winding_t *w ) -{ - int i; - Msg( "\t---\n" ); - for( i = 0; i < w->numpoints; i++ ) - { - Msg( "\t%f %f %f\n", w->p[i].x, w->p[i].y, w->p[i].z ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Adds a winding to the current list of primverts -// Input : *w - the winding -// *pIndices - The output indices -// vertStart - the starting vert index -// vertCount - current count -// Output : int - output count including new verts from this winding -//----------------------------------------------------------------------------- -int AddWindingToPrimverts( const winding_t *w, unsigned short *pIndices, int vertStart, int vertCount ) -{ - for( int i = 0; i < w->numpoints; i++ ) - { - int j; - for( j = vertStart; j < vertStart + vertCount; j++ ) - { - Vector tmp = g_primverts[j].pos - w->p[i]; - - if( tmp.LengthSqr() < POINT_EPSILON*POINT_EPSILON ) - { - pIndices[i] = j; - break; - } - } - if ( j >= vertStart + vertCount ) - { - pIndices[i] = j; - g_primverts[j].pos = w->p[i]; - vertCount++; - g_numprimverts++; - if ( g_numprimverts > MAX_MAP_PRIMVERTS ) - { - Error( "Exceeded max water verts.\nIncrease surface subdivision size or lower your subdivision size in vmt files! (%d>%d)\n", - ( int )g_numprimverts, ( int )MAX_MAP_PRIMVERTS ); - } - } - } - - return vertCount; -} - - - -#pragma optimize( "g", off ) -#define USE_TRISTRIPS - -// UNDONE: Should split this function into subdivide and primitive building parts -// UNDONE: We should try building strips of shared verts for all water faces in a leaf -// since those will be drawn concurrently anyway. It should be more efficient. -static void SubdivideFaceBySubdivSize( face_t *f, float subdivsize ) -{ - // garymcthack - REFACTOR ME!!! - - vec_t dummy; - Vector hackNormal; - WindingPlane( f->w, hackNormal, &dummy ); - - // HACK - only subdivide stuff that is facing up or down (for water) - if( fabs(hackNormal[2]) < .9f ) - { - return; - } - - // Get the extents of the surface. - // garymcthack - this assumes a surface of constant z for now (for water). . can generalize later. - subdivsize = ( int )subdivsize; - winding_t *w; - w = CopyWinding( f->w ); - - Vector min, max; - WindingBounds( w, min, max ); - -#if 0 - Msg( "START WINDING: \n" ); - PrintWinding( w ); -#endif - int xStart, yStart, xEnd, yEnd, xSteps, ySteps; - xStart = ( int )subdivsize * ( int )( ( min[0] - subdivsize ) / subdivsize ); - xEnd = ( int )subdivsize * ( int )( ( max[0] + subdivsize ) / subdivsize ); - yStart = ( int )subdivsize * ( int )( ( min[1] - subdivsize ) / subdivsize ); - yEnd = ( int )subdivsize * ( int )( ( max[1] + subdivsize ) / subdivsize ); - xSteps = ( xEnd - xStart ) / subdivsize; - ySteps = ( yEnd - yStart ) / subdivsize; - int x, y; - int xi, yi; - winding_t **windings = ( winding_t ** )new pwinding_t[xSteps * ySteps]; - memset( windings, 0, sizeof( winding_t * ) * xSteps * ySteps ); - - for( yi = 0, y = yStart; y < yEnd; y += ( int )subdivsize, yi++ ) - { - for( xi = 0, x = xStart; x < xEnd; x += ( int )subdivsize, xi++ ) - { - winding_t *tempWinding, *frontWinding, *backWinding; - float planeDist; - Vector normal; - normal.Init( 1.0f, 0.0f, 0.0f ); - planeDist = ( float )x; - tempWinding = CopyWinding( w ); - ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, - &frontWinding, &backWinding ); - if( tempWinding ) - { - FreeWinding( tempWinding ); - } - if( backWinding ) - { - FreeWinding( backWinding ); - } - if( !frontWinding ) - { - continue; - } - tempWinding = frontWinding; - - normal.Init( -1.0f, 0.0f, 0.0f ); - planeDist = -( float )( x + subdivsize ); - ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, - &frontWinding, &backWinding ); - if( tempWinding ) - { - FreeWinding( tempWinding ); - } - if( backWinding ) - { - FreeWinding( backWinding ); - } - if( !frontWinding ) - { - continue; - } - tempWinding = frontWinding; - - normal.Init( 0.0f, 1.0f, 0.0f ); - planeDist = ( float )y; - ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, - &frontWinding, &backWinding ); - if( tempWinding ) - { - FreeWinding( tempWinding ); - } - if( backWinding ) - { - FreeWinding( backWinding ); - } - if( !frontWinding ) - { - continue; - } - tempWinding = frontWinding; - - normal.Init( 0.0f, -1.0f, 0.0f ); - planeDist = -( float )( y + subdivsize ); - ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, - &frontWinding, &backWinding ); - if( tempWinding ) - { - FreeWinding( tempWinding ); - } - if( backWinding ) - { - FreeWinding( backWinding ); - } - if( !frontWinding ) - { - continue; - } - -#if 0 - Msg( "output winding:\n" ); - PrintWinding( frontWinding ); -#endif - - if( frontWinding ) - { - windings[xi + yi * xSteps] = frontWinding; - } - } - } - FreeWinding( w ); - dprimitive_t &newPrim = g_primitives[g_numprimitives]; - f->firstPrimID = g_numprimitives; - f->numPrims = 1; - newPrim.firstIndex = g_numprimindices; - newPrim.firstVert = g_numprimverts; - newPrim.indexCount = 0; - newPrim.vertCount = 0; -#ifdef USE_TRISTRIPS - newPrim.type = PRIM_TRISTRIP; -#else - newPrim.type = PRIM_TRILIST; -#endif - - CUtlVector triListIndices; - int i; - for( i = 0; i < xSteps * ySteps; i++ ) - { - if( !windings[i] ) - { - continue; - } - unsigned short *pIndices = - ( unsigned short * )_alloca( windings[i]->numpoints * sizeof( unsigned short ) ); - // find indices for the verts. - newPrim.vertCount = AddWindingToPrimverts( windings[i], pIndices, newPrim.firstVert, newPrim.vertCount ); - - // Now that we have indices for the verts, fan-tesselate the polygon and spit out tris. - for( int j = 0; j < windings[i]->numpoints - 2; j++ ) - { - triListIndices.AddToTail( pIndices[0] ); - triListIndices.AddToTail( pIndices[j+1] ); - triListIndices.AddToTail( pIndices[j+2] ); - } - } - - delete [] windings; - // We've already updated the verts and have a trilist. . let's strip it! - if( !triListIndices.Size() ) - { - return; - } - -#ifdef USE_TRISTRIPS - int numTristripIndices; - WORD *pStripIndices = NULL; - Stripify( triListIndices.Size() / 3, triListIndices.Base(), &numTristripIndices, - &pStripIndices ); - Assert( pStripIndices ); - - // FIXME: Should also call ComputeVertexPermutation and reorder the verts. - - for( i = 0; i < numTristripIndices; i++ ) - { - Assert( pStripIndices[i] >= newPrim.firstVert && - pStripIndices[i] < newPrim.firstVert + newPrim.vertCount ); - g_primindices[newPrim.firstIndex + newPrim.indexCount] = pStripIndices[i]; - newPrim.indexCount++; - g_numprimindices++; - if( g_numprimindices > MAX_MAP_PRIMINDICES ) - { - Error( "Exceeded max water indicies.\nIncrease surface subdivision size! (%d>%d)\n", g_numprimindices, MAX_MAP_PRIMINDICES ); - } - } - delete [] pStripIndices; -#else - for( i = 0; i < triListIndices.Size(); i++ ) - { - g_primindices[newPrim.firstIndex + newPrim.indexCount] = triListIndices[i]; - newPrim.indexCount++; - g_numprimindices++; - if( g_numprimindices > MAX_MAP_PRIMINDICES ) - { - Error( "Exceeded max water indicies.\nIncrease surface subdivision size! (%d>%d)\n", g_numprimindices, MAX_MAP_PRIMINDICES ); - } - } -#endif - g_numprimitives++; // don't increment until we get here and are sure that we have a primitive. - if( g_numprimitives > MAX_MAP_PRIMITIVES ) - { - Error( "Exceeded max water primitives.\nIncrease surface subdivision size! (%d>%d)\n", ( int )g_numprimitives, ( int )MAX_MAP_PRIMITIVES ); - } -} - -void SubdivideFaceBySubdivSize( face_t *f ) -{ - if( f->numpoints == 0 || f->split[0] || f->split[1] || f->merged || !f->w ) - { - return; - } - // see if the face needs to be subdivided. - texinfo_t *pTexInfo = &texinfo[f->texinfo]; - dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); - bool bFound; - const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); - MaterialSystemMaterial_t matID = - FindOriginalMaterial( pMaterialName, &bFound, false ); - - if( !bFound ) - { - return; - } - const char *subdivsizeString = GetMaterialVar( matID, "$subdivsize" ); - if( subdivsizeString ) - { - float subdivSize = atof( subdivsizeString ); - if( subdivSize > 0.0f ) - { - // NOTE: Subdivision is unsupported and should be phased out - Warning("Using subdivision on %s\n", pMaterialName ); - SubdivideFaceBySubdivSize( f, subdivSize ); - } - } -} - -void SplitSubdividedFaces_Node_r( node_t *node ) -{ - if (node->planenum == PLANENUM_LEAF) - { - return; - } - face_t *f; - for( f = node->faces; f ;f = f->next ) - { - SubdivideFaceBySubdivSize( f ); - } - - // - // recursively output the other nodes - // - SplitSubdividedFaces_Node_r( node->children[0] ); - SplitSubdividedFaces_Node_r( node->children[1] ); -} - -void SplitSubdividedFaces( face_t *pLeafFaceList, node_t *headnode ) -{ - // deal with leaf faces. - face_t *f = pLeafFaceList; - while ( f ) - { - SubdivideFaceBySubdivSize( f ); - f = f->next; - } - - // deal with node faces. - SplitSubdividedFaces_Node_r( headnode ); -} - -#pragma optimize( "", on ) - -/* -============ -MakeFaces -============ -*/ -void MakeFaces (node_t *node) -{ - qprintf ("--- MakeFaces ---\n"); - c_merge = 0; - c_subdivide = 0; - c_nodefaces = 0; - - MakeFaces_r (node); - - qprintf ("%5i makefaces\n", c_nodefaces); - qprintf ("%5i merged\n", c_merge); - qprintf ("%5i subdivided\n", c_subdivide); +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// faces.c + +#include "vbsp.h" +#include "utlvector.h" +#include "utilmatlib.h" +#include +#include "mstristrip.h" +#include "tier1/strtools.h" +#include "materialpatch.h" +/* + + some faces will be removed before saving, but still form nodes: + + the insides of sky volumes + meeting planes of different water current volumes + +*/ + +// undefine for dumb linear searches +#define USE_HASHING + +#define INTEGRAL_EPSILON 0.01 +#define POINT_EPSILON 0.1 +#define OFF_EPSILON 0.25 + +int c_merge; +int c_subdivide; + +int c_totalverts; +int c_uniqueverts; +int c_degenerate; +int c_tjunctions; +int c_faceoverflows; +int c_facecollapse; +int c_badstartverts; + +#define MAX_SUPERVERTS 512 +int superverts[MAX_SUPERVERTS]; +int numsuperverts; + +face_t *edgefaces[MAX_MAP_EDGES][2]; +int firstmodeledge = 1; +int firstmodelface; + +int c_tryedges; + +Vector edge_dir; +Vector edge_start; +vec_t edge_len; + +int num_edge_verts; +int edge_verts[MAX_MAP_VERTS]; + + +float g_maxLightmapDimension = 32; + + +face_t *NewFaceFromFace (face_t *f); + +// Used to speed up GetEdge2(). Holds a list of edges connected to each vert. +CUtlVector g_VertEdgeList[MAX_MAP_VERTS]; + + +//=========================================================================== + +typedef struct hashvert_s +{ + struct hashvert_s *next; + int num; +} hashvert_t; + +#define HASH_BITS 7 +#define HASH_SIZE (COORD_EXTENT>>HASH_BITS) + + +int vertexchain[MAX_MAP_VERTS]; // the next vertex in a hash chain +int hashverts[HASH_SIZE*HASH_SIZE]; // a vertex number, or 0 for no verts + +//face_t *edgefaces[MAX_MAP_EDGES][2]; + +//============================================================================ + + +unsigned HashVec (Vector& vec) +{ + int x, y; + + x = (MAX_COORD_INTEGER + (int)(vec[0]+0.5)) >> HASH_BITS; + y = (MAX_COORD_INTEGER + (int)(vec[1]+0.5)) >> HASH_BITS; + + if ( x < 0 || x >= HASH_SIZE || y < 0 || y >= HASH_SIZE ) + Error ("HashVec: point outside valid range"); + + return y*HASH_SIZE + x; +} + +#ifdef USE_HASHING +/* +============= +GetVertex + +Uses hashing +============= +*/ +int GetVertexnum (Vector& in) +{ + int h; + int i; + Vector vert; + int vnum; + + c_totalverts++; + + for (i=0 ; i<3 ; i++) + { + if ( fabs(in[i] - (int)(in[i]+0.5)) < INTEGRAL_EPSILON) + vert[i] = (int)(in[i]+0.5); + else + vert[i] = in[i]; + } + + h = HashVec (vert); + + for (vnum=hashverts[h] ; vnum ; vnum=vertexchain[vnum]) + { + Vector& p = dvertexes[vnum].point; + if ( fabs(p[0]-vert[0]) MAX_COORD_INTEGER) + Error ("GetVertexnum: outside world, vertex %.1f %.1f %.1f", v.x, v.y, v.z); + } + + // search for an existing vertex match + for (i=0, dv=dvertexes ; ipoint[j]; + if ( d > POINT_EPSILON || d < -POINT_EPSILON) + break; + } + if (j == 3) + return i; // a match + } + + // new point + if (numvertexes == MAX_MAP_VERTS) + Error ("Too many unique verts, max = %d (map has too much brush geometry)\n", MAX_MAP_VERTS); + VectorCopy (v, dv->point); + numvertexes++; + c_uniqueverts++; + + return numvertexes-1; +} +#endif + + +/* +================== +FaceFromSuperverts + +The faces vertexes have beeb added to the superverts[] array, +and there may be more there than can be held in a face (MAXEDGES). + +If less, the faces vertexnums[] will be filled in, otherwise +face will reference a tree of split[] faces until all of the +vertexnums can be added. + +superverts[base] will become face->vertexnums[0], and the others +will be circularly filled in. +================== +*/ +void FaceFromSuperverts (face_t **pListHead, face_t *f, int base) +{ + face_t *newf; + int remaining; + int i; + + remaining = numsuperverts; + while (remaining > MAXEDGES) + { // must split into two faces, because of vertex overload + c_faceoverflows++; + + newf = NewFaceFromFace (f); + f->split[0] = newf; + + newf->next = *pListHead; + *pListHead = newf; + + newf->numpoints = MAXEDGES; + for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; + + f->split[1] = NewFaceFromFace (f); + f = f->split[1]; + + f->next = *pListHead; + *pListHead = f; + + remaining -= (MAXEDGES-2); + base = (base+MAXEDGES-1)%numsuperverts; + } + + // copy the vertexes back to the face + f->numpoints = remaining; + for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; +} + + +/* +================== +EmitFaceVertexes +================== +*/ +void EmitFaceVertexes (face_t **pListHead, face_t *f) +{ + winding_t *w; + int i; + + if (f->merged || f->split[0] || f->split[1]) + return; + + w = f->w; + for (i=0 ; inumpoints ; i++) + { + if (noweld) + { // make every point unique + if (numvertexes == MAX_MAP_VERTS) + Error ("Too many unique verts, max = %d (map has too much brush geometry)\n", MAX_MAP_VERTS); + superverts[i] = numvertexes; + VectorCopy (w->p[i], dvertexes[numvertexes].point); + numvertexes++; + c_uniqueverts++; + c_totalverts++; + } + else + superverts[i] = GetVertexnum (w->p[i]); + } + numsuperverts = w->numpoints; + + // this may fragment the face if > MAXEDGES + FaceFromSuperverts (pListHead, f, 0); +} + +/* +================== +EmitNodeFaceVertexes_r +================== +*/ +void EmitNodeFaceVertexes_r (node_t *node) +{ + int i; + face_t *f; + + if (node->planenum == PLANENUM_LEAF) + { + // leaf faces are emitted in second pass + return; + } + + for (f=node->faces ; f ; f=f->next) + { + EmitFaceVertexes (&node->faces, f); + } + + for (i=0 ; i<2 ; i++) + { + EmitNodeFaceVertexes_r (node->children[i]); + } +} + +void EmitLeafFaceVertexes( face_t **ppLeafFaceList ) +{ + face_t *f = *ppLeafFaceList; + + while ( f ) + { + EmitFaceVertexes( ppLeafFaceList, f ); + f = f->next; + } +} + + +#ifdef USE_HASHING +/* +========== +FindEdgeVerts + +Uses the hash tables to cut down to a small number +========== +*/ +void FindEdgeVerts (Vector& v1, Vector& v2) +{ + int x1, x2, y1, y2, t; + int x, y; + int vnum; + +#if 0 +{ + int i; + num_edge_verts = numvertexes-1; + for (i=0 ; i> HASH_BITS; + y1 = (MAX_COORD_INTEGER + (int)(v1[1]+0.5)) >> HASH_BITS; + x2 = (MAX_COORD_INTEGER + (int)(v2[0]+0.5)) >> HASH_BITS; + y2 = (MAX_COORD_INTEGER + (int)(v2[1]+0.5)) >> HASH_BITS; + + if (x1 > x2) + { + t = x1; + x1 = x2; + x2 = t; + } + if (y1 > y2) + { + t = y1; + y1 = y2; + y2 = t; + } +#if 0 + x1--; + x2++; + y1--; + y2++; + if (x1 < 0) + x1 = 0; + if (x2 >= HASH_SIZE) + x2 = HASH_SIZE; + if (y1 < 0) + y1 = 0; + if (y2 >= HASH_SIZE) + y2 = HASH_SIZE; +#endif + num_edge_verts = 0; + for (x=x1 ; x <= x2 ; x++) + { + for (y=y1 ; y <= y2 ; y++) + { + for (vnum=hashverts[y*HASH_SIZE+x] ; vnum ; vnum=vertexchain[vnum]) + { + edge_verts[num_edge_verts++] = vnum; + } + } + } +} + +#else +/* +========== +FindEdgeVerts + +Forced a dumb check of everything +========== +*/ +void FindEdgeVerts (Vector& v1, Vector& v2) +{ + int i; + + num_edge_verts = numvertexes-1; + for (i=0 ; i= end) + continue; // off an end + VectorMA (edge_start, dist, edge_dir, exact); + VectorSubtract (p, exact, off); + error = off.Length(); + + if (error > OFF_EPSILON) + continue; // not on the edge + + // break the edge + c_tjunctions++; + TestEdge (start, dist, p1, j, k+1); + TestEdge (dist, end, j, p2, k+1); + return; + } + + // the edge p1 to p2 is now free of tjunctions + if (numsuperverts >= MAX_SUPERVERTS) + Error ("Edge with too many vertices due to t-junctions. Max %d verts along an edge!\n", MAX_SUPERVERTS); + superverts[numsuperverts] = p1; + numsuperverts++; +} + + +// stores the edges that each vert is part of +struct face_vert_table_t +{ + face_vert_table_t() + { + edge0 = -1; + edge1 = -1; + } + + void AddEdge( int edge ) + { + if ( edge0 == -1 ) + { + edge0 = edge; + } + else + { + // can only have two edges + Assert(edge1==-1); + edge1 = edge; + } + } + + bool HasEdge( int edge ) const + { + if ( edge >= 0 ) + { + if ( edge0 == edge || edge1 == edge ) + return true; + } + return false; + } + + int edge0; + int edge1; +}; + +// if these two verts share an edge, they must be collinear +bool IsDiagonal( const face_vert_table_t &v0, const face_vert_table_t &v1 ) +{ + if ( v1.HasEdge(v0.edge0) || v1.HasEdge(v0.edge1) ) + return false; + + return true; +} + + +void Triangulate_r( CUtlVector &out, const CUtlVector &inIndices, const CUtlVector &poly ) +{ + Assert( inIndices.Count() > 2 ); + + // one triangle left, return + if ( inIndices.Count() == 3 ) + { + for ( int i = 0; i < inIndices.Count(); i++ ) + { + out.AddToTail( inIndices[i] ); + } + return; + } + + // check each pair of verts and see if they are diagonal (not on a shared edge) + // if so, split & recurse + for ( int i = 0; i < inIndices.Count(); i++ ) + { + int count = inIndices.Count(); + + // i + count is myself, i + count-1 is previous, so we need to stop at i+count-2 + for ( int j = 2; j < count-1; j++ ) + { + // if these two form a diagonal, split the poly along + // the diagonal and triangulate the two sub-polys + int index = inIndices[i]; + int nextArray = (i+j)%count; + int nextIndex = inIndices[nextArray]; + if ( IsDiagonal(poly[index], poly[nextIndex]) ) + { + // add the poly up to the diagonal + CUtlVector in1; + for ( int k = i; k != nextArray; k = (k+1)%count ) + { + in1.AddToTail(inIndices[k]); + } + in1.AddToTail(nextIndex); + + // add the rest of the poly starting with the diagonal + CUtlVector in2; + in2.AddToTail(index); + for ( int l = nextArray; l != i; l = (l+1)%count ) + { + in2.AddToTail(inIndices[l]); + } + + // triangulate the sub-polys + Triangulate_r( out, in1, poly ); + Triangulate_r( out, in2, poly ); + return; + } + } + } + + // didn't find a diagonal + Assert(0); +} + +/* +================== +FixFaceEdges + +================== +*/ +void FixFaceEdges (face_t **pList, face_t *f) +{ + int p1, p2; + int i; + Vector e2; + vec_t len; + int count[MAX_SUPERVERTS], start[MAX_SUPERVERTS]; + int base; + + if (f->merged || f->split[0] || f->split[1]) + return; + + numsuperverts = 0; + + int originalPoints = f->numpoints; + for (i=0 ; inumpoints ; i++) + { + p1 = f->vertexnums[i]; + p2 = f->vertexnums[(i+1)%f->numpoints]; + + VectorCopy (dvertexes[p1].point, edge_start); + VectorCopy (dvertexes[p2].point, e2); + + FindEdgeVerts (edge_start, e2); + + VectorSubtract (e2, edge_start, edge_dir); + len = VectorNormalize (edge_dir); + + start[i] = numsuperverts; + TestEdge (0, len, p1, p2, 0); + + count[i] = numsuperverts - start[i]; + } + + if (numsuperverts < 3) + { // entire face collapsed + f->numpoints = 0; + c_facecollapse++; + return; + } + + // we want to pick a vertex that doesn't have tjunctions + // on either side, which can cause artifacts on trifans, + // especially underwater + for (i=0 ; inumpoints ; i++) + { + if (count[i] == 1 && count[(i+f->numpoints-1)%f->numpoints] == 1) + break; + } + if (i == f->numpoints) + { + f->badstartvert = true; + c_badstartverts++; + base = 0; + + } + else + { // rotate the vertex order + base = start[i]; + } + + // this may fragment the face if > MAXEDGES + FaceFromSuperverts (pList, f, base); + + // if this is the world, then re-triangulate to sew cracks + if ( f->badstartvert && entity_num == 0 ) + { + CUtlVector poly; + CUtlVector inIndices; + CUtlVector outIndices; + poly.AddMultipleToTail( numsuperverts ); + for ( i = 0; i < originalPoints; i++ ) + { + // edge may not have output any points. Don't mark + if ( !count[i] ) + continue; + // mark each edge the point is a member of + // we'll use this as a fast "is collinear" test + for ( int j = 0; j <= count[i]; j++ ) + { + int polyIndex = (start[i] + j) % numsuperverts; + poly[polyIndex].AddEdge( i ); + } + } + for ( i = 0; i < numsuperverts; i++ ) + { + inIndices.AddToTail( i ); + } + Triangulate_r( outIndices, inIndices, poly ); + dprimitive_t &newPrim = g_primitives[g_numprimitives]; + f->firstPrimID = g_numprimitives; + g_numprimitives++; + f->numPrims = 1; + newPrim.firstIndex = g_numprimindices; + newPrim.firstVert = g_numprimverts; + newPrim.indexCount = outIndices.Count(); + newPrim.vertCount = 0; + newPrim.type = PRIM_TRILIST; + g_numprimindices += newPrim.indexCount; + if ( g_numprimitives > MAX_MAP_PRIMITIVES || g_numprimindices > MAX_MAP_PRIMINDICES ) + { + Error("Too many t-junctions to fix up! (%d prims, max %d :: %d indices, max %d)\n", g_numprimitives, MAX_MAP_PRIMITIVES, g_numprimindices, MAX_MAP_PRIMINDICES ); + } + for ( i = 0; i < outIndices.Count(); i++ ) + { + g_primindices[newPrim.firstIndex + i] = outIndices[i]; + } + } +} + +/* +================== +FixEdges_r +================== +*/ +void FixEdges_r (node_t *node) +{ + int i; + face_t *f; + + if (node->planenum == PLANENUM_LEAF) + { + return; + } + + for (f=node->faces ; f ; f=f->next) + FixFaceEdges (&node->faces, f); + + for (i=0 ; i<2 ; i++) + FixEdges_r (node->children[i]); +} + + +//----------------------------------------------------------------------------- +// Purpose: Fix the t-junctions on detail faces +//----------------------------------------------------------------------------- +void FixLeafFaceEdges( face_t **ppLeafFaceList ) +{ + face_t *f; + + for ( f = *ppLeafFaceList; f; f = f->next ) + { + FixFaceEdges( ppLeafFaceList, f ); + } +} + +/* +=========== +FixTjuncs + +=========== +*/ + +face_t *FixTjuncs (node_t *headnode, face_t *pLeafFaceList) +{ + // snap and merge all vertexes + qprintf ("---- snap verts ----\n"); + memset (hashverts, 0, sizeof(hashverts)); + memset (vertexchain, 0, sizeof(vertexchain)); + c_totalverts = 0; + c_uniqueverts = 0; + c_faceoverflows = 0; + EmitNodeFaceVertexes_r (headnode); + + // UNDONE: This count is wrong with tjuncs off on details - since + + // break edges on tjunctions + qprintf ("---- tjunc ----\n"); + c_tryedges = 0; + c_degenerate = 0; + c_facecollapse = 0; + c_tjunctions = 0; + + if ( g_bAllowDetailCracks ) + { + FixEdges_r (headnode); + EmitLeafFaceVertexes( &pLeafFaceList ); + FixLeafFaceEdges( &pLeafFaceList ); + } + else + { + EmitLeafFaceVertexes( &pLeafFaceList ); + if (!notjunc) + { + FixEdges_r (headnode); + FixLeafFaceEdges( &pLeafFaceList ); + } + } + + + qprintf ("%i unique from %i\n", c_uniqueverts, c_totalverts); + qprintf ("%5i edges degenerated\n", c_degenerate); + qprintf ("%5i faces degenerated\n", c_facecollapse); + qprintf ("%5i edges added by tjunctions\n", c_tjunctions); + qprintf ("%5i faces added by tjunctions\n", c_faceoverflows); + qprintf ("%5i bad start verts\n", c_badstartverts); + + return pLeafFaceList; +} + + +//======================================================== + +int c_faces; + +face_t *AllocFace (void) +{ + static int s_FaceId = 0; + + face_t *f; + + f = (face_t*)malloc(sizeof(*f)); + memset (f, 0, sizeof(*f)); + f->id = s_FaceId; + ++s_FaceId; + + c_faces++; + + return f; +} + +face_t *NewFaceFromFace (face_t *f) +{ + face_t *newf; + + newf = AllocFace (); + *newf = *f; + newf->merged = NULL; + newf->split[0] = newf->split[1] = NULL; + newf->w = NULL; + return newf; +} + +void FreeFace (face_t *f) +{ + if (f->w) + FreeWinding (f->w); + free (f); + c_faces--; +} + + +void FreeFaceList( face_t *pFaces ) +{ + while ( pFaces ) + { + face_t *next = pFaces->next; + + FreeFace( pFaces ); + pFaces = next; + } +} + +//======================================================== + +void GetEdge2_InitOptimizedList() +{ + for( int i=0; i < MAX_MAP_VERTS; i++ ) + g_VertEdgeList[i].RemoveAll(); +} + + +void IntSort( CUtlVector &theList ) +{ + for( int i=0; i < theList.Size()-1; i++ ) + { + if( theList[i] > theList[i+1] ) + { + int temp = theList[i]; + theList[i] = theList[i+1]; + theList[i+1] = temp; + if( i > 0 ) + i -= 2; + else + i = -1; + } + } +} + + +int AddEdge( int v1, int v2, face_t *f ) +{ + if (numedges >= MAX_MAP_EDGES) + Error ("Too many edges in map, max == %d", MAX_MAP_EDGES); + + g_VertEdgeList[v1].AddToTail( numedges ); + g_VertEdgeList[v2].AddToTail( numedges ); + IntSort( g_VertEdgeList[v1] ); + IntSort( g_VertEdgeList[v2] ); + + dedge_t *edge = &dedges[numedges]; + numedges++; + + edge->v[0] = v1; + edge->v[1] = v2; + edgefaces[numedges-1][0] = f; + return numedges - 1; +} + + +/* +================== +GetEdge + +Called by writebsp. +Don't allow four way edges +================== +*/ +int GetEdge2 (int v1, int v2, face_t *f) +{ + dedge_t *edge; + + c_tryedges++; + + if (!noshare) + { + // Check all edges connected to v1. + CUtlVector &theList = g_VertEdgeList[v1]; + for( int i=0; i < theList.Size(); i++ ) + { + int iEdge = theList[i]; + edge = &dedges[iEdge]; + if (v1 == edge->v[1] && v2 == edge->v[0] && edgefaces[iEdge][0]->contents == f->contents) + { + if (edgefaces[iEdge][1]) + continue; + + edgefaces[iEdge][1] = f; + return -iEdge; + } + } + } + + return AddEdge( v1, v2, f ); +} + +/* +=========================================================================== + +FACE MERGING + +=========================================================================== +*/ + +#define CONTINUOUS_EPSILON 0.001 + +/* +============= +TryMergeWinding + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the faces couldn't be merged, or the new face. +The originals will NOT be freed. +============= +*/ +winding_t *TryMergeWinding (winding_t *f1, winding_t *f2, Vector& planenormal) +{ + Vector *p1, *p2, *p3, *p4, *back; + winding_t *newf; + int i, j, k, l; + Vector normal, delta; + vec_t dot; + qboolean keep1, keep2; + + + // + // find a common edge + // + p1 = p2 = NULL; // stop compiler warning + j = 0; // + + for (i=0 ; inumpoints ; i++) + { + p1 = &f1->p[i]; + p2 = &f1->p[(i+1)%f1->numpoints]; + for (j=0 ; jnumpoints ; j++) + { + p3 = &f2->p[j]; + p4 = &f2->p[(j+1)%f2->numpoints]; + for (k=0 ; k<3 ; k++) + { + if (fabs((*p1)[k] - (*p4)[k]) > EQUAL_EPSILON) + break; + if (fabs((*p2)[k] - (*p3)[k]) > EQUAL_EPSILON) + break; + } + if (k==3) + break; + } + if (j < f2->numpoints) + break; + } + + if (i == f1->numpoints) + return NULL; // no matching edges + + // + // check slope of connected lines + // if the slopes are colinear, the point can be removed + // + back = &f1->p[(i+f1->numpoints-1)%f1->numpoints]; + VectorSubtract (*p1, *back, delta); + CrossProduct (planenormal, delta, normal); + VectorNormalize (normal); + + back = &f2->p[(j+2)%f2->numpoints]; + VectorSubtract (*back, *p1, delta); + dot = DotProduct (delta, normal); + if (dot > CONTINUOUS_EPSILON) + return NULL; // not a convex polygon + keep1 = (qboolean)(dot < -CONTINUOUS_EPSILON); + + back = &f1->p[(i+2)%f1->numpoints]; + VectorSubtract (*back, *p2, delta); + CrossProduct (planenormal, delta, normal); + VectorNormalize (normal); + + back = &f2->p[(j+f2->numpoints-1)%f2->numpoints]; + VectorSubtract (*back, *p2, delta); + dot = DotProduct (delta, normal); + if (dot > CONTINUOUS_EPSILON) + return NULL; // not a convex polygon + keep2 = (qboolean)(dot < -CONTINUOUS_EPSILON); + + // + // build the new polygon + // + newf = AllocWinding (f1->numpoints + f2->numpoints); + + // copy first polygon + for (k=(i+1)%f1->numpoints ; k != i ; k=(k+1)%f1->numpoints) + { + if (k==(i+1)%f1->numpoints && !keep2) + continue; + + VectorCopy (f1->p[k], newf->p[newf->numpoints]); + newf->numpoints++; + } + + // copy second polygon + for (l= (j+1)%f2->numpoints ; l != j ; l=(l+1)%f2->numpoints) + { + if (l==(j+1)%f2->numpoints && !keep1) + continue; + VectorCopy (f2->p[l], newf->p[newf->numpoints]); + newf->numpoints++; + } + + return newf; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool OverlaysAreEqual( face_t *f1, face_t *f2 ) +{ + // Check the overlay ids - see if they are the same. + if ( f1->originalface->aOverlayIds.Count() != f2->originalface->aOverlayIds.Count() ) + return false; + + int nOverlayCount = f1->originalface->aOverlayIds.Count(); + for ( int iOverlay = 0; iOverlay < nOverlayCount; ++iOverlay ) + { + int nOverlayId = f1->originalface->aOverlayIds[iOverlay]; + if ( f2->originalface->aOverlayIds.Find( nOverlayId ) == -1 ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool FaceOnWaterBrush( face_t *face ) +{ + side_t *pSide = face->originalface; + if ( !pSide ) + return false; + + if ( pSide->contents & ( CONTENTS_WATER | CONTENTS_SLIME ) ) + return true; + + return false; +} + +/* +============= +TryMerge + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the faces couldn't be merged, or the new face. +The originals will NOT be freed. +============= +*/ +face_t *TryMerge (face_t *f1, face_t *f2, Vector& planenormal) +{ + face_t *newf; + winding_t *nw; + + if (!f1->w || !f2->w) + return NULL; + if (f1->texinfo != f2->texinfo) + return NULL; + if (f1->planenum != f2->planenum) // on front and back sides + return NULL; + if (f1->contents != f2->contents) + return NULL; + if ( f1->originalface->smoothingGroups != f2->originalface->smoothingGroups ) + return NULL; + if ( !OverlaysAreEqual( f1, f2 ) ) + return NULL; + if ( nomergewater && ( FaceOnWaterBrush( f1 ) || FaceOnWaterBrush( f2 ) ) ) + return NULL; + + nw = TryMergeWinding (f1->w, f2->w, planenormal); + if (!nw) + return NULL; + + c_merge++; + newf = NewFaceFromFace (f1); + newf->w = nw; + + f1->merged = newf; + f2->merged = newf; + + return newf; +} + +/* +=============== +MergeFaceList +=============== +*/ +void MergeFaceList(face_t **pList) +{ + face_t *f1, *f2, *end; + face_t *merged; + plane_t *plane; + + merged = NULL; + + for (f1 = *pList; f1 ; f1 = f1->next) + { + if (f1->merged || f1->split[0] || f1->split[1]) + continue; + for (f2 = *pList; f2 != f1 ; f2=f2->next) + { + if (f2->merged || f2->split[0] || f2->split[1]) + continue; + + plane = &g_MainMap->mapplanes[f1->planenum]; + merged = TryMerge (f1, f2, plane->normal); + if (!merged) + continue; + + // add merged to the end of the face list + // so it will be checked against all the faces again + for (end = *pList; end->next ; end = end->next) + ; + merged->next = NULL; + end->next = merged; + break; + } + } +} + +//===================================================================== + +/* +=============== +SubdivideFace + +Chop up faces that are larger than we want in the surface cache +=============== +*/ +void SubdivideFace (face_t **pFaceList, face_t *f) +{ + float mins, maxs; + vec_t v; + vec_t luxelsPerWorldUnit; + int axis, i; + texinfo_t *tex; + Vector temp; + vec_t dist; + winding_t *w, *frontw, *backw; + + if ( f->merged || f->split[0] || f->split[1] ) + return; + +// special (non-surface cached) faces don't need subdivision + tex = &texinfo[f->texinfo]; + + if( tex->flags & SURF_NOLIGHT ) + { + return; + } + + for (axis = 0 ; axis < 2 ; axis++) + { + while (1) + { + mins = 999999; + maxs = -999999; + + VECTOR_COPY (tex->lightmapVecsLuxelsPerWorldUnits[axis], temp); + w = f->w; + for (i=0 ; inumpoints ; i++) + { + v = DotProduct (w->p[i], temp); + if (v < mins) + mins = v; + if (v > maxs) + maxs = v; + } +#if 0 + if (maxs - mins <= 0) + Error ("zero extents"); +#endif + if (maxs - mins <= g_maxLightmapDimension) + break; + + // split it + c_subdivide++; + + luxelsPerWorldUnit = VectorNormalize (temp); + + dist = ( mins + g_maxLightmapDimension - 1 ) / luxelsPerWorldUnit; + + ClipWindingEpsilon (w, temp, dist, ON_EPSILON, &frontw, &backw); + if (!frontw || !backw) + Error ("SubdivideFace: didn't split the polygon"); + + f->split[0] = NewFaceFromFace (f); + f->split[0]->w = frontw; + f->split[0]->next = *pFaceList; + *pFaceList = f->split[0]; + + f->split[1] = NewFaceFromFace (f); + f->split[1]->w = backw; + f->split[1]->next = *pFaceList; + *pFaceList = f->split[1]; + + SubdivideFace (pFaceList, f->split[0]); + SubdivideFace (pFaceList, f->split[1]); + return; + } + } +} + +void SubdivideFaceList(face_t **pFaceList) +{ + face_t *f; + + for (f = *pFaceList ; f ; f=f->next) + { + SubdivideFace (pFaceList, f); + } +} + + +//----------------------------------------------------------------------------- +// Assigns the bottom material to the bottom face +//----------------------------------------------------------------------------- +static bool AssignBottomWaterMaterialToFace( face_t *f ) +{ + // NOTE: This happens *after* cubemap fixup occurs, so we need to get the + // fixed-up bottom material for this + texinfo_t *pTexInfo = &texinfo[f->texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + + char pBottomMatName[512]; + if ( !GetValueFromPatchedMaterial( pMaterialName, "$bottommaterial", pBottomMatName, 512 ) ) + { + if( !Q_stristr( pMaterialName, "nodraw" ) && !Q_stristr( pMaterialName, "toolsskip" ) ) + { + Warning("error: material %s doesn't have a $bottommaterial\n", pMaterialName ); + } + return false; + } + + //Assert( mapplanes[f->planenum].normal.z < 0 ); + texinfo_t newTexInfo; + newTexInfo.flags = pTexInfo->flags; + int j, k; + for (j=0 ; j<2 ; j++) + { + for (k=0 ; k<4 ; k++) + { + newTexInfo.textureVecsTexelsPerWorldUnits[j][k] = pTexInfo->textureVecsTexelsPerWorldUnits[j][k]; + newTexInfo.lightmapVecsLuxelsPerWorldUnits[j][k] = pTexInfo->lightmapVecsLuxelsPerWorldUnits[j][k]; + } + } + newTexInfo.texdata = FindOrCreateTexData( pBottomMatName ); + f->texinfo = FindOrCreateTexInfo( newTexInfo ); + + return true; +} + + +//=========================================================================== + +int c_nodefaces; + +static void SubdivideFaceBySubdivSize( face_t *f, float subdivsize ); +void SubdivideFaceBySubdivSize( face_t *f ); + +/* +============ +FaceFromPortal + +============ +*/ +extern int FindOrCreateTexInfo( const texinfo_t &searchTexInfo ); + +face_t *FaceFromPortal (portal_t *p, int pside) +{ + face_t *f; + side_t *side; + int deltaContents; + + // portal does not bridge different visible contents + side = p->side; + if (!side) + return NULL; + + // allocate a new face + f = AllocFace(); + + // save the original "side" from the map brush -- portal->side + // see FindPortalSide(...) + f->originalface = side; + + // + // save material info + // + f->texinfo = side->texinfo; + f->dispinfo = -1; // all faces with displacement info are created elsewhere + f->smoothingGroups = side->smoothingGroups; + + // save plane info + f->planenum = (side->planenum & ~1) | pside; + if ( entity_num != 0 ) + { + // the brush model renderer doesn't use PLANEBACK, so write the real plane + // inside water faces can be flipped because they are generated on the inside of the brush + if ( p->nodes[pside]->contents & (CONTENTS_WATER|CONTENTS_SLIME) ) + { + f->planenum = (side->planenum & ~1) | pside; + } + else + { + f->planenum = side->planenum; + } + } + + // save portal info + f->portal = p; + f->fogVolumeLeaf = NULL; + + deltaContents = VisibleContents(p->nodes[!pside]->contents^p->nodes[pside]->contents); + + // don't show insides of windows or grates + if ( ((p->nodes[pside]->contents & CONTENTS_WINDOW) && deltaContents == CONTENTS_WINDOW) || + ((p->nodes[pside]->contents & CONTENTS_GRATE) && deltaContents == CONTENTS_GRATE) ) + { + FreeFace( f ); + return NULL; + } + + if ( p->nodes[pside]->contents & MASK_WATER ) + { + f->fogVolumeLeaf = p->nodes[pside]; + } + else if ( p->nodes[!pside]->contents & MASK_WATER ) + { + f->fogVolumeLeaf = p->nodes[!pside]; + } + + // If it's the underside of water, we need to figure out what material to use, etc. + if( ( p->nodes[pside]->contents & CONTENTS_WATER ) && deltaContents == CONTENTS_WATER ) + { + if ( !AssignBottomWaterMaterialToFace( f ) ) + { + FreeFace( f ); + return NULL; + } + } + + // + // generate the winding for the face and save face contents + // + if( pside ) + { + f->w = ReverseWinding(p->winding); + f->contents = p->nodes[1]->contents; + } + else + { + f->w = CopyWinding(p->winding); + f->contents = p->nodes[0]->contents; + } + + f->numPrims = 0; + f->firstPrimID = 0; + + // return the created face + return f; +} + +/* +=============== +MakeFaces_r + +If a portal will make a visible face, +mark the side that originally created it + + solid / empty : solid + solid / water : solid + water / empty : water + water / water : none +=============== +*/ +void MakeFaces_r (node_t *node) +{ + portal_t *p; + int s; + + // recurse down to leafs + if (node->planenum != PLANENUM_LEAF) + { + MakeFaces_r (node->children[0]); + MakeFaces_r (node->children[1]); + + // merge together all visible faces on the node + if (!nomerge) + MergeFaceList(&node->faces); + if (!nosubdiv) + SubdivideFaceList(&node->faces); + + return; + } + + // solid leafs never have visible faces + if (node->contents & CONTENTS_SOLID) + return; + + // see which portals are valid + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + + p->face[s] = FaceFromPortal (p, s); + if (p->face[s]) + { + c_nodefaces++; + p->face[s]->next = p->onnode->faces; + p->onnode->faces = p->face[s]; + } + } +} + +typedef winding_t *pwinding_t; + +static void PrintWinding( winding_t *w ) +{ + int i; + Msg( "\t---\n" ); + for( i = 0; i < w->numpoints; i++ ) + { + Msg( "\t%f %f %f\n", w->p[i].x, w->p[i].y, w->p[i].z ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a winding to the current list of primverts +// Input : *w - the winding +// *pIndices - The output indices +// vertStart - the starting vert index +// vertCount - current count +// Output : int - output count including new verts from this winding +//----------------------------------------------------------------------------- +int AddWindingToPrimverts( const winding_t *w, unsigned short *pIndices, int vertStart, int vertCount ) +{ + for( int i = 0; i < w->numpoints; i++ ) + { + int j; + for( j = vertStart; j < vertStart + vertCount; j++ ) + { + Vector tmp = g_primverts[j].pos - w->p[i]; + + if( tmp.LengthSqr() < POINT_EPSILON*POINT_EPSILON ) + { + pIndices[i] = j; + break; + } + } + if ( j >= vertStart + vertCount ) + { + pIndices[i] = j; + g_primverts[j].pos = w->p[i]; + vertCount++; + g_numprimverts++; + if ( g_numprimverts > MAX_MAP_PRIMVERTS ) + { + Error( "Exceeded max water verts.\nIncrease surface subdivision size or lower your subdivision size in vmt files! (%d>%d)\n", + ( int )g_numprimverts, ( int )MAX_MAP_PRIMVERTS ); + } + } + } + + return vertCount; +} + + + +#pragma optimize( "g", off ) +#define USE_TRISTRIPS + +// UNDONE: Should split this function into subdivide and primitive building parts +// UNDONE: We should try building strips of shared verts for all water faces in a leaf +// since those will be drawn concurrently anyway. It should be more efficient. +static void SubdivideFaceBySubdivSize( face_t *f, float subdivsize ) +{ + // garymcthack - REFACTOR ME!!! + + vec_t dummy; + Vector hackNormal; + WindingPlane( f->w, hackNormal, &dummy ); + + // HACK - only subdivide stuff that is facing up or down (for water) + if( fabs(hackNormal[2]) < .9f ) + { + return; + } + + // Get the extents of the surface. + // garymcthack - this assumes a surface of constant z for now (for water). . can generalize later. + subdivsize = ( int )subdivsize; + winding_t *w; + w = CopyWinding( f->w ); + + Vector min, max; + WindingBounds( w, min, max ); + +#if 0 + Msg( "START WINDING: \n" ); + PrintWinding( w ); +#endif + int xStart, yStart, xEnd, yEnd, xSteps, ySteps; + xStart = ( int )subdivsize * ( int )( ( min[0] - subdivsize ) / subdivsize ); + xEnd = ( int )subdivsize * ( int )( ( max[0] + subdivsize ) / subdivsize ); + yStart = ( int )subdivsize * ( int )( ( min[1] - subdivsize ) / subdivsize ); + yEnd = ( int )subdivsize * ( int )( ( max[1] + subdivsize ) / subdivsize ); + xSteps = ( xEnd - xStart ) / subdivsize; + ySteps = ( yEnd - yStart ) / subdivsize; + int x, y; + int xi, yi; + winding_t **windings = ( winding_t ** )new pwinding_t[xSteps * ySteps]; + memset( windings, 0, sizeof( winding_t * ) * xSteps * ySteps ); + + for( yi = 0, y = yStart; y < yEnd; y += ( int )subdivsize, yi++ ) + { + for( xi = 0, x = xStart; x < xEnd; x += ( int )subdivsize, xi++ ) + { + winding_t *tempWinding, *frontWinding, *backWinding; + float planeDist; + Vector normal; + normal.Init( 1.0f, 0.0f, 0.0f ); + planeDist = ( float )x; + tempWinding = CopyWinding( w ); + ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, + &frontWinding, &backWinding ); + if( tempWinding ) + { + FreeWinding( tempWinding ); + } + if( backWinding ) + { + FreeWinding( backWinding ); + } + if( !frontWinding ) + { + continue; + } + tempWinding = frontWinding; + + normal.Init( -1.0f, 0.0f, 0.0f ); + planeDist = -( float )( x + subdivsize ); + ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, + &frontWinding, &backWinding ); + if( tempWinding ) + { + FreeWinding( tempWinding ); + } + if( backWinding ) + { + FreeWinding( backWinding ); + } + if( !frontWinding ) + { + continue; + } + tempWinding = frontWinding; + + normal.Init( 0.0f, 1.0f, 0.0f ); + planeDist = ( float )y; + ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, + &frontWinding, &backWinding ); + if( tempWinding ) + { + FreeWinding( tempWinding ); + } + if( backWinding ) + { + FreeWinding( backWinding ); + } + if( !frontWinding ) + { + continue; + } + tempWinding = frontWinding; + + normal.Init( 0.0f, -1.0f, 0.0f ); + planeDist = -( float )( y + subdivsize ); + ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, + &frontWinding, &backWinding ); + if( tempWinding ) + { + FreeWinding( tempWinding ); + } + if( backWinding ) + { + FreeWinding( backWinding ); + } + if( !frontWinding ) + { + continue; + } + +#if 0 + Msg( "output winding:\n" ); + PrintWinding( frontWinding ); +#endif + + if( frontWinding ) + { + windings[xi + yi * xSteps] = frontWinding; + } + } + } + FreeWinding( w ); + dprimitive_t &newPrim = g_primitives[g_numprimitives]; + f->firstPrimID = g_numprimitives; + f->numPrims = 1; + newPrim.firstIndex = g_numprimindices; + newPrim.firstVert = g_numprimverts; + newPrim.indexCount = 0; + newPrim.vertCount = 0; +#ifdef USE_TRISTRIPS + newPrim.type = PRIM_TRISTRIP; +#else + newPrim.type = PRIM_TRILIST; +#endif + + CUtlVector triListIndices; + int i; + for( i = 0; i < xSteps * ySteps; i++ ) + { + if( !windings[i] ) + { + continue; + } + unsigned short *pIndices = + ( unsigned short * )_alloca( windings[i]->numpoints * sizeof( unsigned short ) ); + // find indices for the verts. + newPrim.vertCount = AddWindingToPrimverts( windings[i], pIndices, newPrim.firstVert, newPrim.vertCount ); + + // Now that we have indices for the verts, fan-tesselate the polygon and spit out tris. + for( int j = 0; j < windings[i]->numpoints - 2; j++ ) + { + triListIndices.AddToTail( pIndices[0] ); + triListIndices.AddToTail( pIndices[j+1] ); + triListIndices.AddToTail( pIndices[j+2] ); + } + } + + delete [] windings; + // We've already updated the verts and have a trilist. . let's strip it! + if( !triListIndices.Size() ) + { + return; + } + +#ifdef USE_TRISTRIPS + int numTristripIndices; + WORD *pStripIndices = NULL; + Stripify( triListIndices.Size() / 3, triListIndices.Base(), &numTristripIndices, + &pStripIndices ); + Assert( pStripIndices ); + + // FIXME: Should also call ComputeVertexPermutation and reorder the verts. + + for( i = 0; i < numTristripIndices; i++ ) + { + Assert( pStripIndices[i] >= newPrim.firstVert && + pStripIndices[i] < newPrim.firstVert + newPrim.vertCount ); + g_primindices[newPrim.firstIndex + newPrim.indexCount] = pStripIndices[i]; + newPrim.indexCount++; + g_numprimindices++; + if( g_numprimindices > MAX_MAP_PRIMINDICES ) + { + Error( "Exceeded max water indicies.\nIncrease surface subdivision size! (%d>%d)\n", g_numprimindices, MAX_MAP_PRIMINDICES ); + } + } + delete [] pStripIndices; +#else + for( i = 0; i < triListIndices.Size(); i++ ) + { + g_primindices[newPrim.firstIndex + newPrim.indexCount] = triListIndices[i]; + newPrim.indexCount++; + g_numprimindices++; + if( g_numprimindices > MAX_MAP_PRIMINDICES ) + { + Error( "Exceeded max water indicies.\nIncrease surface subdivision size! (%d>%d)\n", g_numprimindices, MAX_MAP_PRIMINDICES ); + } + } +#endif + g_numprimitives++; // don't increment until we get here and are sure that we have a primitive. + if( g_numprimitives > MAX_MAP_PRIMITIVES ) + { + Error( "Exceeded max water primitives.\nIncrease surface subdivision size! (%d>%d)\n", ( int )g_numprimitives, ( int )MAX_MAP_PRIMITIVES ); + } +} + +void SubdivideFaceBySubdivSize( face_t *f ) +{ + if( f->numpoints == 0 || f->split[0] || f->split[1] || f->merged || !f->w ) + { + return; + } + // see if the face needs to be subdivided. + texinfo_t *pTexInfo = &texinfo[f->texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + bool bFound; + const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + MaterialSystemMaterial_t matID = + FindOriginalMaterial( pMaterialName, &bFound, false ); + + if( !bFound ) + { + return; + } + const char *subdivsizeString = GetMaterialVar( matID, "$subdivsize" ); + if( subdivsizeString ) + { + float subdivSize = atof( subdivsizeString ); + if( subdivSize > 0.0f ) + { + // NOTE: Subdivision is unsupported and should be phased out + Warning("Using subdivision on %s\n", pMaterialName ); + SubdivideFaceBySubdivSize( f, subdivSize ); + } + } +} + +void SplitSubdividedFaces_Node_r( node_t *node ) +{ + if (node->planenum == PLANENUM_LEAF) + { + return; + } + face_t *f; + for( f = node->faces; f ;f = f->next ) + { + SubdivideFaceBySubdivSize( f ); + } + + // + // recursively output the other nodes + // + SplitSubdividedFaces_Node_r( node->children[0] ); + SplitSubdividedFaces_Node_r( node->children[1] ); +} + +void SplitSubdividedFaces( face_t *pLeafFaceList, node_t *headnode ) +{ + // deal with leaf faces. + face_t *f = pLeafFaceList; + while ( f ) + { + SubdivideFaceBySubdivSize( f ); + f = f->next; + } + + // deal with node faces. + SplitSubdividedFaces_Node_r( headnode ); +} + +#pragma optimize( "", on ) + +/* +============ +MakeFaces +============ +*/ +void MakeFaces (node_t *node) +{ + qprintf ("--- MakeFaces ---\n"); + c_merge = 0; + c_subdivide = 0; + c_nodefaces = 0; + + MakeFaces_r (node); + + qprintf ("%5i makefaces\n", c_nodefaces); + qprintf ("%5i merged\n", c_merge); + qprintf ("%5i subdivided\n", c_subdivide); } \ No newline at end of file diff --git a/mp/src/utils/vbsp/faces.h b/mp/src/utils/vbsp/faces.h index cfebc24f..79d78954 100644 --- a/mp/src/utils/vbsp/faces.h +++ b/mp/src/utils/vbsp/faces.h @@ -1,20 +1,20 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef FACES_H -#define FACES_H -#ifdef _WIN32 -#pragma once -#endif - - -void GetEdge2_InitOptimizedList(); // Call this before calling GetEdge2() on a bunch of edges. -int AddEdge( int v1, int v2, face_t *f ); -int GetEdge2(int v1, int v2, face_t *f); - - -#endif // FACES_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef FACES_H +#define FACES_H +#ifdef _WIN32 +#pragma once +#endif + + +void GetEdge2_InitOptimizedList(); // Call this before calling GetEdge2() on a bunch of edges. +int AddEdge( int v1, int v2, face_t *f ); +int GetEdge2(int v1, int v2, face_t *f); + + +#endif // FACES_H diff --git a/mp/src/utils/vbsp/glfile.cpp b/mp/src/utils/vbsp/glfile.cpp index 236845bc..361d40b0 100644 --- a/mp/src/utils/vbsp/glfile.cpp +++ b/mp/src/utils/vbsp/glfile.cpp @@ -1,219 +1,219 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "vbsp.h" - -int c_glfaces; - -int PortalVisibleSides (portal_t *p) -{ - int fcon, bcon; - - if (!p->onnode) - return 0; // outside - - fcon = p->nodes[0]->contents; - bcon = p->nodes[1]->contents; - - // same contents never create a face - if (fcon == bcon) - return 0; - - // FIXME: is this correct now? - if (!fcon) - return 1; - if (!bcon) - return 2; - return 0; -} - -void OutputWinding (winding_t *w, FileHandle_t glview) -{ - static int level = 128; - vec_t light; - int i; - - CmdLib_FPrintf( glview, "%i\n", w->numpoints); - level+=28; - light = (level&255)/255.0; - for (i=0 ; inumpoints ; i++) - { - CmdLib_FPrintf(glview, "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n", - w->p[i][0], - w->p[i][1], - w->p[i][2], - light, - light, - light); - } - //CmdLib_FPrintf(glview, "\n"); -} - -void OutputWindingColor (winding_t *w, FileHandle_t glview, int r, int g, int b) -{ - int i; - - CmdLib_FPrintf( glview, "%i\n", w->numpoints); - float lr = r * (1.0f/255.0f); - float lg = g * (1.0f/255.0f); - float lb = b * (1.0f/255.0f); - for (i=0 ; inumpoints ; i++) - { - CmdLib_FPrintf(glview, "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n", - w->p[i][0], - w->p[i][1], - w->p[i][2], - lr, - lg, - lb); - } - //CmdLib_FPrintf(glview, "\n"); -} - -/* -============= -OutputPortal -============= -*/ -void OutputPortal (portal_t *p, FileHandle_t glview) -{ - winding_t *w; - int sides; - - sides = PortalVisibleSides (p); - if (!sides) - return; - - c_glfaces++; - - w = p->winding; - - if (sides == 2) // back side - w = ReverseWinding (w); - - OutputWinding (w, glview); - - if (sides == 2) - FreeWinding(w); -} - -/* -============= -WriteGLView_r -============= -*/ -void WriteGLView_r (node_t *node, FileHandle_t glview) -{ - portal_t *p, *nextp; - - if (node->planenum != PLANENUM_LEAF) - { - WriteGLView_r (node->children[0], glview); - WriteGLView_r (node->children[1], glview); - return; - } - - // write all the portals - for (p=node->portals ; p ; p=nextp) - { - if (p->nodes[0] == node) - { - OutputPortal (p, glview); - nextp = p->next[0]; - } - else - nextp = p->next[1]; - } -} - - -void WriteGLViewFaces_r( node_t *node, FileHandle_t glview ) -{ - portal_t *p, *nextp; - - if (node->planenum != PLANENUM_LEAF) - { - WriteGLViewFaces_r (node->children[0], glview); - WriteGLViewFaces_r (node->children[1], glview); - return; - } - - // write all the portals - for (p=node->portals ; p ; p=nextp) - { - int s = (p->nodes[1] == node); - - if ( p->face[s] ) - { - OutputWinding( p->face[s]->w, glview ); - } - nextp = p->next[s]; - } -} - -/* -============= -WriteGLView -============= -*/ -void WriteGLView (tree_t *tree, char *source) -{ - char name[1024]; - FileHandle_t glview; - - c_glfaces = 0; - sprintf (name, "%s%s.gl",outbase, source); - Msg("Writing %s\n", name); - - glview = g_pFileSystem->Open( name, "w" ); - if (!glview) - Error ("Couldn't open %s", name); - WriteGLView_r (tree->headnode, glview); - g_pFileSystem->Close( glview ); - - Msg("%5i c_glfaces\n", c_glfaces); -} - - -void WriteGLViewFaces( tree_t *tree, const char *pName ) -{ - char name[1024]; - FileHandle_t glview; - - c_glfaces = 0; - sprintf (name, "%s%s.gl", outbase, pName); - Msg("Writing %s\n", name); - - glview = g_pFileSystem->Open( name, "w" ); - if (!glview) - Error ("Couldn't open %s", name); - WriteGLViewFaces_r (tree->headnode, glview); - g_pFileSystem->Close( glview ); - - Msg("%5i c_glfaces\n", c_glfaces); -} - - -void WriteGLViewBrushList( bspbrush_t *pList, const char *pName ) -{ - char name[1024]; - FileHandle_t glview; - - sprintf (name, "%s%s.gl", outbase, pName ); - Msg("Writing %s\n", name); - - glview = g_pFileSystem->Open( name, "w" ); - if (!glview) - Error ("Couldn't open %s", name); - for ( bspbrush_t *pBrush = pList; pBrush; pBrush = pBrush->next ) - { - for (int i = 0; i < pBrush->numsides; i++ ) - OutputWinding( pBrush->sides[i].winding, glview ); - } - g_pFileSystem->Close( glview ); -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" + +int c_glfaces; + +int PortalVisibleSides (portal_t *p) +{ + int fcon, bcon; + + if (!p->onnode) + return 0; // outside + + fcon = p->nodes[0]->contents; + bcon = p->nodes[1]->contents; + + // same contents never create a face + if (fcon == bcon) + return 0; + + // FIXME: is this correct now? + if (!fcon) + return 1; + if (!bcon) + return 2; + return 0; +} + +void OutputWinding (winding_t *w, FileHandle_t glview) +{ + static int level = 128; + vec_t light; + int i; + + CmdLib_FPrintf( glview, "%i\n", w->numpoints); + level+=28; + light = (level&255)/255.0; + for (i=0 ; inumpoints ; i++) + { + CmdLib_FPrintf(glview, "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n", + w->p[i][0], + w->p[i][1], + w->p[i][2], + light, + light, + light); + } + //CmdLib_FPrintf(glview, "\n"); +} + +void OutputWindingColor (winding_t *w, FileHandle_t glview, int r, int g, int b) +{ + int i; + + CmdLib_FPrintf( glview, "%i\n", w->numpoints); + float lr = r * (1.0f/255.0f); + float lg = g * (1.0f/255.0f); + float lb = b * (1.0f/255.0f); + for (i=0 ; inumpoints ; i++) + { + CmdLib_FPrintf(glview, "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n", + w->p[i][0], + w->p[i][1], + w->p[i][2], + lr, + lg, + lb); + } + //CmdLib_FPrintf(glview, "\n"); +} + +/* +============= +OutputPortal +============= +*/ +void OutputPortal (portal_t *p, FileHandle_t glview) +{ + winding_t *w; + int sides; + + sides = PortalVisibleSides (p); + if (!sides) + return; + + c_glfaces++; + + w = p->winding; + + if (sides == 2) // back side + w = ReverseWinding (w); + + OutputWinding (w, glview); + + if (sides == 2) + FreeWinding(w); +} + +/* +============= +WriteGLView_r +============= +*/ +void WriteGLView_r (node_t *node, FileHandle_t glview) +{ + portal_t *p, *nextp; + + if (node->planenum != PLANENUM_LEAF) + { + WriteGLView_r (node->children[0], glview); + WriteGLView_r (node->children[1], glview); + return; + } + + // write all the portals + for (p=node->portals ; p ; p=nextp) + { + if (p->nodes[0] == node) + { + OutputPortal (p, glview); + nextp = p->next[0]; + } + else + nextp = p->next[1]; + } +} + + +void WriteGLViewFaces_r( node_t *node, FileHandle_t glview ) +{ + portal_t *p, *nextp; + + if (node->planenum != PLANENUM_LEAF) + { + WriteGLViewFaces_r (node->children[0], glview); + WriteGLViewFaces_r (node->children[1], glview); + return; + } + + // write all the portals + for (p=node->portals ; p ; p=nextp) + { + int s = (p->nodes[1] == node); + + if ( p->face[s] ) + { + OutputWinding( p->face[s]->w, glview ); + } + nextp = p->next[s]; + } +} + +/* +============= +WriteGLView +============= +*/ +void WriteGLView (tree_t *tree, char *source) +{ + char name[1024]; + FileHandle_t glview; + + c_glfaces = 0; + sprintf (name, "%s%s.gl",outbase, source); + Msg("Writing %s\n", name); + + glview = g_pFileSystem->Open( name, "w" ); + if (!glview) + Error ("Couldn't open %s", name); + WriteGLView_r (tree->headnode, glview); + g_pFileSystem->Close( glview ); + + Msg("%5i c_glfaces\n", c_glfaces); +} + + +void WriteGLViewFaces( tree_t *tree, const char *pName ) +{ + char name[1024]; + FileHandle_t glview; + + c_glfaces = 0; + sprintf (name, "%s%s.gl", outbase, pName); + Msg("Writing %s\n", name); + + glview = g_pFileSystem->Open( name, "w" ); + if (!glview) + Error ("Couldn't open %s", name); + WriteGLViewFaces_r (tree->headnode, glview); + g_pFileSystem->Close( glview ); + + Msg("%5i c_glfaces\n", c_glfaces); +} + + +void WriteGLViewBrushList( bspbrush_t *pList, const char *pName ) +{ + char name[1024]; + FileHandle_t glview; + + sprintf (name, "%s%s.gl", outbase, pName ); + Msg("Writing %s\n", name); + + glview = g_pFileSystem->Open( name, "w" ); + if (!glview) + Error ("Couldn't open %s", name); + for ( bspbrush_t *pBrush = pList; pBrush; pBrush = pBrush->next ) + { + for (int i = 0; i < pBrush->numsides; i++ ) + OutputWinding( pBrush->sides[i].winding, glview ); + } + g_pFileSystem->Close( glview ); +} diff --git a/mp/src/utils/vbsp/ivp.cpp b/mp/src/utils/vbsp/ivp.cpp index 585f0904..3c234c2f 100644 --- a/mp/src/utils/vbsp/ivp.cpp +++ b/mp/src/utils/vbsp/ivp.cpp @@ -1,1656 +1,1656 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include -#include "mathlib/vector.h" -#include "bspfile.h" -#include "bsplib.h" -#include "cmdlib.h" -#include "physdll.h" -#include "utlvector.h" -#include "vbsp.h" -#include "phyfile.h" -#include -#include "KeyValues.h" -#include "UtlBuffer.h" -#include "utlsymbol.h" -#include "utlrbtree.h" -#include "ivp.h" -#include "disp_ivp.h" -#include "materialpatch.h" -#include "bitvec.h" - -// bit per leaf -typedef CBitVec leafbitarray_t; - -// parameters for conversion to vphysics -#define NO_SHRINK 0.0f -// NOTE: vphysics maintains a minimum separation radius between objects -// This radius is set to 0.25, but it's symmetric. So shrinking potentially moveable -// brushes by 0.5 in every direction ensures that these brushes can be constructed -// touching the world, and constrained in place without collisions or friction -// UNDONE: Add a key to disable this shrinking if necessary -#define VPHYSICS_SHRINK (0.5f) // shrink BSP brushes by this much for collision -#define VPHYSICS_MERGE 0.01f // merge verts closer than this - -void EmitPhysCollision(); - -IPhysicsCollision *physcollision = NULL; -extern IPhysicsSurfaceProps *physprops; - -// a list of all of the materials in the world model -static CUtlVector s_WorldPropList; - -//----------------------------------------------------------------------------- -// Purpose: Write key/value pairs out to a memory buffer -//----------------------------------------------------------------------------- -CTextBuffer::CTextBuffer( void ) -{ -} -CTextBuffer::~CTextBuffer( void ) -{ -} - -void CTextBuffer::WriteText( const char *pText ) -{ - int len = strlen( pText ); - CopyData( pText, len ); -} - -void CTextBuffer::WriteIntKey( const char *pKeyName, int outputData ) -{ - char tmp[1024]; - - // FAIL! - if ( strlen(pKeyName) > 1000 ) - { - Msg("Error writing collision data %s\n", pKeyName ); - return; - } - sprintf( tmp, "\"%s\" \"%d\"\n", pKeyName, outputData ); - CopyData( tmp, strlen(tmp) ); -} - -void CTextBuffer::WriteStringKey( const char *pKeyName, const char *outputData ) -{ - CopyStringQuotes( pKeyName ); - CopyData( " ", 1 ); - CopyStringQuotes( outputData ); - CopyData( "\n", 1 ); -} - -void CTextBuffer::WriteFloatKey( const char *pKeyName, float outputData ) -{ - char tmp[1024]; - - // FAIL! - if ( strlen(pKeyName) > 1000 ) - { - Msg("Error writing collision data %s\n", pKeyName ); - return; - } - sprintf( tmp, "\"%s\" \"%f\"\n", pKeyName, outputData ); - CopyData( tmp, strlen(tmp) ); -} - -void CTextBuffer::WriteFloatArrayKey( const char *pKeyName, const float *outputData, int count ) -{ - char tmp[1024]; - - // FAIL! - if ( strlen(pKeyName) > 1000 ) - { - Msg("Error writing collision data %s\n", pKeyName ); - return; - } - sprintf( tmp, "\"%s\" \"", pKeyName ); - for ( int i = 0; i < count; i++ ) - { - char buf[80]; - - sprintf( buf, "%f ", outputData[i] ); - strcat( tmp, buf ); - } - strcat( tmp, "\"\n" ); - - CopyData( tmp, strlen(tmp) ); -} - -void CTextBuffer::CopyStringQuotes( const char *pString ) -{ - CopyData( "\"", 1 ); - CopyData( pString, strlen(pString) ); - CopyData( "\"", 1 ); -} - -void CTextBuffer::Terminate( void ) -{ - CopyData( "\0", 1 ); -} - -void CTextBuffer::CopyData( const char *pData, int len ) -{ - int offset = m_buffer.AddMultipleToTail( len ); - memcpy( m_buffer.Base() + offset, pData, len ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Writes a glview text file containing the collision surface in question -// Input : *pCollide - -// *pFilename - -//----------------------------------------------------------------------------- -void DumpCollideToGlView( CPhysCollide *pCollide, const char *pFilename ) -{ - if ( !pCollide ) - return; - - Msg("Writing %s...\n", pFilename ); - Vector *outVerts; - int vertCount = physcollision->CreateDebugMesh( pCollide, &outVerts ); - FILE *fp = fopen( pFilename, "w" ); - int triCount = vertCount / 3; - int vert = 0; - for ( int i = 0; i < triCount; i++ ) - { - fprintf( fp, "3\n" ); - fprintf( fp, "%6.3f %6.3f %6.3f 1 0 0\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z ); - vert++; - fprintf( fp, "%6.3f %6.3f %6.3f 0 1 0\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z ); - vert++; - fprintf( fp, "%6.3f %6.3f %6.3f 0 0 1\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z ); - vert++; - } - fclose( fp ); - physcollision->DestroyDebugMesh( vertCount, outVerts ); -} - - -void DumpCollideToPHY( CPhysCollide *pCollide, CTextBuffer *text, const char *pFilename ) -{ - Msg("Writing %s...\n", pFilename ); - FILE *fp = fopen( pFilename, "wb" ); - phyheader_t header; - header.size = sizeof(header); - header.id = 0; - header.checkSum = 0; - header.solidCount = 1; - fwrite( &header, sizeof(header), 1, fp ); - int size = physcollision->CollideSize( pCollide ); - fwrite( &size, sizeof(int), 1, fp ); - - char *buf = (char *)malloc( size ); - physcollision->CollideWrite( buf, pCollide ); - fwrite( buf, size, 1, fp ); - - fwrite( text->GetData(), text->GetSize(), 1, fp ); - fclose( fp ); - free( buf ); -} - -CPhysCollisionEntry::CPhysCollisionEntry( CPhysCollide *pCollide ) -{ - m_pCollide = pCollide; -} - -unsigned int CPhysCollisionEntry::GetCollisionBinarySize() -{ - return physcollision->CollideSize( m_pCollide ); -} - -unsigned int CPhysCollisionEntry::WriteCollisionBinary( char *pDest ) -{ - return physcollision->CollideWrite( pDest, m_pCollide ); -} - -void CPhysCollisionEntry::DumpCollideFileName( const char *pName, int modelIndex, CTextBuffer *pTextBuffer ) -{ - char tmp[128]; - sprintf( tmp, "%s%03d.phy", pName, modelIndex ); - DumpCollideToPHY( m_pCollide, pTextBuffer, tmp ); - sprintf( tmp, "%s%03d.txt", pName, modelIndex ); - DumpCollideToGlView( m_pCollide, tmp ); -} - - -class CPhysCollisionEntrySolid : public CPhysCollisionEntry -{ -public: - CPhysCollisionEntrySolid( CPhysCollide *pCollide, const char *pMaterialName, float mass ); - - virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); - virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); - -private: - float m_volume; - float m_mass; - const char *m_pMaterial; -}; - - -CPhysCollisionEntrySolid::CPhysCollisionEntrySolid( CPhysCollide *pCollide, const char *pMaterialName, float mass ) - : CPhysCollisionEntry( pCollide ) -{ - m_volume = physcollision->CollideVolume( m_pCollide ); - m_mass = mass; - m_pMaterial = pMaterialName; -} - -void CPhysCollisionEntrySolid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) -{ - DumpCollideFileName( "collide", modelIndex, pTextBuffer ); -} - -void CPhysCollisionEntrySolid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) -{ - pTextBuffer->WriteText( "solid {\n" ); - pTextBuffer->WriteIntKey( "index", collideIndex ); - pTextBuffer->WriteFloatKey( "mass", m_mass ); - if ( m_pMaterial ) - { - pTextBuffer->WriteStringKey( "surfaceprop", m_pMaterial ); - } - if ( m_volume != 0.f ) - { - pTextBuffer->WriteFloatKey( "volume", m_volume ); - } - pTextBuffer->WriteText( "}\n" ); -} - - -class CPhysCollisionEntryStaticSolid : public CPhysCollisionEntry -{ -public: - CPhysCollisionEntryStaticSolid ( CPhysCollide *pCollide, int contentsMask ); - - virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); - virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); - -private: - int m_contentsMask; -}; - - -CPhysCollisionEntryStaticSolid ::CPhysCollisionEntryStaticSolid ( CPhysCollide *pCollide, int contentsMask ) - : CPhysCollisionEntry( pCollide ), m_contentsMask(contentsMask) -{ -} - -void CPhysCollisionEntryStaticSolid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) -{ - char tmp[128]; - sprintf( tmp, "static%02d", modelIndex ); - DumpCollideFileName( tmp, collideIndex, pTextBuffer ); -} - -void CPhysCollisionEntryStaticSolid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) -{ - pTextBuffer->WriteText( "staticsolid {\n" ); - pTextBuffer->WriteIntKey( "index", collideIndex ); - pTextBuffer->WriteIntKey( "contents", m_contentsMask ); - pTextBuffer->WriteText( "}\n" ); -} - -CPhysCollisionEntryStaticMesh::CPhysCollisionEntryStaticMesh( CPhysCollide *pCollide, const char *pMaterialName ) - : CPhysCollisionEntry( pCollide ) -{ - m_pMaterial = pMaterialName; -} - -void CPhysCollisionEntryStaticMesh::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) -{ - char tmp[128]; - sprintf( tmp, "mesh%02d", modelIndex ); - DumpCollideFileName( tmp, collideIndex, pTextBuffer ); -} - -void CPhysCollisionEntryStaticMesh::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) -{ - pTextBuffer->WriteText( "staticsolid {\n" ); - pTextBuffer->WriteIntKey( "index", collideIndex ); - pTextBuffer->WriteText( "}\n" ); -} - -class CPhysCollisionEntryFluid : public CPhysCollisionEntry -{ -public: - ~CPhysCollisionEntryFluid(); - CPhysCollisionEntryFluid( CPhysCollide *pCollide, const char *pSurfaceProp, float damping, const Vector &normal, float dist, int nContents ); - - virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); - virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); - -private: - char *m_pSurfaceProp; - float m_damping; - Vector m_surfaceNormal; - float m_surfaceDist; - int m_contentsMask; -}; - - -CPhysCollisionEntryFluid::CPhysCollisionEntryFluid( CPhysCollide *pCollide, const char *pSurfaceProp, float damping, const Vector &normal, float dist, int nContents ) - : CPhysCollisionEntry( pCollide ) -{ - m_surfaceNormal = normal; - m_surfaceDist = dist; - m_pSurfaceProp = new char[strlen(pSurfaceProp)+1]; - strcpy( m_pSurfaceProp, pSurfaceProp ); - m_damping = damping; - m_contentsMask = nContents; -} - -CPhysCollisionEntryFluid::~CPhysCollisionEntryFluid() -{ - delete[] m_pSurfaceProp; -} - -void CPhysCollisionEntryFluid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) -{ - char tmp[128]; - sprintf( tmp, "water%02d", modelIndex ); - DumpCollideFileName( tmp, collideIndex, pTextBuffer ); -} - -void CPhysCollisionEntryFluid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) -{ - pTextBuffer->WriteText( "fluid {\n" ); - pTextBuffer->WriteIntKey( "index", collideIndex ); - pTextBuffer->WriteStringKey( "surfaceprop", m_pSurfaceProp ); // write out water material - pTextBuffer->WriteFloatKey( "damping", m_damping ); // write out water damping - pTextBuffer->WriteIntKey( "contents", m_contentsMask ); // write out water contents - float array[4]; - m_surfaceNormal.CopyToArray( array ); - array[3] = m_surfaceDist; - pTextBuffer->WriteFloatArrayKey( "surfaceplane", array, 4 ); // write out water surface plane - pTextBuffer->WriteFloatArrayKey( "currentvelocity", vec3_origin.Base(), 3 ); // write out water velocity - pTextBuffer->WriteText( "}\n" ); -} - -// Get an index into the prop list of this prop (add it if necessary) -static int PropIndex( CUtlVector &propList, int propIndex ) -{ - for ( int i = 0; i < propList.Count(); i++ ) - { - if ( propList[i] == propIndex ) - return i+1; - } - - if ( propList.Count() < 126 ) - { - return propList.AddToTail( propIndex )+1; - } - - return 0; -} - -int RemapWorldMaterial( int materialIndexIn ) -{ - return PropIndex( s_WorldPropList, materialIndexIn ); -} - -typedef struct -{ - float normal[3]; - float dist; -} listplane_t; - -static void AddListPlane( CUtlVector *list, float x, float y, float z, float d ) -{ - listplane_t plane; - plane.normal[0] = x; - plane.normal[1] = y; - plane.normal[2] = z; - plane.dist = d; - - list->AddToTail( plane ); -} - -class CPlaneList -{ -public: - - CPlaneList( float shrink, float merge ); - ~CPlaneList( void ); - - void AddConvex( CPhysConvex *pConvex ); - - // add the brushes to the model - int AddBrushes( void ); - - // Adds a single brush as a convex object - void ReferenceBrush( int brushnumber ); - bool IsBrushReferenced( int brushnumber ); - - void ReferenceLeaf( int leafIndex ); - bool IsLeafReferenced( int leafIndex ); - int GetFirstBrushSide(); - -private: - - CPhysConvex *BuildConvexForBrush( int brushnumber, float shrink, CPhysCollide *pCollideTest, float shrinkMinimum ); - -public: - CUtlVector m_convex; - - CUtlVector m_leafList; - int m_contentsMask; - - float m_shrink; - float m_merge; - bool *m_brushAdded; - float m_totalVolume; -}; - -CPlaneList::CPlaneList( float shrink, float merge ) -{ - m_shrink = shrink; - m_merge = merge; - m_contentsMask = MASK_SOLID; - m_brushAdded = new bool[numbrushes]; - memset( m_brushAdded, 0, sizeof(bool) * numbrushes ); - m_totalVolume = 0; - m_leafList.Purge(); -} - - -CPlaneList::~CPlaneList( void ) -{ - delete[] m_brushAdded; -} - - -void CPlaneList::AddConvex( CPhysConvex *pConvex ) -{ - if ( pConvex ) - { - m_totalVolume += physcollision->ConvexVolume( pConvex ); - m_convex.AddToTail( pConvex ); - } -} - -// Adds a single brush as a convex object -void CPlaneList::ReferenceBrush( int brushnumber ) -{ - if ( !(dbrushes[brushnumber].contents & m_contentsMask) ) - return; - - m_brushAdded[brushnumber] = true; - -} - - -bool CPlaneList::IsBrushReferenced( int brushnumber ) -{ - return m_brushAdded[brushnumber]; -} - -CPhysConvex *CPlaneList::BuildConvexForBrush( int brushnumber, float shrink, CPhysCollide *pCollideTest, float shrinkMinimum ) -{ - CUtlVector temp( 0, 32 ); - - for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ ) - { - dbrushside_t *pside = dbrushsides + i + dbrushes[brushnumber].firstside; - if ( pside->bevel ) - continue; - - dplane_t *pplane = dplanes + pside->planenum; - float shrinkThisPlane = shrink; - - if ( i < g_MainMap->mapbrushes[brushnumber].numsides ) - { - if ( !g_MainMap->mapbrushes[brushnumber].original_sides[i].visible ) - { - // don't shrink brush sides with no visible components. - // this produces something closer to the ideal shrink than simply shrinking all planes - shrinkThisPlane = 0; - } - } - // Make sure shrinking won't swallow geometry along this axis. - if ( pCollideTest && shrinkThisPlane != 0 ) - { - Vector start = physcollision->CollideGetExtent( pCollideTest, vec3_origin, vec3_angle, pplane->normal ); - Vector end = physcollision->CollideGetExtent( pCollideTest, vec3_origin, vec3_angle, -pplane->normal ); - float thick = DotProduct( (end-start), pplane->normal ); - // NOTE: The object must be at least "shrinkMinimum" inches wide on each axis - if ( fabs(thick) < shrinkMinimum ) - { -#if _DEBUG - Warning("Can't shrink brush %d, plane %d (%.2f, %.2f, %.2f)\n", brushnumber, pside->planenum, pplane->normal[0], pplane->normal[1], pplane->normal[2] ); -#endif - shrinkThisPlane = 0; - } - } - AddListPlane( &temp, pplane->normal[0], pplane->normal[1], pplane->normal[2], pplane->dist - shrinkThisPlane ); - } - return physcollision->ConvexFromPlanes( (float *)temp.Base(), temp.Count(), m_merge ); -} - -int CPlaneList::AddBrushes( void ) -{ - int count = 0; - for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ ) - { - if ( IsBrushReferenced(brushnumber) ) - { - CPhysConvex *pBrushConvex = NULL; - if ( m_shrink != 0 ) - { - // Make sure shrinking won't swallow this brush. - CPhysConvex *pConvex = BuildConvexForBrush( brushnumber, 0, NULL, 0 ); - CPhysCollide *pUnshrunkCollide = physcollision->ConvertConvexToCollide( &pConvex, 1 ); - pBrushConvex = BuildConvexForBrush( brushnumber, m_shrink, pUnshrunkCollide, m_shrink * 3 ); - physcollision->DestroyCollide( pUnshrunkCollide ); - } - else - { - pBrushConvex = BuildConvexForBrush( brushnumber, m_shrink, NULL, 1.0 ); - } - - if ( pBrushConvex ) - { - count++; - physcollision->SetConvexGameData( pBrushConvex, brushnumber ); - AddConvex( pBrushConvex ); - } - } - } - return count; -} - - -int CPlaneList::GetFirstBrushSide() -{ - for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ ) - { - if ( IsBrushReferenced(brushnumber) ) - { - for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ ) - { - int sideIndex = i + dbrushes[brushnumber].firstside; - dbrushside_t *pside = dbrushsides + sideIndex; - if ( pside->bevel ) - continue; - return sideIndex; - } - } - } - return 0; -} - -// UNDONE: Try using this kind of algorithm if we run into precision problems. -// NOTE: ConvexFromPlanes will be doing a bunch of matrix inversions that can suffer -// if plane normals are too close to each other... -#if 0 -void CPlaneList::AddBrushes( void ) -{ - CUtlVector temp; - for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ ) - { - if ( IsBrushReferenced(brushnumber) ) - { - CUtlVector windings; - - for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ ) - { - dbrushside_t *pside = dbrushsides + i + dbrushes[brushnumber].firstside; - if (pside->bevel) - continue; - dplane_t *pplane = dplanes + pside->planenum; - winding_t *w = BaseWindingForPlane( pplane->normal, pplane->dist - m_shrink ); - for ( int j = 0; j < dbrushes[brushnumber].numsides && w; j++ ) - { - if (i == j) - continue; - dbrushside_t *pClipSide = dbrushsides + j + dbrushes[brushnumber].firstside; - if (pClipSide->bevel) - continue; - dplane_t *pClipPlane = dplanes + pClipSide->planenum; - ChopWindingInPlace (&w, -pClipPlane->normal, -pClipPlane->dist+m_shrink, 0); //CLIP_EPSILON); - } - if ( w ) - { - windings.AddToTail( w ); - } - } - - CUtlVector vertList; - for ( int p = 0; p < windings.Count(); p++ ) - { - for ( int v = 0; v < windings[p]->numpoints; v++ ) - { - vertList.AddToTail( windings[p]->p + v ); - } - } - CPhysConvex *pConvex = physcollision->ConvexFromVerts( vertList.Base(), vertList.Count() ); - if ( pConvex ) - { - physcollision->SetConvexGameData( pConvex, brushnumber ); - AddConvex( pConvex ); - } - temp.RemoveAll(); - } - } -} -#endif - -// If I have a list of leaves, make sure this leaf is in it. -// Otherwise, process all leaves -bool CPlaneList::IsLeafReferenced( int leafIndex ) -{ - if ( !m_leafList.Count() ) - return true; - - for ( int i = 0; i < m_leafList.Count(); i++ ) - { - if ( m_leafList[i] == leafIndex ) - return true; - } - - return false; -} - -// Add a leaf to my list of interesting leaves -void CPlaneList::ReferenceLeaf( int leafIndex ) -{ - m_leafList.AddToTail( leafIndex ); -} - -static void VisitLeaves_r( CPlaneList &planes, int node ) -{ - if ( node < 0 ) - { - int leafIndex = -1 - node; - if ( planes.IsLeafReferenced(leafIndex) ) - { - int i; - - // Add the solids in the "empty" leaf - for ( i = 0; i < dleafs[leafIndex].numleafbrushes; i++ ) - { - int brushIndex = dleafbrushes[dleafs[leafIndex].firstleafbrush + i]; - planes.ReferenceBrush( brushIndex ); - } - } - } - else - { - dnode_t *pnode = dnodes + node; - - VisitLeaves_r( planes, pnode->children[0] ); - VisitLeaves_r( planes, pnode->children[1] ); - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -struct waterleaf_t -{ - Vector surfaceNormal; - float surfaceDist; - float minZ; - bool hasSurface; - int waterLeafIndex;// this is the submerged leaf - int planenum; //UNDONE: REMOVE - int surfaceTexInfo; // if hasSurface == true, this is the texinfo index for the water material - int outsideLeafIndex;// this is the leaf on the other side of the water surface - node_t *pNode; -}; - - - -// returns true if newleaf should appear before currentleaf in the list -static bool IsLowerLeaf( const waterleaf_t &newleaf, const waterleaf_t ¤tleaf ) -{ - if ( newleaf.hasSurface && currentleaf.hasSurface ) - { - // the one with the upmost pointing z goes first - if ( currentleaf.surfaceNormal.z > newleaf.surfaceNormal.z ) - return false; - - if ( fabs(currentleaf.surfaceNormal.z - newleaf.surfaceNormal.z) < 0.01 ) - { - if ( newleaf.surfaceDist < currentleaf.surfaceDist ) - return true; - } - return true; - } - else if ( newleaf.hasSurface ) // the leaf with a surface always goes first - return true; - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: Water surfaces are stored in an RB tree and the tree is used to -// create one-off .vmt files embedded in the .bsp for each surface so that the -// water depth effect occurs on a per-water surface level. -//----------------------------------------------------------------------------- -struct WaterTexInfo -{ - // The mangled new .vmt name ( materials/levelename/oldmaterial_depth_xxx ) where xxx is - // the water depth (as an integer ) - CUtlSymbol m_FullName; - - // The original .vmt name - CUtlSymbol m_MaterialName; - - // The depth of the water this texinfo refers to - int m_nWaterDepth; - - // The texinfo id - int m_nTexInfo; - - // The subdivision size for the water surface -// float m_SubdivSize; -}; - -//----------------------------------------------------------------------------- -// Purpose: Helper for RB tree operations ( we compare full mangled names ) -// Input : src1 - -// src2 - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool WaterLessFunc( WaterTexInfo const& src1, WaterTexInfo const& src2 ) -{ - return src1.m_FullName < src2.m_FullName; -} - -//----------------------------------------------------------------------------- -// Purpose: A growable RB tree of water surfaces -//----------------------------------------------------------------------------- -static CUtlRBTree< WaterTexInfo, int > g_WaterTexInfos( 0, 32, WaterLessFunc ); - -#if 0 -float GetSubdivSizeForFogVolume( int fogVolumeID ) -{ - Assert( fogVolumeID >= 0 && fogVolumeID < g_WaterTexInfos.Count() ); - return g_WaterTexInfos[fogVolumeID].m_SubdivSize; -} -#endif - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *mapname - -// *materialname - -// waterdepth - -// *fullname - -//----------------------------------------------------------------------------- -void GetWaterTextureName( char const *mapname, char const *materialname, int waterdepth, char *fullname ) -{ - char temp[ 512 ]; - - // Construct the full name (prepend mapname to reduce name collisions) - sprintf( temp, "maps/%s/%s_depth_%i", mapname, materialname, (int)waterdepth ); - - // Make sure it's lower case - strlwr( temp ); - - strcpy( fullname, temp ); -} - -//----------------------------------------------------------------------------- -// Purpose: Called to write procedural materials in the rb tree to the embedded -// pak file for this .bsp -//----------------------------------------------------------------------------- -void EmitWaterMaterialFile( WaterTexInfo *wti ) -{ - char waterTextureName[512]; - if ( !wti ) - { - return; - } - - GetWaterTextureName( mapbase, wti->m_MaterialName.String(), ( int )wti->m_nWaterDepth, waterTextureName ); - - // Convert to string - char szDepth[ 32 ]; - sprintf( szDepth, "%i", wti->m_nWaterDepth ); - CreateMaterialPatch( wti->m_MaterialName.String(), waterTextureName, "$waterdepth", szDepth, PATCH_INSERT ); -} - -//----------------------------------------------------------------------------- -// Purpose: Takes the texinfo_t referenced by the .vmt and the computed depth for the -// surface and looks up or creates a texdata/texinfo for the mangled one-off water .vmt file -// Input : *pBaseInfo - -// depth - -// Output : int -//----------------------------------------------------------------------------- -int FindOrCreateWaterTexInfo( texinfo_t *pBaseInfo, float depth ) -{ - char fullname[ 512 ]; - char materialname[ 512 ]; - - // Get the base texture/material name - char const *name = TexDataStringTable_GetString( GetTexData( pBaseInfo->texdata )->nameStringTableID ); - - GetWaterTextureName( mapbase, name, (int)depth, fullname ); - - // See if we already have an entry for this depth - WaterTexInfo lookup; - lookup.m_FullName = fullname; - int idx = g_WaterTexInfos.Find( lookup ); - - // If so, return the existing entry texinfo index - if ( idx != g_WaterTexInfos.InvalidIndex() ) - { - return g_WaterTexInfos[ idx ].m_nTexInfo; - } - - // Otherwise, fill in the rest of the data - lookup.m_nWaterDepth = (int)depth; - // Remember the current material name - sprintf( materialname, "%s", name ); - strlwr( materialname ); - lookup.m_MaterialName = materialname; - - texinfo_t ti; - // Make a copy - ti = *pBaseInfo; - // Create a texdata that is based on the underlying existing entry - ti.texdata = FindAliasedTexData( fullname, GetTexData( pBaseInfo->texdata ) ); - - // Find or create a new index - lookup.m_nTexInfo = FindOrCreateTexInfo( ti ); - - // Add the new texinfo to the RB tree - idx = g_WaterTexInfos.Insert( lookup ); - - // Msg( "created texinfo for %s\n", lookup.m_FullName.String() ); - - // Go ahead and create the new vmt file. - EmitWaterMaterialFile( &g_WaterTexInfos[idx] ); - - // Return the new texinfo - return g_WaterTexInfos[ idx ].m_nTexInfo; -} - -extern node_t *dfacenodes[MAX_MAP_FACES]; -static void WriteFogVolumeIDs( dmodel_t *pModel ) -{ - int i; - - // write fog volume ID to each face in this model - for( i = pModel->firstface; i < pModel->firstface + pModel->numfaces; i++ ) - { - dface_t *pFace = &dfaces[i]; - node_t *pFaceNode = dfacenodes[i]; - texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; - pFace->surfaceFogVolumeID = -1; - if ( pFaceNode ) - { - if ( (pTexInfo->flags & SURF_WARP ) && pFaceNode->planenum == PLANENUM_LEAF && pFaceNode->diskId >= 0 ) - { - pFace->surfaceFogVolumeID = dleafs[pFaceNode->diskId].leafWaterDataID; - dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[pFace->surfaceFogVolumeID]; - - // HACKHACK: Should probably mark these faces as water bottom or "bottommaterial" faces. - // HACKHACK: Use a heuristic, if it points up, it's the water top. - if ( dplanes[pFace->planenum].normal.z > 0 ) - { - pFace->texinfo = pLeafWaterData->surfaceTexInfoID; - } - } - else - { - // missed this face somehow? - Assert( !(pTexInfo->flags & SURF_WARP ) ); - } - - } - } -} - - -static bool PortalCrossesWater( waterleaf_t &baseleaf, portal_t *portal ) -{ - if ( baseleaf.hasSurface ) - { - int side = WindingOnPlaneSide( portal->winding, baseleaf.surfaceNormal, baseleaf.surfaceDist ); - if ( side == SIDE_CROSS || side == SIDE_FRONT ) - return true; - } - - return false; -} - - -static int FindOrCreateLeafWaterData( float surfaceZ, float minZ, int surfaceTexInfoID ) -{ - int i; - for( i = 0; i < numleafwaterdata; i++ ) - { - dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[i]; - if( pLeafWaterData->surfaceZ == surfaceZ && - pLeafWaterData->minZ == minZ && - pLeafWaterData->surfaceTexInfoID == surfaceTexInfoID ) - { - return i; - } - } - dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[numleafwaterdata]; - pLeafWaterData->surfaceZ = surfaceZ; - pLeafWaterData->minZ = minZ; - pLeafWaterData->surfaceTexInfoID = surfaceTexInfoID; - numleafwaterdata++; - return numleafwaterdata - 1; -} - - -// Enumerate all leaves under node with contents in contentsMask and add them to list -void EnumLeaves_r( CUtlVector &list, node_t *node, int contentsMask ) -{ - if ( node->planenum != PLANENUM_LEAF ) - { - EnumLeaves_r( list, node->children[0], contentsMask ); - EnumLeaves_r( list, node->children[1], contentsMask ); - return; - } - - if ( !(node->contents & contentsMask) ) - return; - - - // has the contents, put it in the list - list.AddToTail( node ); -} - - -// Builds a waterleaf_t for the given leaf -static void BuildWaterLeaf( node_t *pLeafIn, waterleaf_t &waterLeafOut ) -{ - waterLeafOut.pNode = pLeafIn; - waterLeafOut.waterLeafIndex = pLeafIn->diskId; - waterLeafOut.outsideLeafIndex = -1; - waterLeafOut.hasSurface = false; - waterLeafOut.surfaceDist = MAX_COORD_INTEGER; - waterLeafOut.surfaceNormal.Init( 0.f, 0.f, 1.f ); - waterLeafOut.planenum = -1; - waterLeafOut.surfaceTexInfo = -1; - waterLeafOut.minZ = MAX_COORD_INTEGER; - - // search the list of portals out of this leaf for one that leaves water - // If you find one, this leaf has a surface, so fill out the surface data - int oppositeNodeIndex = 0; - for (portal_t *p = pLeafIn->portals ; p ; p = p->next[!oppositeNodeIndex]) - { - oppositeNodeIndex = (p->nodes[0] == pLeafIn) ? 1 : 0; - - // not visible, can't be the portals we're looking for... - if ( !p->side ) - continue; - - // See if this portal crosses into air - node_t *pOpposite = p->nodes[oppositeNodeIndex]; - if ( !(pOpposite->contents & MASK_WATER) && !(pOpposite->contents & MASK_SOLID) ) - { - // it does, there must be a surface here - plane_t *plane = &g_MainMap->mapplanes[p->side->planenum]; - if ( waterLeafOut.hasSurface ) - { - // Sort to find the most upward facing normal (skips sides) - if ( waterLeafOut.surfaceNormal.z > plane->normal.z ) - continue; - if ( (waterLeafOut.surfaceNormal.z == plane->normal.z) && waterLeafOut.surfaceDist >= plane->dist ) - continue; - } - // water surface needs to point at least somewhat up, this is - // probably a map error - if ( plane->normal.z <= 0 ) - continue; - waterLeafOut.surfaceDist = plane->dist; - waterLeafOut.surfaceNormal = plane->normal; - waterLeafOut.hasSurface = true; - waterLeafOut.outsideLeafIndex = p->nodes[oppositeNodeIndex]->diskId; - waterLeafOut.surfaceTexInfo = p->side->texinfo; - } - } -} - - -static void InsertSortWaterLeaf( CUtlVector &list, const waterleaf_t &leafInsert ) -{ - // insertion sort the leaf (lowest leaves go first) - // leaves that aren't actually on the surface of the water will have leaf.hasSurface == false. - for ( int i = 0; i < list.Count(); i++ ) - { - if ( IsLowerLeaf( leafInsert, list[i] ) ) - { - list.InsertBefore( i, leafInsert ); - return; - } - } - - // must the highest one, so stick it at the end. - list.AddToTail( leafInsert ); -} - - -// Flood fill the tree, finding neighboring water volumes and connecting them to this list -// Cut groups that try to cross the surface. -// Mark leaves that are in a group as "visited" so they won't be chosen by subsequent fills -static void Flood_FindConnectedWaterVolumes_r( CUtlVector &list, node_t *pLeaf, waterleaf_t &baseleaf, leafbitarray_t &visited ) -{ - // already visited, or not the same water contents - if ( pLeaf->diskId < 0 || visited.Get(pLeaf->diskId) || !(pLeaf->contents & (baseleaf.pNode->contents & MASK_WATER) ) ) - return; - - int oppositeNodeIndex = 0; - for (portal_t *p = pLeaf->portals ; p ; p = p->next[!oppositeNodeIndex]) - { - oppositeNodeIndex = (p->nodes[0] == pLeaf) ? 1 : 0; - - // If any portal crosses the water surface, don't flow through this leaf - if ( PortalCrossesWater( baseleaf, p ) ) - return; - } - - visited.Set( pLeaf->diskId ); - list.AddToTail( pLeaf ); - - baseleaf.minZ = min( pLeaf->mins.z, baseleaf.minZ ); - - for (portal_t *p = pLeaf->portals ; p ; p = p->next[!oppositeNodeIndex]) - { - oppositeNodeIndex = (p->nodes[0] == pLeaf) ? 1 : 0; - - Flood_FindConnectedWaterVolumes_r( list, p->nodes[oppositeNodeIndex], baseleaf, visited ); - } -} - -// UNDONE: This is a bit of a hack to avoid crashing when we can't find an -// appropriate texinfo for a water model (to get physics properties) -int FirstWaterTexinfo( bspbrush_t *brushlist, int contents ) -{ - while (brushlist) - { - if ( brushlist->original->contents & contents ) - { - for ( int i = 0; i < brushlist->original->numsides; i++ ) - { - if ( brushlist->original->original_sides[i].contents & contents ) - { - return brushlist->original->original_sides[i].texinfo; - } - } - } - brushlist = brushlist->next; - } - - Assert(0); - return 0; -} - -// This is a list of water data that will be turned into physics models -struct watermodel_t -{ - int modelIndex; - int contents; - waterleaf_t waterLeafData; - int depthTexinfo; - int firstWaterLeafIndex; - int waterLeafCount; - int fogVolumeIndex; -}; - -static CUtlVector g_WaterModels; -static CUtlVector g_WaterLeafList; - -// Creates a list of watermodel_t for later processing by EmitPhysCollision -void EmitWaterVolumesForBSP( dmodel_t *pModel, node_t *node ) -{ - CUtlVector leafListAnyWater; - // build the list of all leaves containing water - EnumLeaves_r( leafListAnyWater, node, MASK_WATER ); - - // make a sorted list to flood fill - CUtlVector list; - - int i; - for ( i = 0; i < leafListAnyWater.Count(); i++ ) - { - waterleaf_t waterLeaf; - BuildWaterLeaf( leafListAnyWater[i], waterLeaf ); - InsertSortWaterLeaf( list, waterLeaf ); - } - - leafbitarray_t visited; - CUtlVector waterAreaList; - for ( i = 0; i < list.Count(); i++ ) - { - Flood_FindConnectedWaterVolumes_r( waterAreaList, list[i].pNode, list[i], visited ); - - // did we find a list of leaves connected to this one? - // remember the list is sorted, so this one may have been attached to a previous - // leaf. So it could have nothing hanging off of it. - if ( waterAreaList.Count() ) - { - // yes, emit a watermodel - watermodel_t tmp; - tmp.modelIndex = nummodels; - tmp.contents = list[i].pNode->contents; - tmp.waterLeafData = list[i]; - tmp.firstWaterLeafIndex = g_WaterLeafList.Count(); - tmp.waterLeafCount = waterAreaList.Count(); - - float waterDepth = tmp.waterLeafData.surfaceDist - tmp.waterLeafData.minZ; - if ( tmp.waterLeafData.surfaceTexInfo < 0 ) - { - // the map has probably leaked in this case, but output something anyway. - Assert(list[i].pNode->planenum == PLANENUM_LEAF); - tmp.waterLeafData.surfaceTexInfo = FirstWaterTexinfo( list[i].pNode->brushlist, tmp.contents ); - } - tmp.depthTexinfo = FindOrCreateWaterTexInfo( &texinfo[ tmp.waterLeafData.surfaceTexInfo ], waterDepth ); - tmp.fogVolumeIndex = FindOrCreateLeafWaterData( tmp.waterLeafData.surfaceDist, tmp.waterLeafData.minZ, tmp.waterLeafData.surfaceTexInfo ); - - for ( int j = 0; j < waterAreaList.Count(); j++ ) - { - g_WaterLeafList.AddToTail( waterAreaList[j]->diskId ); - } - waterAreaList.RemoveAll(); - g_WaterModels.AddToTail( tmp ); - } - } - - WriteFogVolumeIDs( pModel ); -} - - -static void ConvertWaterModelToPhysCollide( CUtlVector &collisionList, int modelIndex, - float shrinkSize, float mergeTolerance ) -{ - dmodel_t *pModel = dmodels + modelIndex; - - for ( int i = 0; i < g_WaterModels.Count(); i++ ) - { - watermodel_t &waterModel = g_WaterModels[i]; - if ( waterModel.modelIndex != modelIndex ) - continue; - - CPlaneList planes( shrinkSize, mergeTolerance ); - int firstLeaf = waterModel.firstWaterLeafIndex; - planes.m_contentsMask = waterModel.contents; - - // push all of the leaves into the collision list - for ( int j = 0; j < waterModel.waterLeafCount; j++ ) - { - int leafIndex = g_WaterLeafList[firstLeaf + j]; - - dleaf_t *pLeaf = dleafs + leafIndex; - // fixup waterdata - pLeaf->leafWaterDataID = waterModel.fogVolumeIndex; - planes.ReferenceLeaf( leafIndex ); - } - - // visit the referenced leaves that belong to this model - VisitLeaves_r( planes, pModel->headnode ); - - // Now add the brushes from those leaves as convex - - // BUGBUG: NOTE: If your map has a brush that crosses the surface, it will be added to two water - // volumes. This only happens with connected water volumes with multiple surface heights - // UNDONE: Right now map makers must cut such brushes. It could be automatically cut by adding the - // surface plane to the list for each brush before calling ConvexFromPlanes() - planes.AddBrushes(); - - int count = planes.m_convex.Count(); - if ( !count ) - continue; - - // Save off the plane of the surface for this group as well as the collision model - // for all convex objects in the group. - CPhysCollide *pCollide = physcollision->ConvertConvexToCollide( planes.m_convex.Base(), count ); - if ( pCollide ) - { - int waterSurfaceTexInfoID = -1; - // use defaults - const char *pSurfaceProp = "water"; - float damping = 0.01; - if ( waterSurfaceTexInfoID >= 0 ) - { - // material override - int texdata = texinfo[waterSurfaceTexInfoID].texdata; - int prop = g_SurfaceProperties[texdata]; - pSurfaceProp = physprops->GetPropName( prop ); - } - - if ( !waterModel.waterLeafData.hasSurface ) - { - waterModel.waterLeafData.surfaceNormal.Init( 0,0,1 ); - Vector top = physcollision->CollideGetExtent( pCollide, vec3_origin, vec3_angle, waterModel.waterLeafData.surfaceNormal ); - waterModel.waterLeafData.surfaceDist = top.z; - } - CPhysCollisionEntryFluid *pCollisionEntryFuild = new CPhysCollisionEntryFluid( pCollide, - pSurfaceProp, damping, waterModel.waterLeafData.surfaceNormal, waterModel.waterLeafData.surfaceDist, waterModel.contents ); - collisionList.AddToTail( pCollisionEntryFuild ); - } - } -} - -// compute a normal for a triangle of the given three points (points are clockwise, normal points out) -static Vector TriangleNormal( const Vector &p0, const Vector &p1, const Vector &p2 ) -{ - Vector e0 = p1 - p0; - Vector e1 = p2 - p0; - Vector normal = CrossProduct( e1, e0 ); - VectorNormalize( normal ); - - return normal; -} - - -// find the side of the brush with the normal closest to the given normal -static dbrushside_t *FindBrushSide( int brushIndex, const Vector &normal ) -{ - dbrush_t *pbrush = &dbrushes[brushIndex]; - dbrushside_t *out = NULL; - float best = -1.f; - - for ( int i = 0; i < pbrush->numsides; i++ ) - { - dbrushside_t *pside = dbrushsides + i + pbrush->firstside; - dplane_t *pplane = dplanes + pside->planenum; - float dot = DotProduct( normal, pplane->normal ); - if ( dot > best ) - { - best = dot; - out = pside; - } - } - - return out; -} - - - -static void ConvertWorldBrushesToPhysCollide( CUtlVector &collisionList, float shrinkSize, float mergeTolerance, int contentsMask ) -{ - CPlaneList planes( shrinkSize, mergeTolerance ); - - planes.m_contentsMask = contentsMask; - - VisitLeaves_r( planes, dmodels[0].headnode ); - planes.AddBrushes(); - - int count = planes.m_convex.Count(); - if ( count ) - { - CPhysCollide *pCollide = physcollision->ConvertConvexToCollide( planes.m_convex.Base(), count ); - - ICollisionQuery *pQuery = physcollision->CreateQueryModel( pCollide ); - int convex = pQuery->ConvexCount(); - for ( int i = 0; i < convex; i++ ) - { - int triCount = pQuery->TriangleCount( i ); - int brushIndex = pQuery->GetGameData( i ); - - Vector points[3]; - for ( int j = 0; j < triCount; j++ ) - { - pQuery->GetTriangleVerts( i, j, points ); - Vector normal = TriangleNormal( points[0], points[1], points[2] ); - dbrushside_t *pside = FindBrushSide( brushIndex, normal ); - if ( pside->texinfo != TEXINFO_NODE ) - { - int prop = g_SurfaceProperties[texinfo[pside->texinfo].texdata]; - pQuery->SetTriangleMaterialIndex( i, j, RemapWorldMaterial( prop ) ); - } - } - } - physcollision->DestroyQueryModel( pQuery ); - pQuery = NULL; - - collisionList.AddToTail( new CPhysCollisionEntryStaticSolid( pCollide, contentsMask ) ); - } -} - -// adds any world, terrain, and water collision models to the collision list -static void BuildWorldPhysModel( CUtlVector &collisionList, float shrinkSize, float mergeTolerance ) -{ - ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, MASK_SOLID ); - ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, CONTENTS_PLAYERCLIP ); - ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, CONTENTS_MONSTERCLIP ); - - if ( !g_bNoVirtualMesh && Disp_HasPower4Displacements() ) - { - Warning("WARNING: Map using power 4 displacements, terrain physics cannot be compressed, map will need additional memory and CPU.\n"); - g_bNoVirtualMesh = true; - } - - // if there's terrain, save it off as a static mesh/polysoup - if ( g_bNoVirtualMesh || !physcollision->SupportsVirtualMesh() ) - { - Disp_AddCollisionModels( collisionList, &dmodels[0], MASK_SOLID ); - } - else - { - Disp_BuildVirtualMesh( MASK_SOLID ); - } - ConvertWaterModelToPhysCollide( collisionList, 0, shrinkSize, mergeTolerance ); -} - - -// adds a collision entry for this brush model -static void ConvertModelToPhysCollide( CUtlVector &collisionList, int modelIndex, int contents, float shrinkSize, float mergeTolerance ) -{ - int i; - CPlaneList planes( shrinkSize, mergeTolerance ); - - planes.m_contentsMask = contents; - - dmodel_t *pModel = dmodels + modelIndex; - VisitLeaves_r( planes, pModel->headnode ); - planes.AddBrushes(); - int count = planes.m_convex.Count(); - convertconvexparams_t params; - params.Defaults(); - params.buildOuterConvexHull = count > 1 ? true : false; - params.buildDragAxisAreas = true; - Vector size = pModel->maxs - pModel->mins; - - float minSurfaceArea = -1.0f; - for ( i = 0; i < 3; i++ ) - { - int other = (i+1)%3; - int cross = (i+2)%3; - float surfaceArea = size[other] * size[cross]; - if ( minSurfaceArea < 0 || surfaceArea < minSurfaceArea ) - { - minSurfaceArea = surfaceArea; - } - } - // this can be really slow with super-large models and a low error tolerance - // Basically you get a ray cast through each square of epsilon surface area on each OBB side - // So compute it for 1% error (on the smallest side, less on larger sides) - params.dragAreaEpsilon = clamp( minSurfaceArea * 1e-2f, 1.0f, 1024.0f ); - CPhysCollide *pCollide = physcollision->ConvertConvexToCollideParams( planes.m_convex.Base(), count, params ); - - if ( !pCollide ) - return; - - struct - { - int prop; - float area; - } proplist[256]; - int numprops = 1; - - proplist[0].prop = -1; - proplist[0].area = 1; - // compute the array of props on the surface of this model - - // NODRAW brushes no longer have any faces - if ( !dmodels[modelIndex].numfaces ) - { - int sideIndex = planes.GetFirstBrushSide(); - int texdata = texinfo[dbrushsides[sideIndex].texinfo].texdata; - int prop = g_SurfaceProperties[texdata]; - proplist[numprops].prop = prop; - proplist[numprops].area = 2; - numprops++; - } - - for ( i = 0; i < dmodels[modelIndex].numfaces; i++ ) - { - dface_t *face = dfaces + i + dmodels[modelIndex].firstface; - int texdata = texinfo[face->texinfo].texdata; - int prop = g_SurfaceProperties[texdata]; - int j; - for ( j = 0; j < numprops; j++ ) - { - if ( proplist[j].prop == prop ) - { - proplist[j].area += face->area; - break; - } - } - - if ( (!numprops || j >= numprops) && numprops < ARRAYSIZE(proplist) ) - { - proplist[numprops].prop = prop; - proplist[numprops].area = face->area; - numprops++; - } - } - - - // choose the prop with the most surface area - int maxIndex = -1; - float maxArea = 0; - float totalArea = 0; - - for ( i = 0; i < numprops; i++ ) - { - if ( proplist[i].area > maxArea ) - { - maxIndex = i; - maxArea = proplist[i].area; - } - // add up the total surface area - totalArea += proplist[i].area; - } - - float mass = 1.0f; - const char *pMaterial = "default"; - if ( maxIndex >= 0 ) - { - int prop = proplist[maxIndex].prop; - - // use default if this material has no prop - if ( prop < 0 ) - prop = 0; - - pMaterial = physprops->GetPropName( prop ); - float density, thickness; - physprops->GetPhysicsProperties( prop, &density, &thickness, NULL, NULL ); - - // if this is a "shell" material (it is hollow and encloses some empty space) - // compute the mass with a constant surface thickness - if ( thickness != 0 ) - { - mass = totalArea * thickness * density * CUBIC_METERS_PER_CUBIC_INCH; - } - else - { - // material is completely solid, compute total mass as if constant density throughout. - mass = planes.m_totalVolume * density * CUBIC_METERS_PER_CUBIC_INCH; - } - } - - // Clamp mass to 100,000 kg - if ( mass > VPHYSICS_MAX_MASS ) - { - mass = VPHYSICS_MAX_MASS; - } - - collisionList.AddToTail( new CPhysCollisionEntrySolid( pCollide, pMaterial, mass ) ); -} - -static void ClearLeafWaterData( void ) -{ - int i; - - for( i = 0; i < numleafs; i++ ) - { - dleafs[i].leafWaterDataID = -1; - dleafs[i].contents &= ~CONTENTS_TESTFOGVOLUME; - } -} - - -// This is the only public entry to this file. -// The global data touched in the file is: -// from bsplib.h: -// g_pPhysCollide : This is an output from this file. -// g_PhysCollideSize : This is set in this file. -// g_numdispinfo : This is an input to this file. -// g_dispinfo : This is an input to this file. -// numnodewaterdata : This is an output from this file. -// dleafwaterdata : This is an output from this file. -// from vbsp.h: -// g_SurfaceProperties : This is an input to this file. -void EmitPhysCollision() -{ - ClearLeafWaterData(); - - CreateInterfaceFn physicsFactory = GetPhysicsFactory(); - if ( physicsFactory ) - { - physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); - } - - if ( !physcollision ) - { - Warning("!!! WARNING: Can't build collision data!\n" ); - return; - } - - CUtlVector collisionList[MAX_MAP_MODELS]; - CTextBuffer *pTextBuffer[MAX_MAP_MODELS]; - - int physModelCount = 0, totalSize = 0; - - int start = Plat_FloatTime(); - - Msg("Building Physics collision data...\n" ); - - int i, j; - for ( i = 0; i < nummodels; i++ ) - { - // Build a list of collision models for this brush model section - if ( i == 0 ) - { - // world is the only model that processes water separately. - // other brushes are assumed to be completely solid or completely liquid - BuildWorldPhysModel( collisionList[i], NO_SHRINK, VPHYSICS_MERGE); - } - else - { - ConvertModelToPhysCollide( collisionList[i], i, MASK_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|MASK_WATER, VPHYSICS_SHRINK, VPHYSICS_MERGE ); - } - - pTextBuffer[i] = NULL; - if ( !collisionList[i].Count() ) - continue; - - // if we've got collision models, write their script for processing in the game - pTextBuffer[i] = new CTextBuffer; - for ( j = 0; j < collisionList[i].Count(); j++ ) - { - // dump a text file for visualization - if ( dumpcollide ) - { - collisionList[i][j]->DumpCollide( pTextBuffer[i], i, j ); - } - // each model knows how to write its script - collisionList[i][j]->WriteToTextBuffer( pTextBuffer[i], i, j ); - // total up the binary section's size - totalSize += collisionList[i][j]->GetCollisionBinarySize() + sizeof(int); - } - - // These sections only appear in the world's collision text - if ( i == 0 ) - { - if ( !g_bNoVirtualMesh && physcollision->SupportsVirtualMesh() ) - { - pTextBuffer[i]->WriteText("virtualterrain {}\n"); - } - if ( s_WorldPropList.Count() ) - { - pTextBuffer[i]->WriteText( "materialtable {\n" ); - for ( j = 0; j < s_WorldPropList.Count(); j++ ) - { - int propIndex = s_WorldPropList[j]; - if ( propIndex < 0 ) - { - pTextBuffer[i]->WriteIntKey( "default", j+1 ); - } - else - { - pTextBuffer[i]->WriteIntKey( physprops->GetPropName( propIndex ), j+1 ); - } - } - pTextBuffer[i]->WriteText( "}\n" ); - } - } - - pTextBuffer[i]->Terminate(); - - // total lump size includes the text buffers (scripts) - totalSize += pTextBuffer[i]->GetSize(); - - physModelCount++; - } - - // add one for tail of list marker - physModelCount++; - - // DWORD align the lump because AddLump assumes that it is DWORD aligned. - byte *ptr ; - g_PhysCollideSize = totalSize + (physModelCount * sizeof(dphysmodel_t)); - g_pPhysCollide = (byte *)malloc(( g_PhysCollideSize + 3 ) & ~3 ); - memset( g_pPhysCollide, 0, g_PhysCollideSize ); - ptr = g_pPhysCollide; - - for ( i = 0; i < nummodels; i++ ) - { - if ( pTextBuffer[i] ) - { - int j; - - dphysmodel_t model; - - model.modelIndex = i; - model.solidCount = collisionList[i].Count(); - model.dataSize = sizeof(int) * model.solidCount; - - for ( j = 0; j < model.solidCount; j++ ) - { - model.dataSize += collisionList[i][j]->GetCollisionBinarySize(); - } - model.keydataSize = pTextBuffer[i]->GetSize(); - - // store the header - memcpy( ptr, &model, sizeof(model) ); - ptr += sizeof(model); - - for ( j = 0; j < model.solidCount; j++ ) - { - int collideSize = collisionList[i][j]->GetCollisionBinarySize(); - - // write size - memcpy( ptr, &collideSize, sizeof(int) ); - ptr += sizeof(int); - - // now write the collision model - collisionList[i][j]->WriteCollisionBinary( reinterpret_cast(ptr) ); - ptr += collideSize; - } - - memcpy( ptr, pTextBuffer[i]->GetData(), pTextBuffer[i]->GetSize() ); - ptr += pTextBuffer[i]->GetSize(); - } - - delete pTextBuffer[i]; - } - - dphysmodel_t model; - - // Mark end of list - model.modelIndex = -1; - model.dataSize = -1; - model.keydataSize = 0; - model.solidCount = 0; - memcpy( ptr, &model, sizeof(model) ); - ptr += sizeof(model); - Assert( (ptr-g_pPhysCollide) == g_PhysCollideSize); - Msg("done (%d) (%d bytes)\n", (int)(Plat_FloatTime() - start), g_PhysCollideSize ); - - // UNDONE: Collision models (collisionList) memory leak! -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include +#include "mathlib/vector.h" +#include "bspfile.h" +#include "bsplib.h" +#include "cmdlib.h" +#include "physdll.h" +#include "utlvector.h" +#include "vbsp.h" +#include "phyfile.h" +#include +#include "KeyValues.h" +#include "UtlBuffer.h" +#include "utlsymbol.h" +#include "utlrbtree.h" +#include "ivp.h" +#include "disp_ivp.h" +#include "materialpatch.h" +#include "bitvec.h" + +// bit per leaf +typedef CBitVec leafbitarray_t; + +// parameters for conversion to vphysics +#define NO_SHRINK 0.0f +// NOTE: vphysics maintains a minimum separation radius between objects +// This radius is set to 0.25, but it's symmetric. So shrinking potentially moveable +// brushes by 0.5 in every direction ensures that these brushes can be constructed +// touching the world, and constrained in place without collisions or friction +// UNDONE: Add a key to disable this shrinking if necessary +#define VPHYSICS_SHRINK (0.5f) // shrink BSP brushes by this much for collision +#define VPHYSICS_MERGE 0.01f // merge verts closer than this + +void EmitPhysCollision(); + +IPhysicsCollision *physcollision = NULL; +extern IPhysicsSurfaceProps *physprops; + +// a list of all of the materials in the world model +static CUtlVector s_WorldPropList; + +//----------------------------------------------------------------------------- +// Purpose: Write key/value pairs out to a memory buffer +//----------------------------------------------------------------------------- +CTextBuffer::CTextBuffer( void ) +{ +} +CTextBuffer::~CTextBuffer( void ) +{ +} + +void CTextBuffer::WriteText( const char *pText ) +{ + int len = strlen( pText ); + CopyData( pText, len ); +} + +void CTextBuffer::WriteIntKey( const char *pKeyName, int outputData ) +{ + char tmp[1024]; + + // FAIL! + if ( strlen(pKeyName) > 1000 ) + { + Msg("Error writing collision data %s\n", pKeyName ); + return; + } + sprintf( tmp, "\"%s\" \"%d\"\n", pKeyName, outputData ); + CopyData( tmp, strlen(tmp) ); +} + +void CTextBuffer::WriteStringKey( const char *pKeyName, const char *outputData ) +{ + CopyStringQuotes( pKeyName ); + CopyData( " ", 1 ); + CopyStringQuotes( outputData ); + CopyData( "\n", 1 ); +} + +void CTextBuffer::WriteFloatKey( const char *pKeyName, float outputData ) +{ + char tmp[1024]; + + // FAIL! + if ( strlen(pKeyName) > 1000 ) + { + Msg("Error writing collision data %s\n", pKeyName ); + return; + } + sprintf( tmp, "\"%s\" \"%f\"\n", pKeyName, outputData ); + CopyData( tmp, strlen(tmp) ); +} + +void CTextBuffer::WriteFloatArrayKey( const char *pKeyName, const float *outputData, int count ) +{ + char tmp[1024]; + + // FAIL! + if ( strlen(pKeyName) > 1000 ) + { + Msg("Error writing collision data %s\n", pKeyName ); + return; + } + sprintf( tmp, "\"%s\" \"", pKeyName ); + for ( int i = 0; i < count; i++ ) + { + char buf[80]; + + sprintf( buf, "%f ", outputData[i] ); + strcat( tmp, buf ); + } + strcat( tmp, "\"\n" ); + + CopyData( tmp, strlen(tmp) ); +} + +void CTextBuffer::CopyStringQuotes( const char *pString ) +{ + CopyData( "\"", 1 ); + CopyData( pString, strlen(pString) ); + CopyData( "\"", 1 ); +} + +void CTextBuffer::Terminate( void ) +{ + CopyData( "\0", 1 ); +} + +void CTextBuffer::CopyData( const char *pData, int len ) +{ + int offset = m_buffer.AddMultipleToTail( len ); + memcpy( m_buffer.Base() + offset, pData, len ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Writes a glview text file containing the collision surface in question +// Input : *pCollide - +// *pFilename - +//----------------------------------------------------------------------------- +void DumpCollideToGlView( CPhysCollide *pCollide, const char *pFilename ) +{ + if ( !pCollide ) + return; + + Msg("Writing %s...\n", pFilename ); + Vector *outVerts; + int vertCount = physcollision->CreateDebugMesh( pCollide, &outVerts ); + FILE *fp = fopen( pFilename, "w" ); + int triCount = vertCount / 3; + int vert = 0; + for ( int i = 0; i < triCount; i++ ) + { + fprintf( fp, "3\n" ); + fprintf( fp, "%6.3f %6.3f %6.3f 1 0 0\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z ); + vert++; + fprintf( fp, "%6.3f %6.3f %6.3f 0 1 0\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z ); + vert++; + fprintf( fp, "%6.3f %6.3f %6.3f 0 0 1\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z ); + vert++; + } + fclose( fp ); + physcollision->DestroyDebugMesh( vertCount, outVerts ); +} + + +void DumpCollideToPHY( CPhysCollide *pCollide, CTextBuffer *text, const char *pFilename ) +{ + Msg("Writing %s...\n", pFilename ); + FILE *fp = fopen( pFilename, "wb" ); + phyheader_t header; + header.size = sizeof(header); + header.id = 0; + header.checkSum = 0; + header.solidCount = 1; + fwrite( &header, sizeof(header), 1, fp ); + int size = physcollision->CollideSize( pCollide ); + fwrite( &size, sizeof(int), 1, fp ); + + char *buf = (char *)malloc( size ); + physcollision->CollideWrite( buf, pCollide ); + fwrite( buf, size, 1, fp ); + + fwrite( text->GetData(), text->GetSize(), 1, fp ); + fclose( fp ); + free( buf ); +} + +CPhysCollisionEntry::CPhysCollisionEntry( CPhysCollide *pCollide ) +{ + m_pCollide = pCollide; +} + +unsigned int CPhysCollisionEntry::GetCollisionBinarySize() +{ + return physcollision->CollideSize( m_pCollide ); +} + +unsigned int CPhysCollisionEntry::WriteCollisionBinary( char *pDest ) +{ + return physcollision->CollideWrite( pDest, m_pCollide ); +} + +void CPhysCollisionEntry::DumpCollideFileName( const char *pName, int modelIndex, CTextBuffer *pTextBuffer ) +{ + char tmp[128]; + sprintf( tmp, "%s%03d.phy", pName, modelIndex ); + DumpCollideToPHY( m_pCollide, pTextBuffer, tmp ); + sprintf( tmp, "%s%03d.txt", pName, modelIndex ); + DumpCollideToGlView( m_pCollide, tmp ); +} + + +class CPhysCollisionEntrySolid : public CPhysCollisionEntry +{ +public: + CPhysCollisionEntrySolid( CPhysCollide *pCollide, const char *pMaterialName, float mass ); + + virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + +private: + float m_volume; + float m_mass; + const char *m_pMaterial; +}; + + +CPhysCollisionEntrySolid::CPhysCollisionEntrySolid( CPhysCollide *pCollide, const char *pMaterialName, float mass ) + : CPhysCollisionEntry( pCollide ) +{ + m_volume = physcollision->CollideVolume( m_pCollide ); + m_mass = mass; + m_pMaterial = pMaterialName; +} + +void CPhysCollisionEntrySolid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + DumpCollideFileName( "collide", modelIndex, pTextBuffer ); +} + +void CPhysCollisionEntrySolid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + pTextBuffer->WriteText( "solid {\n" ); + pTextBuffer->WriteIntKey( "index", collideIndex ); + pTextBuffer->WriteFloatKey( "mass", m_mass ); + if ( m_pMaterial ) + { + pTextBuffer->WriteStringKey( "surfaceprop", m_pMaterial ); + } + if ( m_volume != 0.f ) + { + pTextBuffer->WriteFloatKey( "volume", m_volume ); + } + pTextBuffer->WriteText( "}\n" ); +} + + +class CPhysCollisionEntryStaticSolid : public CPhysCollisionEntry +{ +public: + CPhysCollisionEntryStaticSolid ( CPhysCollide *pCollide, int contentsMask ); + + virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + +private: + int m_contentsMask; +}; + + +CPhysCollisionEntryStaticSolid ::CPhysCollisionEntryStaticSolid ( CPhysCollide *pCollide, int contentsMask ) + : CPhysCollisionEntry( pCollide ), m_contentsMask(contentsMask) +{ +} + +void CPhysCollisionEntryStaticSolid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + char tmp[128]; + sprintf( tmp, "static%02d", modelIndex ); + DumpCollideFileName( tmp, collideIndex, pTextBuffer ); +} + +void CPhysCollisionEntryStaticSolid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + pTextBuffer->WriteText( "staticsolid {\n" ); + pTextBuffer->WriteIntKey( "index", collideIndex ); + pTextBuffer->WriteIntKey( "contents", m_contentsMask ); + pTextBuffer->WriteText( "}\n" ); +} + +CPhysCollisionEntryStaticMesh::CPhysCollisionEntryStaticMesh( CPhysCollide *pCollide, const char *pMaterialName ) + : CPhysCollisionEntry( pCollide ) +{ + m_pMaterial = pMaterialName; +} + +void CPhysCollisionEntryStaticMesh::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + char tmp[128]; + sprintf( tmp, "mesh%02d", modelIndex ); + DumpCollideFileName( tmp, collideIndex, pTextBuffer ); +} + +void CPhysCollisionEntryStaticMesh::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + pTextBuffer->WriteText( "staticsolid {\n" ); + pTextBuffer->WriteIntKey( "index", collideIndex ); + pTextBuffer->WriteText( "}\n" ); +} + +class CPhysCollisionEntryFluid : public CPhysCollisionEntry +{ +public: + ~CPhysCollisionEntryFluid(); + CPhysCollisionEntryFluid( CPhysCollide *pCollide, const char *pSurfaceProp, float damping, const Vector &normal, float dist, int nContents ); + + virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + +private: + char *m_pSurfaceProp; + float m_damping; + Vector m_surfaceNormal; + float m_surfaceDist; + int m_contentsMask; +}; + + +CPhysCollisionEntryFluid::CPhysCollisionEntryFluid( CPhysCollide *pCollide, const char *pSurfaceProp, float damping, const Vector &normal, float dist, int nContents ) + : CPhysCollisionEntry( pCollide ) +{ + m_surfaceNormal = normal; + m_surfaceDist = dist; + m_pSurfaceProp = new char[strlen(pSurfaceProp)+1]; + strcpy( m_pSurfaceProp, pSurfaceProp ); + m_damping = damping; + m_contentsMask = nContents; +} + +CPhysCollisionEntryFluid::~CPhysCollisionEntryFluid() +{ + delete[] m_pSurfaceProp; +} + +void CPhysCollisionEntryFluid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + char tmp[128]; + sprintf( tmp, "water%02d", modelIndex ); + DumpCollideFileName( tmp, collideIndex, pTextBuffer ); +} + +void CPhysCollisionEntryFluid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + pTextBuffer->WriteText( "fluid {\n" ); + pTextBuffer->WriteIntKey( "index", collideIndex ); + pTextBuffer->WriteStringKey( "surfaceprop", m_pSurfaceProp ); // write out water material + pTextBuffer->WriteFloatKey( "damping", m_damping ); // write out water damping + pTextBuffer->WriteIntKey( "contents", m_contentsMask ); // write out water contents + float array[4]; + m_surfaceNormal.CopyToArray( array ); + array[3] = m_surfaceDist; + pTextBuffer->WriteFloatArrayKey( "surfaceplane", array, 4 ); // write out water surface plane + pTextBuffer->WriteFloatArrayKey( "currentvelocity", vec3_origin.Base(), 3 ); // write out water velocity + pTextBuffer->WriteText( "}\n" ); +} + +// Get an index into the prop list of this prop (add it if necessary) +static int PropIndex( CUtlVector &propList, int propIndex ) +{ + for ( int i = 0; i < propList.Count(); i++ ) + { + if ( propList[i] == propIndex ) + return i+1; + } + + if ( propList.Count() < 126 ) + { + return propList.AddToTail( propIndex )+1; + } + + return 0; +} + +int RemapWorldMaterial( int materialIndexIn ) +{ + return PropIndex( s_WorldPropList, materialIndexIn ); +} + +typedef struct +{ + float normal[3]; + float dist; +} listplane_t; + +static void AddListPlane( CUtlVector *list, float x, float y, float z, float d ) +{ + listplane_t plane; + plane.normal[0] = x; + plane.normal[1] = y; + plane.normal[2] = z; + plane.dist = d; + + list->AddToTail( plane ); +} + +class CPlaneList +{ +public: + + CPlaneList( float shrink, float merge ); + ~CPlaneList( void ); + + void AddConvex( CPhysConvex *pConvex ); + + // add the brushes to the model + int AddBrushes( void ); + + // Adds a single brush as a convex object + void ReferenceBrush( int brushnumber ); + bool IsBrushReferenced( int brushnumber ); + + void ReferenceLeaf( int leafIndex ); + bool IsLeafReferenced( int leafIndex ); + int GetFirstBrushSide(); + +private: + + CPhysConvex *BuildConvexForBrush( int brushnumber, float shrink, CPhysCollide *pCollideTest, float shrinkMinimum ); + +public: + CUtlVector m_convex; + + CUtlVector m_leafList; + int m_contentsMask; + + float m_shrink; + float m_merge; + bool *m_brushAdded; + float m_totalVolume; +}; + +CPlaneList::CPlaneList( float shrink, float merge ) +{ + m_shrink = shrink; + m_merge = merge; + m_contentsMask = MASK_SOLID; + m_brushAdded = new bool[numbrushes]; + memset( m_brushAdded, 0, sizeof(bool) * numbrushes ); + m_totalVolume = 0; + m_leafList.Purge(); +} + + +CPlaneList::~CPlaneList( void ) +{ + delete[] m_brushAdded; +} + + +void CPlaneList::AddConvex( CPhysConvex *pConvex ) +{ + if ( pConvex ) + { + m_totalVolume += physcollision->ConvexVolume( pConvex ); + m_convex.AddToTail( pConvex ); + } +} + +// Adds a single brush as a convex object +void CPlaneList::ReferenceBrush( int brushnumber ) +{ + if ( !(dbrushes[brushnumber].contents & m_contentsMask) ) + return; + + m_brushAdded[brushnumber] = true; + +} + + +bool CPlaneList::IsBrushReferenced( int brushnumber ) +{ + return m_brushAdded[brushnumber]; +} + +CPhysConvex *CPlaneList::BuildConvexForBrush( int brushnumber, float shrink, CPhysCollide *pCollideTest, float shrinkMinimum ) +{ + CUtlVector temp( 0, 32 ); + + for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ ) + { + dbrushside_t *pside = dbrushsides + i + dbrushes[brushnumber].firstside; + if ( pside->bevel ) + continue; + + dplane_t *pplane = dplanes + pside->planenum; + float shrinkThisPlane = shrink; + + if ( i < g_MainMap->mapbrushes[brushnumber].numsides ) + { + if ( !g_MainMap->mapbrushes[brushnumber].original_sides[i].visible ) + { + // don't shrink brush sides with no visible components. + // this produces something closer to the ideal shrink than simply shrinking all planes + shrinkThisPlane = 0; + } + } + // Make sure shrinking won't swallow geometry along this axis. + if ( pCollideTest && shrinkThisPlane != 0 ) + { + Vector start = physcollision->CollideGetExtent( pCollideTest, vec3_origin, vec3_angle, pplane->normal ); + Vector end = physcollision->CollideGetExtent( pCollideTest, vec3_origin, vec3_angle, -pplane->normal ); + float thick = DotProduct( (end-start), pplane->normal ); + // NOTE: The object must be at least "shrinkMinimum" inches wide on each axis + if ( fabs(thick) < shrinkMinimum ) + { +#if _DEBUG + Warning("Can't shrink brush %d, plane %d (%.2f, %.2f, %.2f)\n", brushnumber, pside->planenum, pplane->normal[0], pplane->normal[1], pplane->normal[2] ); +#endif + shrinkThisPlane = 0; + } + } + AddListPlane( &temp, pplane->normal[0], pplane->normal[1], pplane->normal[2], pplane->dist - shrinkThisPlane ); + } + return physcollision->ConvexFromPlanes( (float *)temp.Base(), temp.Count(), m_merge ); +} + +int CPlaneList::AddBrushes( void ) +{ + int count = 0; + for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ ) + { + if ( IsBrushReferenced(brushnumber) ) + { + CPhysConvex *pBrushConvex = NULL; + if ( m_shrink != 0 ) + { + // Make sure shrinking won't swallow this brush. + CPhysConvex *pConvex = BuildConvexForBrush( brushnumber, 0, NULL, 0 ); + CPhysCollide *pUnshrunkCollide = physcollision->ConvertConvexToCollide( &pConvex, 1 ); + pBrushConvex = BuildConvexForBrush( brushnumber, m_shrink, pUnshrunkCollide, m_shrink * 3 ); + physcollision->DestroyCollide( pUnshrunkCollide ); + } + else + { + pBrushConvex = BuildConvexForBrush( brushnumber, m_shrink, NULL, 1.0 ); + } + + if ( pBrushConvex ) + { + count++; + physcollision->SetConvexGameData( pBrushConvex, brushnumber ); + AddConvex( pBrushConvex ); + } + } + } + return count; +} + + +int CPlaneList::GetFirstBrushSide() +{ + for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ ) + { + if ( IsBrushReferenced(brushnumber) ) + { + for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ ) + { + int sideIndex = i + dbrushes[brushnumber].firstside; + dbrushside_t *pside = dbrushsides + sideIndex; + if ( pside->bevel ) + continue; + return sideIndex; + } + } + } + return 0; +} + +// UNDONE: Try using this kind of algorithm if we run into precision problems. +// NOTE: ConvexFromPlanes will be doing a bunch of matrix inversions that can suffer +// if plane normals are too close to each other... +#if 0 +void CPlaneList::AddBrushes( void ) +{ + CUtlVector temp; + for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ ) + { + if ( IsBrushReferenced(brushnumber) ) + { + CUtlVector windings; + + for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ ) + { + dbrushside_t *pside = dbrushsides + i + dbrushes[brushnumber].firstside; + if (pside->bevel) + continue; + dplane_t *pplane = dplanes + pside->planenum; + winding_t *w = BaseWindingForPlane( pplane->normal, pplane->dist - m_shrink ); + for ( int j = 0; j < dbrushes[brushnumber].numsides && w; j++ ) + { + if (i == j) + continue; + dbrushside_t *pClipSide = dbrushsides + j + dbrushes[brushnumber].firstside; + if (pClipSide->bevel) + continue; + dplane_t *pClipPlane = dplanes + pClipSide->planenum; + ChopWindingInPlace (&w, -pClipPlane->normal, -pClipPlane->dist+m_shrink, 0); //CLIP_EPSILON); + } + if ( w ) + { + windings.AddToTail( w ); + } + } + + CUtlVector vertList; + for ( int p = 0; p < windings.Count(); p++ ) + { + for ( int v = 0; v < windings[p]->numpoints; v++ ) + { + vertList.AddToTail( windings[p]->p + v ); + } + } + CPhysConvex *pConvex = physcollision->ConvexFromVerts( vertList.Base(), vertList.Count() ); + if ( pConvex ) + { + physcollision->SetConvexGameData( pConvex, brushnumber ); + AddConvex( pConvex ); + } + temp.RemoveAll(); + } + } +} +#endif + +// If I have a list of leaves, make sure this leaf is in it. +// Otherwise, process all leaves +bool CPlaneList::IsLeafReferenced( int leafIndex ) +{ + if ( !m_leafList.Count() ) + return true; + + for ( int i = 0; i < m_leafList.Count(); i++ ) + { + if ( m_leafList[i] == leafIndex ) + return true; + } + + return false; +} + +// Add a leaf to my list of interesting leaves +void CPlaneList::ReferenceLeaf( int leafIndex ) +{ + m_leafList.AddToTail( leafIndex ); +} + +static void VisitLeaves_r( CPlaneList &planes, int node ) +{ + if ( node < 0 ) + { + int leafIndex = -1 - node; + if ( planes.IsLeafReferenced(leafIndex) ) + { + int i; + + // Add the solids in the "empty" leaf + for ( i = 0; i < dleafs[leafIndex].numleafbrushes; i++ ) + { + int brushIndex = dleafbrushes[dleafs[leafIndex].firstleafbrush + i]; + planes.ReferenceBrush( brushIndex ); + } + } + } + else + { + dnode_t *pnode = dnodes + node; + + VisitLeaves_r( planes, pnode->children[0] ); + VisitLeaves_r( planes, pnode->children[1] ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +struct waterleaf_t +{ + Vector surfaceNormal; + float surfaceDist; + float minZ; + bool hasSurface; + int waterLeafIndex;// this is the submerged leaf + int planenum; //UNDONE: REMOVE + int surfaceTexInfo; // if hasSurface == true, this is the texinfo index for the water material + int outsideLeafIndex;// this is the leaf on the other side of the water surface + node_t *pNode; +}; + + + +// returns true if newleaf should appear before currentleaf in the list +static bool IsLowerLeaf( const waterleaf_t &newleaf, const waterleaf_t ¤tleaf ) +{ + if ( newleaf.hasSurface && currentleaf.hasSurface ) + { + // the one with the upmost pointing z goes first + if ( currentleaf.surfaceNormal.z > newleaf.surfaceNormal.z ) + return false; + + if ( fabs(currentleaf.surfaceNormal.z - newleaf.surfaceNormal.z) < 0.01 ) + { + if ( newleaf.surfaceDist < currentleaf.surfaceDist ) + return true; + } + return true; + } + else if ( newleaf.hasSurface ) // the leaf with a surface always goes first + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Water surfaces are stored in an RB tree and the tree is used to +// create one-off .vmt files embedded in the .bsp for each surface so that the +// water depth effect occurs on a per-water surface level. +//----------------------------------------------------------------------------- +struct WaterTexInfo +{ + // The mangled new .vmt name ( materials/levelename/oldmaterial_depth_xxx ) where xxx is + // the water depth (as an integer ) + CUtlSymbol m_FullName; + + // The original .vmt name + CUtlSymbol m_MaterialName; + + // The depth of the water this texinfo refers to + int m_nWaterDepth; + + // The texinfo id + int m_nTexInfo; + + // The subdivision size for the water surface +// float m_SubdivSize; +}; + +//----------------------------------------------------------------------------- +// Purpose: Helper for RB tree operations ( we compare full mangled names ) +// Input : src1 - +// src2 - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool WaterLessFunc( WaterTexInfo const& src1, WaterTexInfo const& src2 ) +{ + return src1.m_FullName < src2.m_FullName; +} + +//----------------------------------------------------------------------------- +// Purpose: A growable RB tree of water surfaces +//----------------------------------------------------------------------------- +static CUtlRBTree< WaterTexInfo, int > g_WaterTexInfos( 0, 32, WaterLessFunc ); + +#if 0 +float GetSubdivSizeForFogVolume( int fogVolumeID ) +{ + Assert( fogVolumeID >= 0 && fogVolumeID < g_WaterTexInfos.Count() ); + return g_WaterTexInfos[fogVolumeID].m_SubdivSize; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mapname - +// *materialname - +// waterdepth - +// *fullname - +//----------------------------------------------------------------------------- +void GetWaterTextureName( char const *mapname, char const *materialname, int waterdepth, char *fullname ) +{ + char temp[ 512 ]; + + // Construct the full name (prepend mapname to reduce name collisions) + sprintf( temp, "maps/%s/%s_depth_%i", mapname, materialname, (int)waterdepth ); + + // Make sure it's lower case + strlwr( temp ); + + strcpy( fullname, temp ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called to write procedural materials in the rb tree to the embedded +// pak file for this .bsp +//----------------------------------------------------------------------------- +void EmitWaterMaterialFile( WaterTexInfo *wti ) +{ + char waterTextureName[512]; + if ( !wti ) + { + return; + } + + GetWaterTextureName( mapbase, wti->m_MaterialName.String(), ( int )wti->m_nWaterDepth, waterTextureName ); + + // Convert to string + char szDepth[ 32 ]; + sprintf( szDepth, "%i", wti->m_nWaterDepth ); + CreateMaterialPatch( wti->m_MaterialName.String(), waterTextureName, "$waterdepth", szDepth, PATCH_INSERT ); +} + +//----------------------------------------------------------------------------- +// Purpose: Takes the texinfo_t referenced by the .vmt and the computed depth for the +// surface and looks up or creates a texdata/texinfo for the mangled one-off water .vmt file +// Input : *pBaseInfo - +// depth - +// Output : int +//----------------------------------------------------------------------------- +int FindOrCreateWaterTexInfo( texinfo_t *pBaseInfo, float depth ) +{ + char fullname[ 512 ]; + char materialname[ 512 ]; + + // Get the base texture/material name + char const *name = TexDataStringTable_GetString( GetTexData( pBaseInfo->texdata )->nameStringTableID ); + + GetWaterTextureName( mapbase, name, (int)depth, fullname ); + + // See if we already have an entry for this depth + WaterTexInfo lookup; + lookup.m_FullName = fullname; + int idx = g_WaterTexInfos.Find( lookup ); + + // If so, return the existing entry texinfo index + if ( idx != g_WaterTexInfos.InvalidIndex() ) + { + return g_WaterTexInfos[ idx ].m_nTexInfo; + } + + // Otherwise, fill in the rest of the data + lookup.m_nWaterDepth = (int)depth; + // Remember the current material name + sprintf( materialname, "%s", name ); + strlwr( materialname ); + lookup.m_MaterialName = materialname; + + texinfo_t ti; + // Make a copy + ti = *pBaseInfo; + // Create a texdata that is based on the underlying existing entry + ti.texdata = FindAliasedTexData( fullname, GetTexData( pBaseInfo->texdata ) ); + + // Find or create a new index + lookup.m_nTexInfo = FindOrCreateTexInfo( ti ); + + // Add the new texinfo to the RB tree + idx = g_WaterTexInfos.Insert( lookup ); + + // Msg( "created texinfo for %s\n", lookup.m_FullName.String() ); + + // Go ahead and create the new vmt file. + EmitWaterMaterialFile( &g_WaterTexInfos[idx] ); + + // Return the new texinfo + return g_WaterTexInfos[ idx ].m_nTexInfo; +} + +extern node_t *dfacenodes[MAX_MAP_FACES]; +static void WriteFogVolumeIDs( dmodel_t *pModel ) +{ + int i; + + // write fog volume ID to each face in this model + for( i = pModel->firstface; i < pModel->firstface + pModel->numfaces; i++ ) + { + dface_t *pFace = &dfaces[i]; + node_t *pFaceNode = dfacenodes[i]; + texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; + pFace->surfaceFogVolumeID = -1; + if ( pFaceNode ) + { + if ( (pTexInfo->flags & SURF_WARP ) && pFaceNode->planenum == PLANENUM_LEAF && pFaceNode->diskId >= 0 ) + { + pFace->surfaceFogVolumeID = dleafs[pFaceNode->diskId].leafWaterDataID; + dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[pFace->surfaceFogVolumeID]; + + // HACKHACK: Should probably mark these faces as water bottom or "bottommaterial" faces. + // HACKHACK: Use a heuristic, if it points up, it's the water top. + if ( dplanes[pFace->planenum].normal.z > 0 ) + { + pFace->texinfo = pLeafWaterData->surfaceTexInfoID; + } + } + else + { + // missed this face somehow? + Assert( !(pTexInfo->flags & SURF_WARP ) ); + } + + } + } +} + + +static bool PortalCrossesWater( waterleaf_t &baseleaf, portal_t *portal ) +{ + if ( baseleaf.hasSurface ) + { + int side = WindingOnPlaneSide( portal->winding, baseleaf.surfaceNormal, baseleaf.surfaceDist ); + if ( side == SIDE_CROSS || side == SIDE_FRONT ) + return true; + } + + return false; +} + + +static int FindOrCreateLeafWaterData( float surfaceZ, float minZ, int surfaceTexInfoID ) +{ + int i; + for( i = 0; i < numleafwaterdata; i++ ) + { + dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[i]; + if( pLeafWaterData->surfaceZ == surfaceZ && + pLeafWaterData->minZ == minZ && + pLeafWaterData->surfaceTexInfoID == surfaceTexInfoID ) + { + return i; + } + } + dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[numleafwaterdata]; + pLeafWaterData->surfaceZ = surfaceZ; + pLeafWaterData->minZ = minZ; + pLeafWaterData->surfaceTexInfoID = surfaceTexInfoID; + numleafwaterdata++; + return numleafwaterdata - 1; +} + + +// Enumerate all leaves under node with contents in contentsMask and add them to list +void EnumLeaves_r( CUtlVector &list, node_t *node, int contentsMask ) +{ + if ( node->planenum != PLANENUM_LEAF ) + { + EnumLeaves_r( list, node->children[0], contentsMask ); + EnumLeaves_r( list, node->children[1], contentsMask ); + return; + } + + if ( !(node->contents & contentsMask) ) + return; + + + // has the contents, put it in the list + list.AddToTail( node ); +} + + +// Builds a waterleaf_t for the given leaf +static void BuildWaterLeaf( node_t *pLeafIn, waterleaf_t &waterLeafOut ) +{ + waterLeafOut.pNode = pLeafIn; + waterLeafOut.waterLeafIndex = pLeafIn->diskId; + waterLeafOut.outsideLeafIndex = -1; + waterLeafOut.hasSurface = false; + waterLeafOut.surfaceDist = MAX_COORD_INTEGER; + waterLeafOut.surfaceNormal.Init( 0.f, 0.f, 1.f ); + waterLeafOut.planenum = -1; + waterLeafOut.surfaceTexInfo = -1; + waterLeafOut.minZ = MAX_COORD_INTEGER; + + // search the list of portals out of this leaf for one that leaves water + // If you find one, this leaf has a surface, so fill out the surface data + int oppositeNodeIndex = 0; + for (portal_t *p = pLeafIn->portals ; p ; p = p->next[!oppositeNodeIndex]) + { + oppositeNodeIndex = (p->nodes[0] == pLeafIn) ? 1 : 0; + + // not visible, can't be the portals we're looking for... + if ( !p->side ) + continue; + + // See if this portal crosses into air + node_t *pOpposite = p->nodes[oppositeNodeIndex]; + if ( !(pOpposite->contents & MASK_WATER) && !(pOpposite->contents & MASK_SOLID) ) + { + // it does, there must be a surface here + plane_t *plane = &g_MainMap->mapplanes[p->side->planenum]; + if ( waterLeafOut.hasSurface ) + { + // Sort to find the most upward facing normal (skips sides) + if ( waterLeafOut.surfaceNormal.z > plane->normal.z ) + continue; + if ( (waterLeafOut.surfaceNormal.z == plane->normal.z) && waterLeafOut.surfaceDist >= plane->dist ) + continue; + } + // water surface needs to point at least somewhat up, this is + // probably a map error + if ( plane->normal.z <= 0 ) + continue; + waterLeafOut.surfaceDist = plane->dist; + waterLeafOut.surfaceNormal = plane->normal; + waterLeafOut.hasSurface = true; + waterLeafOut.outsideLeafIndex = p->nodes[oppositeNodeIndex]->diskId; + waterLeafOut.surfaceTexInfo = p->side->texinfo; + } + } +} + + +static void InsertSortWaterLeaf( CUtlVector &list, const waterleaf_t &leafInsert ) +{ + // insertion sort the leaf (lowest leaves go first) + // leaves that aren't actually on the surface of the water will have leaf.hasSurface == false. + for ( int i = 0; i < list.Count(); i++ ) + { + if ( IsLowerLeaf( leafInsert, list[i] ) ) + { + list.InsertBefore( i, leafInsert ); + return; + } + } + + // must the highest one, so stick it at the end. + list.AddToTail( leafInsert ); +} + + +// Flood fill the tree, finding neighboring water volumes and connecting them to this list +// Cut groups that try to cross the surface. +// Mark leaves that are in a group as "visited" so they won't be chosen by subsequent fills +static void Flood_FindConnectedWaterVolumes_r( CUtlVector &list, node_t *pLeaf, waterleaf_t &baseleaf, leafbitarray_t &visited ) +{ + // already visited, or not the same water contents + if ( pLeaf->diskId < 0 || visited.Get(pLeaf->diskId) || !(pLeaf->contents & (baseleaf.pNode->contents & MASK_WATER) ) ) + return; + + int oppositeNodeIndex = 0; + for (portal_t *p = pLeaf->portals ; p ; p = p->next[!oppositeNodeIndex]) + { + oppositeNodeIndex = (p->nodes[0] == pLeaf) ? 1 : 0; + + // If any portal crosses the water surface, don't flow through this leaf + if ( PortalCrossesWater( baseleaf, p ) ) + return; + } + + visited.Set( pLeaf->diskId ); + list.AddToTail( pLeaf ); + + baseleaf.minZ = min( pLeaf->mins.z, baseleaf.minZ ); + + for (portal_t *p = pLeaf->portals ; p ; p = p->next[!oppositeNodeIndex]) + { + oppositeNodeIndex = (p->nodes[0] == pLeaf) ? 1 : 0; + + Flood_FindConnectedWaterVolumes_r( list, p->nodes[oppositeNodeIndex], baseleaf, visited ); + } +} + +// UNDONE: This is a bit of a hack to avoid crashing when we can't find an +// appropriate texinfo for a water model (to get physics properties) +int FirstWaterTexinfo( bspbrush_t *brushlist, int contents ) +{ + while (brushlist) + { + if ( brushlist->original->contents & contents ) + { + for ( int i = 0; i < brushlist->original->numsides; i++ ) + { + if ( brushlist->original->original_sides[i].contents & contents ) + { + return brushlist->original->original_sides[i].texinfo; + } + } + } + brushlist = brushlist->next; + } + + Assert(0); + return 0; +} + +// This is a list of water data that will be turned into physics models +struct watermodel_t +{ + int modelIndex; + int contents; + waterleaf_t waterLeafData; + int depthTexinfo; + int firstWaterLeafIndex; + int waterLeafCount; + int fogVolumeIndex; +}; + +static CUtlVector g_WaterModels; +static CUtlVector g_WaterLeafList; + +// Creates a list of watermodel_t for later processing by EmitPhysCollision +void EmitWaterVolumesForBSP( dmodel_t *pModel, node_t *node ) +{ + CUtlVector leafListAnyWater; + // build the list of all leaves containing water + EnumLeaves_r( leafListAnyWater, node, MASK_WATER ); + + // make a sorted list to flood fill + CUtlVector list; + + int i; + for ( i = 0; i < leafListAnyWater.Count(); i++ ) + { + waterleaf_t waterLeaf; + BuildWaterLeaf( leafListAnyWater[i], waterLeaf ); + InsertSortWaterLeaf( list, waterLeaf ); + } + + leafbitarray_t visited; + CUtlVector waterAreaList; + for ( i = 0; i < list.Count(); i++ ) + { + Flood_FindConnectedWaterVolumes_r( waterAreaList, list[i].pNode, list[i], visited ); + + // did we find a list of leaves connected to this one? + // remember the list is sorted, so this one may have been attached to a previous + // leaf. So it could have nothing hanging off of it. + if ( waterAreaList.Count() ) + { + // yes, emit a watermodel + watermodel_t tmp; + tmp.modelIndex = nummodels; + tmp.contents = list[i].pNode->contents; + tmp.waterLeafData = list[i]; + tmp.firstWaterLeafIndex = g_WaterLeafList.Count(); + tmp.waterLeafCount = waterAreaList.Count(); + + float waterDepth = tmp.waterLeafData.surfaceDist - tmp.waterLeafData.minZ; + if ( tmp.waterLeafData.surfaceTexInfo < 0 ) + { + // the map has probably leaked in this case, but output something anyway. + Assert(list[i].pNode->planenum == PLANENUM_LEAF); + tmp.waterLeafData.surfaceTexInfo = FirstWaterTexinfo( list[i].pNode->brushlist, tmp.contents ); + } + tmp.depthTexinfo = FindOrCreateWaterTexInfo( &texinfo[ tmp.waterLeafData.surfaceTexInfo ], waterDepth ); + tmp.fogVolumeIndex = FindOrCreateLeafWaterData( tmp.waterLeafData.surfaceDist, tmp.waterLeafData.minZ, tmp.waterLeafData.surfaceTexInfo ); + + for ( int j = 0; j < waterAreaList.Count(); j++ ) + { + g_WaterLeafList.AddToTail( waterAreaList[j]->diskId ); + } + waterAreaList.RemoveAll(); + g_WaterModels.AddToTail( tmp ); + } + } + + WriteFogVolumeIDs( pModel ); +} + + +static void ConvertWaterModelToPhysCollide( CUtlVector &collisionList, int modelIndex, + float shrinkSize, float mergeTolerance ) +{ + dmodel_t *pModel = dmodels + modelIndex; + + for ( int i = 0; i < g_WaterModels.Count(); i++ ) + { + watermodel_t &waterModel = g_WaterModels[i]; + if ( waterModel.modelIndex != modelIndex ) + continue; + + CPlaneList planes( shrinkSize, mergeTolerance ); + int firstLeaf = waterModel.firstWaterLeafIndex; + planes.m_contentsMask = waterModel.contents; + + // push all of the leaves into the collision list + for ( int j = 0; j < waterModel.waterLeafCount; j++ ) + { + int leafIndex = g_WaterLeafList[firstLeaf + j]; + + dleaf_t *pLeaf = dleafs + leafIndex; + // fixup waterdata + pLeaf->leafWaterDataID = waterModel.fogVolumeIndex; + planes.ReferenceLeaf( leafIndex ); + } + + // visit the referenced leaves that belong to this model + VisitLeaves_r( planes, pModel->headnode ); + + // Now add the brushes from those leaves as convex + + // BUGBUG: NOTE: If your map has a brush that crosses the surface, it will be added to two water + // volumes. This only happens with connected water volumes with multiple surface heights + // UNDONE: Right now map makers must cut such brushes. It could be automatically cut by adding the + // surface plane to the list for each brush before calling ConvexFromPlanes() + planes.AddBrushes(); + + int count = planes.m_convex.Count(); + if ( !count ) + continue; + + // Save off the plane of the surface for this group as well as the collision model + // for all convex objects in the group. + CPhysCollide *pCollide = physcollision->ConvertConvexToCollide( planes.m_convex.Base(), count ); + if ( pCollide ) + { + int waterSurfaceTexInfoID = -1; + // use defaults + const char *pSurfaceProp = "water"; + float damping = 0.01; + if ( waterSurfaceTexInfoID >= 0 ) + { + // material override + int texdata = texinfo[waterSurfaceTexInfoID].texdata; + int prop = g_SurfaceProperties[texdata]; + pSurfaceProp = physprops->GetPropName( prop ); + } + + if ( !waterModel.waterLeafData.hasSurface ) + { + waterModel.waterLeafData.surfaceNormal.Init( 0,0,1 ); + Vector top = physcollision->CollideGetExtent( pCollide, vec3_origin, vec3_angle, waterModel.waterLeafData.surfaceNormal ); + waterModel.waterLeafData.surfaceDist = top.z; + } + CPhysCollisionEntryFluid *pCollisionEntryFuild = new CPhysCollisionEntryFluid( pCollide, + pSurfaceProp, damping, waterModel.waterLeafData.surfaceNormal, waterModel.waterLeafData.surfaceDist, waterModel.contents ); + collisionList.AddToTail( pCollisionEntryFuild ); + } + } +} + +// compute a normal for a triangle of the given three points (points are clockwise, normal points out) +static Vector TriangleNormal( const Vector &p0, const Vector &p1, const Vector &p2 ) +{ + Vector e0 = p1 - p0; + Vector e1 = p2 - p0; + Vector normal = CrossProduct( e1, e0 ); + VectorNormalize( normal ); + + return normal; +} + + +// find the side of the brush with the normal closest to the given normal +static dbrushside_t *FindBrushSide( int brushIndex, const Vector &normal ) +{ + dbrush_t *pbrush = &dbrushes[brushIndex]; + dbrushside_t *out = NULL; + float best = -1.f; + + for ( int i = 0; i < pbrush->numsides; i++ ) + { + dbrushside_t *pside = dbrushsides + i + pbrush->firstside; + dplane_t *pplane = dplanes + pside->planenum; + float dot = DotProduct( normal, pplane->normal ); + if ( dot > best ) + { + best = dot; + out = pside; + } + } + + return out; +} + + + +static void ConvertWorldBrushesToPhysCollide( CUtlVector &collisionList, float shrinkSize, float mergeTolerance, int contentsMask ) +{ + CPlaneList planes( shrinkSize, mergeTolerance ); + + planes.m_contentsMask = contentsMask; + + VisitLeaves_r( planes, dmodels[0].headnode ); + planes.AddBrushes(); + + int count = planes.m_convex.Count(); + if ( count ) + { + CPhysCollide *pCollide = physcollision->ConvertConvexToCollide( planes.m_convex.Base(), count ); + + ICollisionQuery *pQuery = physcollision->CreateQueryModel( pCollide ); + int convex = pQuery->ConvexCount(); + for ( int i = 0; i < convex; i++ ) + { + int triCount = pQuery->TriangleCount( i ); + int brushIndex = pQuery->GetGameData( i ); + + Vector points[3]; + for ( int j = 0; j < triCount; j++ ) + { + pQuery->GetTriangleVerts( i, j, points ); + Vector normal = TriangleNormal( points[0], points[1], points[2] ); + dbrushside_t *pside = FindBrushSide( brushIndex, normal ); + if ( pside->texinfo != TEXINFO_NODE ) + { + int prop = g_SurfaceProperties[texinfo[pside->texinfo].texdata]; + pQuery->SetTriangleMaterialIndex( i, j, RemapWorldMaterial( prop ) ); + } + } + } + physcollision->DestroyQueryModel( pQuery ); + pQuery = NULL; + + collisionList.AddToTail( new CPhysCollisionEntryStaticSolid( pCollide, contentsMask ) ); + } +} + +// adds any world, terrain, and water collision models to the collision list +static void BuildWorldPhysModel( CUtlVector &collisionList, float shrinkSize, float mergeTolerance ) +{ + ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, MASK_SOLID ); + ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, CONTENTS_PLAYERCLIP ); + ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, CONTENTS_MONSTERCLIP ); + + if ( !g_bNoVirtualMesh && Disp_HasPower4Displacements() ) + { + Warning("WARNING: Map using power 4 displacements, terrain physics cannot be compressed, map will need additional memory and CPU.\n"); + g_bNoVirtualMesh = true; + } + + // if there's terrain, save it off as a static mesh/polysoup + if ( g_bNoVirtualMesh || !physcollision->SupportsVirtualMesh() ) + { + Disp_AddCollisionModels( collisionList, &dmodels[0], MASK_SOLID ); + } + else + { + Disp_BuildVirtualMesh( MASK_SOLID ); + } + ConvertWaterModelToPhysCollide( collisionList, 0, shrinkSize, mergeTolerance ); +} + + +// adds a collision entry for this brush model +static void ConvertModelToPhysCollide( CUtlVector &collisionList, int modelIndex, int contents, float shrinkSize, float mergeTolerance ) +{ + int i; + CPlaneList planes( shrinkSize, mergeTolerance ); + + planes.m_contentsMask = contents; + + dmodel_t *pModel = dmodels + modelIndex; + VisitLeaves_r( planes, pModel->headnode ); + planes.AddBrushes(); + int count = planes.m_convex.Count(); + convertconvexparams_t params; + params.Defaults(); + params.buildOuterConvexHull = count > 1 ? true : false; + params.buildDragAxisAreas = true; + Vector size = pModel->maxs - pModel->mins; + + float minSurfaceArea = -1.0f; + for ( i = 0; i < 3; i++ ) + { + int other = (i+1)%3; + int cross = (i+2)%3; + float surfaceArea = size[other] * size[cross]; + if ( minSurfaceArea < 0 || surfaceArea < minSurfaceArea ) + { + minSurfaceArea = surfaceArea; + } + } + // this can be really slow with super-large models and a low error tolerance + // Basically you get a ray cast through each square of epsilon surface area on each OBB side + // So compute it for 1% error (on the smallest side, less on larger sides) + params.dragAreaEpsilon = clamp( minSurfaceArea * 1e-2f, 1.0f, 1024.0f ); + CPhysCollide *pCollide = physcollision->ConvertConvexToCollideParams( planes.m_convex.Base(), count, params ); + + if ( !pCollide ) + return; + + struct + { + int prop; + float area; + } proplist[256]; + int numprops = 1; + + proplist[0].prop = -1; + proplist[0].area = 1; + // compute the array of props on the surface of this model + + // NODRAW brushes no longer have any faces + if ( !dmodels[modelIndex].numfaces ) + { + int sideIndex = planes.GetFirstBrushSide(); + int texdata = texinfo[dbrushsides[sideIndex].texinfo].texdata; + int prop = g_SurfaceProperties[texdata]; + proplist[numprops].prop = prop; + proplist[numprops].area = 2; + numprops++; + } + + for ( i = 0; i < dmodels[modelIndex].numfaces; i++ ) + { + dface_t *face = dfaces + i + dmodels[modelIndex].firstface; + int texdata = texinfo[face->texinfo].texdata; + int prop = g_SurfaceProperties[texdata]; + int j; + for ( j = 0; j < numprops; j++ ) + { + if ( proplist[j].prop == prop ) + { + proplist[j].area += face->area; + break; + } + } + + if ( (!numprops || j >= numprops) && numprops < ARRAYSIZE(proplist) ) + { + proplist[numprops].prop = prop; + proplist[numprops].area = face->area; + numprops++; + } + } + + + // choose the prop with the most surface area + int maxIndex = -1; + float maxArea = 0; + float totalArea = 0; + + for ( i = 0; i < numprops; i++ ) + { + if ( proplist[i].area > maxArea ) + { + maxIndex = i; + maxArea = proplist[i].area; + } + // add up the total surface area + totalArea += proplist[i].area; + } + + float mass = 1.0f; + const char *pMaterial = "default"; + if ( maxIndex >= 0 ) + { + int prop = proplist[maxIndex].prop; + + // use default if this material has no prop + if ( prop < 0 ) + prop = 0; + + pMaterial = physprops->GetPropName( prop ); + float density, thickness; + physprops->GetPhysicsProperties( prop, &density, &thickness, NULL, NULL ); + + // if this is a "shell" material (it is hollow and encloses some empty space) + // compute the mass with a constant surface thickness + if ( thickness != 0 ) + { + mass = totalArea * thickness * density * CUBIC_METERS_PER_CUBIC_INCH; + } + else + { + // material is completely solid, compute total mass as if constant density throughout. + mass = planes.m_totalVolume * density * CUBIC_METERS_PER_CUBIC_INCH; + } + } + + // Clamp mass to 100,000 kg + if ( mass > VPHYSICS_MAX_MASS ) + { + mass = VPHYSICS_MAX_MASS; + } + + collisionList.AddToTail( new CPhysCollisionEntrySolid( pCollide, pMaterial, mass ) ); +} + +static void ClearLeafWaterData( void ) +{ + int i; + + for( i = 0; i < numleafs; i++ ) + { + dleafs[i].leafWaterDataID = -1; + dleafs[i].contents &= ~CONTENTS_TESTFOGVOLUME; + } +} + + +// This is the only public entry to this file. +// The global data touched in the file is: +// from bsplib.h: +// g_pPhysCollide : This is an output from this file. +// g_PhysCollideSize : This is set in this file. +// g_numdispinfo : This is an input to this file. +// g_dispinfo : This is an input to this file. +// numnodewaterdata : This is an output from this file. +// dleafwaterdata : This is an output from this file. +// from vbsp.h: +// g_SurfaceProperties : This is an input to this file. +void EmitPhysCollision() +{ + ClearLeafWaterData(); + + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); + if ( physicsFactory ) + { + physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); + } + + if ( !physcollision ) + { + Warning("!!! WARNING: Can't build collision data!\n" ); + return; + } + + CUtlVector collisionList[MAX_MAP_MODELS]; + CTextBuffer *pTextBuffer[MAX_MAP_MODELS]; + + int physModelCount = 0, totalSize = 0; + + int start = Plat_FloatTime(); + + Msg("Building Physics collision data...\n" ); + + int i, j; + for ( i = 0; i < nummodels; i++ ) + { + // Build a list of collision models for this brush model section + if ( i == 0 ) + { + // world is the only model that processes water separately. + // other brushes are assumed to be completely solid or completely liquid + BuildWorldPhysModel( collisionList[i], NO_SHRINK, VPHYSICS_MERGE); + } + else + { + ConvertModelToPhysCollide( collisionList[i], i, MASK_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|MASK_WATER, VPHYSICS_SHRINK, VPHYSICS_MERGE ); + } + + pTextBuffer[i] = NULL; + if ( !collisionList[i].Count() ) + continue; + + // if we've got collision models, write their script for processing in the game + pTextBuffer[i] = new CTextBuffer; + for ( j = 0; j < collisionList[i].Count(); j++ ) + { + // dump a text file for visualization + if ( dumpcollide ) + { + collisionList[i][j]->DumpCollide( pTextBuffer[i], i, j ); + } + // each model knows how to write its script + collisionList[i][j]->WriteToTextBuffer( pTextBuffer[i], i, j ); + // total up the binary section's size + totalSize += collisionList[i][j]->GetCollisionBinarySize() + sizeof(int); + } + + // These sections only appear in the world's collision text + if ( i == 0 ) + { + if ( !g_bNoVirtualMesh && physcollision->SupportsVirtualMesh() ) + { + pTextBuffer[i]->WriteText("virtualterrain {}\n"); + } + if ( s_WorldPropList.Count() ) + { + pTextBuffer[i]->WriteText( "materialtable {\n" ); + for ( j = 0; j < s_WorldPropList.Count(); j++ ) + { + int propIndex = s_WorldPropList[j]; + if ( propIndex < 0 ) + { + pTextBuffer[i]->WriteIntKey( "default", j+1 ); + } + else + { + pTextBuffer[i]->WriteIntKey( physprops->GetPropName( propIndex ), j+1 ); + } + } + pTextBuffer[i]->WriteText( "}\n" ); + } + } + + pTextBuffer[i]->Terminate(); + + // total lump size includes the text buffers (scripts) + totalSize += pTextBuffer[i]->GetSize(); + + physModelCount++; + } + + // add one for tail of list marker + physModelCount++; + + // DWORD align the lump because AddLump assumes that it is DWORD aligned. + byte *ptr ; + g_PhysCollideSize = totalSize + (physModelCount * sizeof(dphysmodel_t)); + g_pPhysCollide = (byte *)malloc(( g_PhysCollideSize + 3 ) & ~3 ); + memset( g_pPhysCollide, 0, g_PhysCollideSize ); + ptr = g_pPhysCollide; + + for ( i = 0; i < nummodels; i++ ) + { + if ( pTextBuffer[i] ) + { + int j; + + dphysmodel_t model; + + model.modelIndex = i; + model.solidCount = collisionList[i].Count(); + model.dataSize = sizeof(int) * model.solidCount; + + for ( j = 0; j < model.solidCount; j++ ) + { + model.dataSize += collisionList[i][j]->GetCollisionBinarySize(); + } + model.keydataSize = pTextBuffer[i]->GetSize(); + + // store the header + memcpy( ptr, &model, sizeof(model) ); + ptr += sizeof(model); + + for ( j = 0; j < model.solidCount; j++ ) + { + int collideSize = collisionList[i][j]->GetCollisionBinarySize(); + + // write size + memcpy( ptr, &collideSize, sizeof(int) ); + ptr += sizeof(int); + + // now write the collision model + collisionList[i][j]->WriteCollisionBinary( reinterpret_cast(ptr) ); + ptr += collideSize; + } + + memcpy( ptr, pTextBuffer[i]->GetData(), pTextBuffer[i]->GetSize() ); + ptr += pTextBuffer[i]->GetSize(); + } + + delete pTextBuffer[i]; + } + + dphysmodel_t model; + + // Mark end of list + model.modelIndex = -1; + model.dataSize = -1; + model.keydataSize = 0; + model.solidCount = 0; + memcpy( ptr, &model, sizeof(model) ); + ptr += sizeof(model); + Assert( (ptr-g_pPhysCollide) == g_PhysCollideSize); + Msg("done (%d) (%d bytes)\n", (int)(Plat_FloatTime() - start), g_PhysCollideSize ); + + // UNDONE: Collision models (collisionList) memory leak! +} diff --git a/mp/src/utils/vbsp/ivp.h b/mp/src/utils/vbsp/ivp.h index d3c310dc..318a199d 100644 --- a/mp/src/utils/vbsp/ivp.h +++ b/mp/src/utils/vbsp/ivp.h @@ -1,75 +1,75 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef IVP_H -#define IVP_H -#ifdef _WIN32 -#pragma once -#endif - -#include "utlvector.h" - -class CPhysCollide; -class CTextBuffer; -class IPhysicsCollision; -extern IPhysicsCollision *physcollision; - -// a list of all of the materials in the world model -extern int RemapWorldMaterial( int materialIndexIn ); - -class CPhysCollisionEntry -{ -public: - CPhysCollisionEntry( CPhysCollide *pCollide ); - - virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) = 0; - virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) = 0; - - unsigned int GetCollisionBinarySize(); - unsigned int WriteCollisionBinary( char *pDest ); - -protected: - void DumpCollideFileName( const char *pName, int modelIndex, CTextBuffer *pTextBuffer ); - -protected: - CPhysCollide *m_pCollide; -}; - -class CPhysCollisionEntryStaticMesh : public CPhysCollisionEntry -{ -public: - CPhysCollisionEntryStaticMesh( CPhysCollide *pCollide, const char *pMaterialName ); - - virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); - virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); - -private: - const char *m_pMaterial; -}; - - -class CTextBuffer -{ -public: - CTextBuffer( void ); - ~CTextBuffer( void ); - inline int GetSize( void ) { return m_buffer.Count(); } - inline char *GetData( void ) { return m_buffer.Base(); } - - void WriteText( const char *pText ); - void WriteIntKey( const char *pKeyName, int outputData ); - void WriteStringKey( const char *pKeyName, const char *outputData ); - void WriteFloatKey( const char *pKeyName, float outputData ); - void WriteFloatArrayKey( const char *pKeyName, const float *outputData, int count ); - - void CopyStringQuotes( const char *pString ); - void Terminate( void ); -private: - void CopyData( const char *pData, int len ); - CUtlVector m_buffer; -}; - -#endif // IVP_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef IVP_H +#define IVP_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utlvector.h" + +class CPhysCollide; +class CTextBuffer; +class IPhysicsCollision; +extern IPhysicsCollision *physcollision; + +// a list of all of the materials in the world model +extern int RemapWorldMaterial( int materialIndexIn ); + +class CPhysCollisionEntry +{ +public: + CPhysCollisionEntry( CPhysCollide *pCollide ); + + virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) = 0; + virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) = 0; + + unsigned int GetCollisionBinarySize(); + unsigned int WriteCollisionBinary( char *pDest ); + +protected: + void DumpCollideFileName( const char *pName, int modelIndex, CTextBuffer *pTextBuffer ); + +protected: + CPhysCollide *m_pCollide; +}; + +class CPhysCollisionEntryStaticMesh : public CPhysCollisionEntry +{ +public: + CPhysCollisionEntryStaticMesh( CPhysCollide *pCollide, const char *pMaterialName ); + + virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + +private: + const char *m_pMaterial; +}; + + +class CTextBuffer +{ +public: + CTextBuffer( void ); + ~CTextBuffer( void ); + inline int GetSize( void ) { return m_buffer.Count(); } + inline char *GetData( void ) { return m_buffer.Base(); } + + void WriteText( const char *pText ); + void WriteIntKey( const char *pKeyName, int outputData ); + void WriteStringKey( const char *pKeyName, const char *outputData ); + void WriteFloatKey( const char *pKeyName, float outputData ); + void WriteFloatArrayKey( const char *pKeyName, const float *outputData, int count ); + + void CopyStringQuotes( const char *pString ); + void Terminate( void ); +private: + void CopyData( const char *pData, int len ); + CUtlVector m_buffer; +}; + +#endif // IVP_H diff --git a/mp/src/utils/vbsp/leakfile.cpp b/mp/src/utils/vbsp/leakfile.cpp index bd8a9b28..d6038830 100644 --- a/mp/src/utils/vbsp/leakfile.cpp +++ b/mp/src/utils/vbsp/leakfile.cpp @@ -1,168 +1,168 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "vbsp.h" -#include "color.h" - -/* -============================================================================== - -LEAF FILE GENERATION - -Save out name.line for qe3 to read -============================================================================== -*/ - - -/* -============= -LeakFile - -Finds the shortest possible chain of portals -that leads from the outside leaf to a specifically -occupied leaf -============= -*/ -void LeakFile (tree_t *tree) -{ - Vector mid; - FILE *linefile; - char filename[1024]; - node_t *node; - int count; - - if (!tree->outside_node.occupied) - return; - - tree->leaked = true; - qprintf ("--- LeakFile ---\n"); - - // - // write the points to the file - // - sprintf (filename, "%s.lin", source); - linefile = fopen (filename, "w"); - if (!linefile) - Error ("Couldn't open %s\n", filename); - - count = 0; - node = &tree->outside_node; - while (node->occupied > 1) - { - portal_t *nextportal = NULL; - node_t *nextnode = NULL; - int s = 0; - - // find the best portal exit - int next = node->occupied; - for (portal_t *p=node->portals ; p ; p = p->next[!s]) - { - s = (p->nodes[0] == node); - if (p->nodes[s]->occupied - && p->nodes[s]->occupied < next) - { - nextportal = p; - nextnode = p->nodes[s]; - next = nextnode->occupied; - } - } - node = nextnode; - WindingCenter (nextportal->winding, mid); - fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); - count++; - } - - // Add the occupant's origin to the leakfile. - Vector origin; - GetVectorForKey (node->occupant, "origin", origin); - - fprintf (linefile, "%f %f %f\n", origin[0], origin[1], origin[2]); - qprintf ("%5i point linefile\n", count+1); - - fclose (linefile); - - // Emit a leak warning. - const char *cl = ValueForKey (node->occupant, "classname"); - Color red(255,0,0,255); - ColorSpewMessage( SPEW_MESSAGE, &red, "Entity %s (%.2f %.2f %.2f) leaked!\n", cl, origin[0], origin[1], origin[2] ); -} - -void AreaportalLeakFile( tree_t *tree, portal_t *pStartPortal, portal_t *pEndPortal, node_t *pStart ) -{ - Vector mid; - FILE *linefile; - char filename[1024]; - node_t *node; - int count; - - // wrote a leak line file already, don't overwrite it with the areaportal leak file - if ( tree->leaked ) - return; - - tree->leaked = true; - qprintf ("--- LeakFile ---\n"); - - // - // write the points to the file - // - sprintf (filename, "%s.lin", source); - linefile = fopen (filename, "w"); - if (!linefile) - Error ("Couldn't open %s\n", filename); - - count = 2; - WindingCenter (pEndPortal->winding, mid); - fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); - mid = 0.5 * (pStart->mins + pStart->maxs); - fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); - - node = pStart; - while (node->occupied >= 1) - { - portal_t *nextportal = NULL; - node_t *nextnode = NULL; - int s = 0; - - // find the best portal exit - int next = node->occupied; - for (portal_t *p=node->portals ; p ; p = p->next[!s]) - { - s = (p->nodes[0] == node); - if (p->nodes[s]->occupied - && p->nodes[s]->occupied < next) - { - nextportal = p; - nextnode = p->nodes[s]; - next = nextnode->occupied; - } - } - if ( !nextnode ) - break; - node = nextnode; - WindingCenter (nextportal->winding, mid); - fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); - count++; - } - // add the occupant center - if ( node ) - { - mid = 0.5 * (node->mins + node->maxs); - fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); - count++; - } - WindingCenter (pStartPortal->winding, mid); - count++; - fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); - - qprintf ("%5i point linefile\n", count); - - fclose (linefile); - Warning( "Wrote %s\n", filename ); - Color red(255,0,0,255); - ColorSpewMessage( SPEW_MESSAGE, &red, "Areaportal leak ! File: %s ", filename ); +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" +#include "color.h" + +/* +============================================================================== + +LEAF FILE GENERATION + +Save out name.line for qe3 to read +============================================================================== +*/ + + +/* +============= +LeakFile + +Finds the shortest possible chain of portals +that leads from the outside leaf to a specifically +occupied leaf +============= +*/ +void LeakFile (tree_t *tree) +{ + Vector mid; + FILE *linefile; + char filename[1024]; + node_t *node; + int count; + + if (!tree->outside_node.occupied) + return; + + tree->leaked = true; + qprintf ("--- LeakFile ---\n"); + + // + // write the points to the file + // + sprintf (filename, "%s.lin", source); + linefile = fopen (filename, "w"); + if (!linefile) + Error ("Couldn't open %s\n", filename); + + count = 0; + node = &tree->outside_node; + while (node->occupied > 1) + { + portal_t *nextportal = NULL; + node_t *nextnode = NULL; + int s = 0; + + // find the best portal exit + int next = node->occupied; + for (portal_t *p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (p->nodes[s]->occupied + && p->nodes[s]->occupied < next) + { + nextportal = p; + nextnode = p->nodes[s]; + next = nextnode->occupied; + } + } + node = nextnode; + WindingCenter (nextportal->winding, mid); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + count++; + } + + // Add the occupant's origin to the leakfile. + Vector origin; + GetVectorForKey (node->occupant, "origin", origin); + + fprintf (linefile, "%f %f %f\n", origin[0], origin[1], origin[2]); + qprintf ("%5i point linefile\n", count+1); + + fclose (linefile); + + // Emit a leak warning. + const char *cl = ValueForKey (node->occupant, "classname"); + Color red(255,0,0,255); + ColorSpewMessage( SPEW_MESSAGE, &red, "Entity %s (%.2f %.2f %.2f) leaked!\n", cl, origin[0], origin[1], origin[2] ); +} + +void AreaportalLeakFile( tree_t *tree, portal_t *pStartPortal, portal_t *pEndPortal, node_t *pStart ) +{ + Vector mid; + FILE *linefile; + char filename[1024]; + node_t *node; + int count; + + // wrote a leak line file already, don't overwrite it with the areaportal leak file + if ( tree->leaked ) + return; + + tree->leaked = true; + qprintf ("--- LeakFile ---\n"); + + // + // write the points to the file + // + sprintf (filename, "%s.lin", source); + linefile = fopen (filename, "w"); + if (!linefile) + Error ("Couldn't open %s\n", filename); + + count = 2; + WindingCenter (pEndPortal->winding, mid); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + mid = 0.5 * (pStart->mins + pStart->maxs); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + + node = pStart; + while (node->occupied >= 1) + { + portal_t *nextportal = NULL; + node_t *nextnode = NULL; + int s = 0; + + // find the best portal exit + int next = node->occupied; + for (portal_t *p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (p->nodes[s]->occupied + && p->nodes[s]->occupied < next) + { + nextportal = p; + nextnode = p->nodes[s]; + next = nextnode->occupied; + } + } + if ( !nextnode ) + break; + node = nextnode; + WindingCenter (nextportal->winding, mid); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + count++; + } + // add the occupant center + if ( node ) + { + mid = 0.5 * (node->mins + node->maxs); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + count++; + } + WindingCenter (pStartPortal->winding, mid); + count++; + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + + qprintf ("%5i point linefile\n", count); + + fclose (linefile); + Warning( "Wrote %s\n", filename ); + Color red(255,0,0,255); + ColorSpewMessage( SPEW_MESSAGE, &red, "Areaportal leak ! File: %s ", filename ); } \ No newline at end of file diff --git a/mp/src/utils/vbsp/manifest.cpp b/mp/src/utils/vbsp/manifest.cpp index 44dd07b8..c72a9564 100644 --- a/mp/src/utils/vbsp/manifest.cpp +++ b/mp/src/utils/vbsp/manifest.cpp @@ -1,568 +1,568 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -#include "vbsp.h" -#include "map_shared.h" -#include "fgdlib/fgdlib.h" -#include "manifest.h" -#include "windows.h" - -//----------------------------------------------------------------------------- -// Purpose: default constructor -//----------------------------------------------------------------------------- -CManifestMap::CManifestMap( void ) -{ - m_RelativeMapFileName[ 0 ] = 0; - m_bTopLevelMap = false; -} - - -//----------------------------------------------------------------------------- -// Purpose: default constructor -//----------------------------------------------------------------------------- -CManifest::CManifest( void ) -{ - m_InstancePath[ 0 ] = 0; - m_bIsCordoning = false; - m_CordoningMapEnt = NULL; -} - - -//----------------------------------------------------------------------------- -// Purpose: this function will parse through the known keys for the manifest map entry -// Input : szKey - the key name -// szValue - the value -// pManifestMap - the manifest map this belongs to -// Output : ChunkFileResult_t - result of the parsing -//----------------------------------------------------------------------------- -ChunkFileResult_t CManifest::LoadManifestMapKeyCallback( const char *szKey, const char *szValue, CManifestMap *pManifestMap ) -{ - if ( !stricmp( szKey, "Name" ) ) - { - // pManifestMap->m_FriendlyName = szValue; - } - else if ( !stricmp( szKey, "File" ) ) - { - strcpy( pManifestMap->m_RelativeMapFileName, szValue ); - } - else if ( !stricmp( szKey, "IsPrimary" ) ) - { - // pManifestMap->m_bPrimaryMap = ( atoi( szValue ) == 1 ); - } - else if ( !stricmp( szKey, "IsProtected" ) ) - { - // pManifestMap->m_bCanBeModified = ( atoi( szValue ) != 1 ); - } - else if ( !stricmp( szKey, "TopLevel" ) ) - { - pManifestMap->m_bTopLevelMap = ( atoi( szValue ) == 1 ); - } - - return ChunkFile_Ok; -} - - -//----------------------------------------------------------------------------- -// Purpose: this function is responsible for setting up the manifest map about to be read in -// Input : pFile - the chunk file being read -// pDoc - the owning manifest document -// Output : ChunkFileResult_t - result of the parsing -//----------------------------------------------------------------------------- -ChunkFileResult_t CManifest::LoadManifestVMFCallback( CChunkFile *pFile, CManifest *pManifest ) -{ - CManifestMap *pManifestMap = new CManifestMap(); - - pManifest->m_Maps.AddToTail( pManifestMap ); - - ChunkFileResult_t eResult = pFile->ReadChunk( ( KeyHandler_t )LoadManifestMapKeyCallback, pManifestMap ); - - return( eResult ); -} - - -//----------------------------------------------------------------------------- -// Purpose: this function will load the VMF chunk -// Input : pFile - the chunk file being read -// pDoc - the owning manifest document -// Output : ChunkFileResult_t - result of the parsing -//----------------------------------------------------------------------------- -ChunkFileResult_t CManifest::LoadManifestMapsCallback( CChunkFile *pFile, CManifest *pManifest ) -{ - CChunkHandlerMap Handlers; - Handlers.AddHandler( "VMF", ( ChunkHandler_t )LoadManifestVMFCallback, pManifest ); - pFile->PushHandlers(&Handlers); - - ChunkFileResult_t eResult = ChunkFile_Ok; - - eResult = pFile->ReadChunk(); - - pFile->PopHandlers(); - - return( eResult ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : -//----------------------------------------------------------------------------- -ChunkFileResult_t CManifest::LoadCordonBoxCallback( CChunkFile *pFile, Cordon_t *pCordon ) -{ - // Add a box to this cordon. - pCordon->m_Boxes.AddToTail(); - BoundBox &box = pCordon->m_Boxes.Tail(); - - // Fill it in with the data from the VMF. - return pFile->ReadChunk( (KeyHandler_t)LoadCordonBoxKeyCallback, (void *)&box ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : -//----------------------------------------------------------------------------- -ChunkFileResult_t CManifest::LoadCordonBoxKeyCallback( const char *szKey, const char *szValue, BoundBox *pBox ) -{ - if (!stricmp(szKey, "mins")) - { - CChunkFile::ReadKeyValuePoint(szValue, pBox->bmins); - } - else if (!stricmp(szKey, "maxs")) - { - CChunkFile::ReadKeyValuePoint(szValue, pBox->bmaxs); - } - - return ChunkFile_Ok; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : -//----------------------------------------------------------------------------- -ChunkFileResult_t CManifest::LoadCordonKeyCallback( const char *szKey, const char *szValue, Cordon_t *pCordon ) -{ - if (!stricmp(szKey, "name")) - { - pCordon->m_szName.Set( szValue ); - } - // Whether this particular cordon volume is active. - else if (!stricmp(szKey, "active")) - { - CChunkFile::ReadKeyValueBool(szValue, pCordon->m_bActive); - } - - return ChunkFile_Ok; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : -//----------------------------------------------------------------------------- -ChunkFileResult_t CManifest::LoadCordonCallback( CChunkFile *pFile, CManifest *pManifest ) -{ - // Add a new cordon which will be filled in by the key callback - pManifest->m_Cordons.AddToTail(); - Cordon_t &cordon = pManifest->m_Cordons.Tail(); - - CChunkHandlerMap Handlers; - Handlers.AddHandler( "box", (ChunkHandler_t)CManifest::LoadCordonBoxCallback, (void *)&cordon ); - - pFile->PushHandlers(&Handlers); - ChunkFileResult_t eResult = pFile->ReadChunk( (KeyHandler_t)LoadCordonKeyCallback, (void *)&cordon ); - pFile->PopHandlers(); - - return(eResult); -} - - -//----------------------------------------------------------------------------------------------------------- -// Parses keys that are applicable to all cordons in the map. -//----------------------------------------------------------------------------- -ChunkFileResult_t CManifest::LoadCordonsKeyCallback( const char *szKey, const char *szValue, CManifest *pManifest ) -{ - // Whether the cordoning system is enabled or disabled. - if ( !stricmp( szKey, "active" ) ) - { - CChunkFile::ReadKeyValueBool( szValue, pManifest->m_bIsCordoning ); - } - - return ChunkFile_Ok; -} - - -//----------------------------------------------------------------------------- -// Parses the VMF chunk that pertains to all the cordons in the map: -// -// cordons -// { -// "active" "true" -// cordon -// { -// "active" "true" -// "box" -// { -// "mins" "-1024, -1024, -1024" -// "maxs" "1024, 1024, 1024" -// } -// ...may be more boxes... -// } -// ...may be more cordons... -// } -// -//----------------------------------------------------------------------------- -ChunkFileResult_t CManifest::LoadCordonsCallback( CChunkFile *pFile, CManifest *pManifest ) -{ - CChunkHandlerMap Handlers; - Handlers.AddHandler( "cordon", (ChunkHandler_t)CManifest::LoadCordonCallback, pManifest ); - - pFile->PushHandlers(&Handlers); - ChunkFileResult_t eResult = pFile->ReadChunk( (KeyHandler_t)LoadCordonsKeyCallback, pManifest ); - pFile->PopHandlers(); - - return(eResult); -} - -extern ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); - -ChunkFileResult_t CManifest::LoadManifestCordoningPrefsCallback( CChunkFile *pFile, CManifest *pDoc ) -{ - pDoc->m_CordoningMapEnt = &g_MainMap->entities[g_MainMap->num_entities]; - g_MainMap->num_entities++; - memset( pDoc->m_CordoningMapEnt, 0, sizeof( *pDoc->m_CordoningMapEnt ) ); - pDoc->m_CordoningMapEnt->firstbrush = g_MainMap->nummapbrushes; - pDoc->m_CordoningMapEnt->numbrushes = 0; - - LoadEntity_t LoadEntity; - LoadEntity.pEntity = pDoc->m_CordoningMapEnt; - - // No default flags/contents - LoadEntity.nBaseFlags = 0; - LoadEntity.nBaseContents = 0; - - // - // Set up handlers for the subchunks that we are interested in. - // - CChunkHandlerMap Handlers; - Handlers.AddHandler( "cordons", ( ChunkHandler_t )CManifest::LoadCordonsCallback, pDoc ); - Handlers.AddHandler("solid", (ChunkHandler_t)::LoadSolidCallback, &LoadEntity); - pFile->PushHandlers(&Handlers); - - ChunkFileResult_t eResult = ChunkFile_Ok; - - eResult = pFile->ReadChunk(); - - pFile->PopHandlers(); - - return( eResult ); -} - - -//----------------------------------------------------------------------------- -// Purpose: this function will create a new entity pair -// Input : pKey - the key of the pair -// pValue - the value of the pair -// Output : returns a newly created epair structure -//----------------------------------------------------------------------------- -epair_t *CManifest::CreateEPair( char *pKey, char *pValue ) -{ - epair_t *pEPair = new epair_t; - - pEPair->key = new char[ strlen( pKey ) + 1 ]; - pEPair->value = new char[ strlen( pValue ) + 1 ]; - - strcpy( pEPair->key, pKey ); - strcpy( pEPair->value, pValue ); - - return pEPair; -} - - -//----------------------------------------------------------------------------- -// Purpose: this function will load in all of the submaps belonging to this manifest, -// except for the top level map, which is loaded separately. -// Input : pMapFile - the top level map that was previously loaded -// pszFileName - the absolute file name of the top level map file -// Output : returns true if all submaps were loaded -//----------------------------------------------------------------------------- -bool CManifest::LoadSubMaps( CMapFile *pMapFile, const char *pszFileName ) -{ - entity_t *InstanceEntity; - epair_t *pEPair; - - InstanceEntity = &pMapFile->entities[ pMapFile->num_entities ]; - pMapFile->num_entities++; - memset( InstanceEntity, 0, sizeof( *InstanceEntity ) ); - - InstanceEntity->origin.Init( 0.0f, 0.0f, 0.0f ); - pEPair = CreateEPair( "classname", "worldspawn" ); - pEPair->next = InstanceEntity->epairs; - InstanceEntity->epairs = pEPair; - - for( int i = 0; i < m_Maps.Count(); i++ ) - { - // if ( m_Maps[ i ]->m_bTopLevelMap == false ) - { - char FileName[ MAX_PATH ]; - - sprintf( FileName, "%s%s", m_InstancePath, m_Maps[ i ]->m_RelativeMapFileName ); - - InstanceEntity = &pMapFile->entities[ pMapFile->num_entities ]; - pMapFile->num_entities++; - - memset( InstanceEntity, 0, sizeof( *InstanceEntity ) ); - InstanceEntity->origin.Init( 0.0f, 0.0f, 0.0f ); - - pEPair = CreateEPair( "angles", "0 0 0" ); - pEPair->next = InstanceEntity->epairs; - InstanceEntity->epairs = pEPair; - - char temp[ 128 ]; - sprintf( temp, "%d", GameData::NAME_FIXUP_NONE ); - - pEPair = CreateEPair( "fixup_style", temp ); - pEPair->next = InstanceEntity->epairs; - InstanceEntity->epairs = pEPair; - - pEPair = CreateEPair( "classname", "func_instance" ); - pEPair->next = InstanceEntity->epairs; - InstanceEntity->epairs = pEPair; - - pEPair = CreateEPair( "file", m_Maps[ i ]->m_RelativeMapFileName ); - pEPair->next = InstanceEntity->epairs; - InstanceEntity->epairs = pEPair; - - if ( m_Maps[ i ]->m_bTopLevelMap == true ) - { - pEPair = CreateEPair( "toplevel", "1" ); - pEPair->next = InstanceEntity->epairs; - InstanceEntity->epairs = pEPair; - } - } - } - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : -//----------------------------------------------------------------------------- -bool CManifest::LoadVMFManifestUserPrefs( const char *pszFileName ) -{ - char UserName[ MAX_PATH ], FileName[ MAX_PATH ], UserPrefsFileName[ MAX_PATH ]; - DWORD UserNameSize; - - UserNameSize = sizeof( UserName ); - if ( GetUserName( UserName, &UserNameSize ) == 0 ) - { - strcpy( UserPrefsFileName, "default" ); - } - - sprintf( UserPrefsFileName, "\\%s.vmm_prefs", UserName ); - V_StripExtension( pszFileName, FileName, sizeof( FileName ) ); - strcat( FileName, UserPrefsFileName ); - - FILE *fp = fopen( FileName, "rb" ); - if ( !fp ) - { - return false; - } - - CChunkFile File; - ChunkFileResult_t eResult = File.Open( FileName, ChunkFile_Read ); - - if ( eResult == ChunkFile_Ok ) - { - // - // Set up handlers for the subchunks that we are interested in. - // - CChunkHandlerMap Handlers; - Handlers.AddHandler( "cordoning", ( ChunkHandler_t )CManifest::LoadManifestCordoningPrefsCallback, this ); - - // Handlers.SetErrorHandler( ( ChunkErrorHandler_t )CMapDoc::HandleLoadError, this); - - File.PushHandlers(&Handlers); - - while( eResult == ChunkFile_Ok ) - { - eResult = File.ReadChunk(); - } - - if ( eResult == ChunkFile_EOF ) - { - eResult = ChunkFile_Ok; - } - - File.PopHandlers(); - } - - if ( eResult == ChunkFile_Ok ) - { - } - else - { - // no pref message for now - // GetMainWnd()->MessageBox( File.GetErrorText( eResult ), "Error loading manifest!", MB_OK | MB_ICONEXCLAMATION ); - } - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: Loads a .VMM file. -// Input : pszFileName - Full path of the map file to load. -//----------------------------------------------------------------------------- -bool CManifest::LoadVMFManifest( const char *pszFileName ) -{ - V_StripExtension( pszFileName, m_InstancePath, sizeof( m_InstancePath ) ); - strcat( m_InstancePath, "\\" ); - - CChunkFile File; - ChunkFileResult_t eResult = File.Open( pszFileName, ChunkFile_Read ); - if ( eResult != ChunkFile_Ok ) - { - g_MapError.ReportError( File.GetErrorText( eResult ) ); - return false; - } - - CChunkHandlerMap Handlers; - Handlers.AddHandler( "Maps", ( ChunkHandler_t )LoadManifestMapsCallback, this ); - - File.PushHandlers(&Handlers); - - while (eResult == ChunkFile_Ok) - { - eResult = File.ReadChunk(); - } - - if (eResult == ChunkFile_EOF) - { - eResult = ChunkFile_Ok; - } - - File.PopHandlers(); - - if ( eResult == ChunkFile_Ok ) - { - int index = g_Maps.AddToTail( new CMapFile() ); - g_LoadingMap = g_Maps[ index ]; - if ( g_MainMap == NULL ) - { - g_MainMap = g_LoadingMap; - } - - LoadSubMaps( g_LoadingMap, pszFileName ); - - LoadVMFManifestUserPrefs( pszFileName ); - } - - return ( eResult == ChunkFile_Ok ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : -// Output : -//----------------------------------------------------------------------------- -void CManifest::CordonWorld( ) -{ - if ( m_bIsCordoning == false ) - { - return; - } - - for ( int i = 0; i < g_MainMap->num_entities; i++ ) - { - if ( i == 0 ) - { // for world spawn, we look at brushes - for( int nBrushNum = 0; nBrushNum < g_MainMap->entities[ i ].numbrushes; nBrushNum++ ) - { - int nIndex = g_MainMap->entities[ i ].firstbrush + nBrushNum; - - bool bRemove = true; - - for( int nCordon = 0; nCordon < m_Cordons.Count(); nCordon++ ) - { - if ( m_Cordons[ nCordon ].m_bActive == false ) - { - continue; - } - - for( int nBox = 0; nBox < m_Cordons[ nCordon ].m_Boxes.Count(); nBox++ ) - { - if ( m_Cordons[ nCordon ].m_Boxes[ nBox ].IsIntersectingBox( g_MainMap->mapbrushes[ nIndex ].mins, g_MainMap->mapbrushes[ nIndex ].maxs ) == true ) - { - bRemove = false; - break; - } - } - - if ( bRemove == false ) - { - break; - } - } - - if ( bRemove ) - { - int nSize = ( g_MainMap->entities[ i ].numbrushes - nBrushNum - 1 ) * sizeof( g_MainMap->mapbrushes[ 0 ] ); - memmove( &g_MainMap->mapbrushes[ nIndex ], &g_MainMap->mapbrushes[ nIndex + 1 ], nSize ); - g_MainMap->entities[ i ].numbrushes--; - nBrushNum--; - } - } - } - else if ( &g_MainMap->entities[ i ] != m_CordoningMapEnt ) - { // for all other entities, even if they include brushes, we look at origin - if ( g_MainMap->entities[ i ].numbrushes == 0 && g_MainMap->entities[ i ].epairs == NULL ) - { - continue; - } - - bool bRemove = true; - - for( int nCordon = 0; nCordon < m_Cordons.Count(); nCordon++ ) - { - if ( m_Cordons[ nCordon ].m_bActive == false ) - { - continue; - } - - for( int nBox = 0; nBox < m_Cordons[ nCordon ].m_Boxes.Count(); nBox++ ) - { - if ( m_Cordons[ nCordon ].m_Boxes[ nBox ].ContainsPoint( g_MainMap->entities[ i ].origin ) == true ) - { - bRemove = false; - break; - } - } - - if ( bRemove == false ) - { - break; - } - } - - if ( bRemove ) - { - g_MainMap->entities[ i ].numbrushes = 0; - g_MainMap->entities[ i ].epairs = NULL; - } - } - } - - if ( m_CordoningMapEnt ) - { - g_MainMap->MoveBrushesToWorldGeneral( m_CordoningMapEnt ); - m_CordoningMapEnt->numbrushes = 0; - m_CordoningMapEnt->epairs = NULL; - } -} +//========= Copyright Valve Corporation, All rights reserved. ============// +#include "vbsp.h" +#include "map_shared.h" +#include "fgdlib/fgdlib.h" +#include "manifest.h" +#include "windows.h" + +//----------------------------------------------------------------------------- +// Purpose: default constructor +//----------------------------------------------------------------------------- +CManifestMap::CManifestMap( void ) +{ + m_RelativeMapFileName[ 0 ] = 0; + m_bTopLevelMap = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: default constructor +//----------------------------------------------------------------------------- +CManifest::CManifest( void ) +{ + m_InstancePath[ 0 ] = 0; + m_bIsCordoning = false; + m_CordoningMapEnt = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will parse through the known keys for the manifest map entry +// Input : szKey - the key name +// szValue - the value +// pManifestMap - the manifest map this belongs to +// Output : ChunkFileResult_t - result of the parsing +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadManifestMapKeyCallback( const char *szKey, const char *szValue, CManifestMap *pManifestMap ) +{ + if ( !stricmp( szKey, "Name" ) ) + { + // pManifestMap->m_FriendlyName = szValue; + } + else if ( !stricmp( szKey, "File" ) ) + { + strcpy( pManifestMap->m_RelativeMapFileName, szValue ); + } + else if ( !stricmp( szKey, "IsPrimary" ) ) + { + // pManifestMap->m_bPrimaryMap = ( atoi( szValue ) == 1 ); + } + else if ( !stricmp( szKey, "IsProtected" ) ) + { + // pManifestMap->m_bCanBeModified = ( atoi( szValue ) != 1 ); + } + else if ( !stricmp( szKey, "TopLevel" ) ) + { + pManifestMap->m_bTopLevelMap = ( atoi( szValue ) == 1 ); + } + + return ChunkFile_Ok; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function is responsible for setting up the manifest map about to be read in +// Input : pFile - the chunk file being read +// pDoc - the owning manifest document +// Output : ChunkFileResult_t - result of the parsing +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadManifestVMFCallback( CChunkFile *pFile, CManifest *pManifest ) +{ + CManifestMap *pManifestMap = new CManifestMap(); + + pManifest->m_Maps.AddToTail( pManifestMap ); + + ChunkFileResult_t eResult = pFile->ReadChunk( ( KeyHandler_t )LoadManifestMapKeyCallback, pManifestMap ); + + return( eResult ); +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will load the VMF chunk +// Input : pFile - the chunk file being read +// pDoc - the owning manifest document +// Output : ChunkFileResult_t - result of the parsing +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadManifestMapsCallback( CChunkFile *pFile, CManifest *pManifest ) +{ + CChunkHandlerMap Handlers; + Handlers.AddHandler( "VMF", ( ChunkHandler_t )LoadManifestVMFCallback, pManifest ); + pFile->PushHandlers(&Handlers); + + ChunkFileResult_t eResult = ChunkFile_Ok; + + eResult = pFile->ReadChunk(); + + pFile->PopHandlers(); + + return( eResult ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonBoxCallback( CChunkFile *pFile, Cordon_t *pCordon ) +{ + // Add a box to this cordon. + pCordon->m_Boxes.AddToTail(); + BoundBox &box = pCordon->m_Boxes.Tail(); + + // Fill it in with the data from the VMF. + return pFile->ReadChunk( (KeyHandler_t)LoadCordonBoxKeyCallback, (void *)&box ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonBoxKeyCallback( const char *szKey, const char *szValue, BoundBox *pBox ) +{ + if (!stricmp(szKey, "mins")) + { + CChunkFile::ReadKeyValuePoint(szValue, pBox->bmins); + } + else if (!stricmp(szKey, "maxs")) + { + CChunkFile::ReadKeyValuePoint(szValue, pBox->bmaxs); + } + + return ChunkFile_Ok; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonKeyCallback( const char *szKey, const char *szValue, Cordon_t *pCordon ) +{ + if (!stricmp(szKey, "name")) + { + pCordon->m_szName.Set( szValue ); + } + // Whether this particular cordon volume is active. + else if (!stricmp(szKey, "active")) + { + CChunkFile::ReadKeyValueBool(szValue, pCordon->m_bActive); + } + + return ChunkFile_Ok; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonCallback( CChunkFile *pFile, CManifest *pManifest ) +{ + // Add a new cordon which will be filled in by the key callback + pManifest->m_Cordons.AddToTail(); + Cordon_t &cordon = pManifest->m_Cordons.Tail(); + + CChunkHandlerMap Handlers; + Handlers.AddHandler( "box", (ChunkHandler_t)CManifest::LoadCordonBoxCallback, (void *)&cordon ); + + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk( (KeyHandler_t)LoadCordonKeyCallback, (void *)&cordon ); + pFile->PopHandlers(); + + return(eResult); +} + + +//----------------------------------------------------------------------------------------------------------- +// Parses keys that are applicable to all cordons in the map. +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonsKeyCallback( const char *szKey, const char *szValue, CManifest *pManifest ) +{ + // Whether the cordoning system is enabled or disabled. + if ( !stricmp( szKey, "active" ) ) + { + CChunkFile::ReadKeyValueBool( szValue, pManifest->m_bIsCordoning ); + } + + return ChunkFile_Ok; +} + + +//----------------------------------------------------------------------------- +// Parses the VMF chunk that pertains to all the cordons in the map: +// +// cordons +// { +// "active" "true" +// cordon +// { +// "active" "true" +// "box" +// { +// "mins" "-1024, -1024, -1024" +// "maxs" "1024, 1024, 1024" +// } +// ...may be more boxes... +// } +// ...may be more cordons... +// } +// +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonsCallback( CChunkFile *pFile, CManifest *pManifest ) +{ + CChunkHandlerMap Handlers; + Handlers.AddHandler( "cordon", (ChunkHandler_t)CManifest::LoadCordonCallback, pManifest ); + + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk( (KeyHandler_t)LoadCordonsKeyCallback, pManifest ); + pFile->PopHandlers(); + + return(eResult); +} + +extern ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); + +ChunkFileResult_t CManifest::LoadManifestCordoningPrefsCallback( CChunkFile *pFile, CManifest *pDoc ) +{ + pDoc->m_CordoningMapEnt = &g_MainMap->entities[g_MainMap->num_entities]; + g_MainMap->num_entities++; + memset( pDoc->m_CordoningMapEnt, 0, sizeof( *pDoc->m_CordoningMapEnt ) ); + pDoc->m_CordoningMapEnt->firstbrush = g_MainMap->nummapbrushes; + pDoc->m_CordoningMapEnt->numbrushes = 0; + + LoadEntity_t LoadEntity; + LoadEntity.pEntity = pDoc->m_CordoningMapEnt; + + // No default flags/contents + LoadEntity.nBaseFlags = 0; + LoadEntity.nBaseContents = 0; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler( "cordons", ( ChunkHandler_t )CManifest::LoadCordonsCallback, pDoc ); + Handlers.AddHandler("solid", (ChunkHandler_t)::LoadSolidCallback, &LoadEntity); + pFile->PushHandlers(&Handlers); + + ChunkFileResult_t eResult = ChunkFile_Ok; + + eResult = pFile->ReadChunk(); + + pFile->PopHandlers(); + + return( eResult ); +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will create a new entity pair +// Input : pKey - the key of the pair +// pValue - the value of the pair +// Output : returns a newly created epair structure +//----------------------------------------------------------------------------- +epair_t *CManifest::CreateEPair( char *pKey, char *pValue ) +{ + epair_t *pEPair = new epair_t; + + pEPair->key = new char[ strlen( pKey ) + 1 ]; + pEPair->value = new char[ strlen( pValue ) + 1 ]; + + strcpy( pEPair->key, pKey ); + strcpy( pEPair->value, pValue ); + + return pEPair; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will load in all of the submaps belonging to this manifest, +// except for the top level map, which is loaded separately. +// Input : pMapFile - the top level map that was previously loaded +// pszFileName - the absolute file name of the top level map file +// Output : returns true if all submaps were loaded +//----------------------------------------------------------------------------- +bool CManifest::LoadSubMaps( CMapFile *pMapFile, const char *pszFileName ) +{ + entity_t *InstanceEntity; + epair_t *pEPair; + + InstanceEntity = &pMapFile->entities[ pMapFile->num_entities ]; + pMapFile->num_entities++; + memset( InstanceEntity, 0, sizeof( *InstanceEntity ) ); + + InstanceEntity->origin.Init( 0.0f, 0.0f, 0.0f ); + pEPair = CreateEPair( "classname", "worldspawn" ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + + for( int i = 0; i < m_Maps.Count(); i++ ) + { + // if ( m_Maps[ i ]->m_bTopLevelMap == false ) + { + char FileName[ MAX_PATH ]; + + sprintf( FileName, "%s%s", m_InstancePath, m_Maps[ i ]->m_RelativeMapFileName ); + + InstanceEntity = &pMapFile->entities[ pMapFile->num_entities ]; + pMapFile->num_entities++; + + memset( InstanceEntity, 0, sizeof( *InstanceEntity ) ); + InstanceEntity->origin.Init( 0.0f, 0.0f, 0.0f ); + + pEPair = CreateEPair( "angles", "0 0 0" ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + + char temp[ 128 ]; + sprintf( temp, "%d", GameData::NAME_FIXUP_NONE ); + + pEPair = CreateEPair( "fixup_style", temp ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + + pEPair = CreateEPair( "classname", "func_instance" ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + + pEPair = CreateEPair( "file", m_Maps[ i ]->m_RelativeMapFileName ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + + if ( m_Maps[ i ]->m_bTopLevelMap == true ) + { + pEPair = CreateEPair( "toplevel", "1" ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + } + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CManifest::LoadVMFManifestUserPrefs( const char *pszFileName ) +{ + char UserName[ MAX_PATH ], FileName[ MAX_PATH ], UserPrefsFileName[ MAX_PATH ]; + DWORD UserNameSize; + + UserNameSize = sizeof( UserName ); + if ( GetUserName( UserName, &UserNameSize ) == 0 ) + { + strcpy( UserPrefsFileName, "default" ); + } + + sprintf( UserPrefsFileName, "\\%s.vmm_prefs", UserName ); + V_StripExtension( pszFileName, FileName, sizeof( FileName ) ); + strcat( FileName, UserPrefsFileName ); + + FILE *fp = fopen( FileName, "rb" ); + if ( !fp ) + { + return false; + } + + CChunkFile File; + ChunkFileResult_t eResult = File.Open( FileName, ChunkFile_Read ); + + if ( eResult == ChunkFile_Ok ) + { + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler( "cordoning", ( ChunkHandler_t )CManifest::LoadManifestCordoningPrefsCallback, this ); + + // Handlers.SetErrorHandler( ( ChunkErrorHandler_t )CMapDoc::HandleLoadError, this); + + File.PushHandlers(&Handlers); + + while( eResult == ChunkFile_Ok ) + { + eResult = File.ReadChunk(); + } + + if ( eResult == ChunkFile_EOF ) + { + eResult = ChunkFile_Ok; + } + + File.PopHandlers(); + } + + if ( eResult == ChunkFile_Ok ) + { + } + else + { + // no pref message for now + // GetMainWnd()->MessageBox( File.GetErrorText( eResult ), "Error loading manifest!", MB_OK | MB_ICONEXCLAMATION ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Loads a .VMM file. +// Input : pszFileName - Full path of the map file to load. +//----------------------------------------------------------------------------- +bool CManifest::LoadVMFManifest( const char *pszFileName ) +{ + V_StripExtension( pszFileName, m_InstancePath, sizeof( m_InstancePath ) ); + strcat( m_InstancePath, "\\" ); + + CChunkFile File; + ChunkFileResult_t eResult = File.Open( pszFileName, ChunkFile_Read ); + if ( eResult != ChunkFile_Ok ) + { + g_MapError.ReportError( File.GetErrorText( eResult ) ); + return false; + } + + CChunkHandlerMap Handlers; + Handlers.AddHandler( "Maps", ( ChunkHandler_t )LoadManifestMapsCallback, this ); + + File.PushHandlers(&Handlers); + + while (eResult == ChunkFile_Ok) + { + eResult = File.ReadChunk(); + } + + if (eResult == ChunkFile_EOF) + { + eResult = ChunkFile_Ok; + } + + File.PopHandlers(); + + if ( eResult == ChunkFile_Ok ) + { + int index = g_Maps.AddToTail( new CMapFile() ); + g_LoadingMap = g_Maps[ index ]; + if ( g_MainMap == NULL ) + { + g_MainMap = g_LoadingMap; + } + + LoadSubMaps( g_LoadingMap, pszFileName ); + + LoadVMFManifestUserPrefs( pszFileName ); + } + + return ( eResult == ChunkFile_Ok ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CManifest::CordonWorld( ) +{ + if ( m_bIsCordoning == false ) + { + return; + } + + for ( int i = 0; i < g_MainMap->num_entities; i++ ) + { + if ( i == 0 ) + { // for world spawn, we look at brushes + for( int nBrushNum = 0; nBrushNum < g_MainMap->entities[ i ].numbrushes; nBrushNum++ ) + { + int nIndex = g_MainMap->entities[ i ].firstbrush + nBrushNum; + + bool bRemove = true; + + for( int nCordon = 0; nCordon < m_Cordons.Count(); nCordon++ ) + { + if ( m_Cordons[ nCordon ].m_bActive == false ) + { + continue; + } + + for( int nBox = 0; nBox < m_Cordons[ nCordon ].m_Boxes.Count(); nBox++ ) + { + if ( m_Cordons[ nCordon ].m_Boxes[ nBox ].IsIntersectingBox( g_MainMap->mapbrushes[ nIndex ].mins, g_MainMap->mapbrushes[ nIndex ].maxs ) == true ) + { + bRemove = false; + break; + } + } + + if ( bRemove == false ) + { + break; + } + } + + if ( bRemove ) + { + int nSize = ( g_MainMap->entities[ i ].numbrushes - nBrushNum - 1 ) * sizeof( g_MainMap->mapbrushes[ 0 ] ); + memmove( &g_MainMap->mapbrushes[ nIndex ], &g_MainMap->mapbrushes[ nIndex + 1 ], nSize ); + g_MainMap->entities[ i ].numbrushes--; + nBrushNum--; + } + } + } + else if ( &g_MainMap->entities[ i ] != m_CordoningMapEnt ) + { // for all other entities, even if they include brushes, we look at origin + if ( g_MainMap->entities[ i ].numbrushes == 0 && g_MainMap->entities[ i ].epairs == NULL ) + { + continue; + } + + bool bRemove = true; + + for( int nCordon = 0; nCordon < m_Cordons.Count(); nCordon++ ) + { + if ( m_Cordons[ nCordon ].m_bActive == false ) + { + continue; + } + + for( int nBox = 0; nBox < m_Cordons[ nCordon ].m_Boxes.Count(); nBox++ ) + { + if ( m_Cordons[ nCordon ].m_Boxes[ nBox ].ContainsPoint( g_MainMap->entities[ i ].origin ) == true ) + { + bRemove = false; + break; + } + } + + if ( bRemove == false ) + { + break; + } + } + + if ( bRemove ) + { + g_MainMap->entities[ i ].numbrushes = 0; + g_MainMap->entities[ i ].epairs = NULL; + } + } + } + + if ( m_CordoningMapEnt ) + { + g_MainMap->MoveBrushesToWorldGeneral( m_CordoningMapEnt ); + m_CordoningMapEnt->numbrushes = 0; + m_CordoningMapEnt->epairs = NULL; + } +} diff --git a/mp/src/utils/vbsp/manifest.h b/mp/src/utils/vbsp/manifest.h index f3b915a1..e7b801e1 100644 --- a/mp/src/utils/vbsp/manifest.h +++ b/mp/src/utils/vbsp/manifest.h @@ -1,73 +1,73 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=====================================================================================// - -#ifndef __MANIFEST_H -#define __MANIFEST_H - -#ifdef _WIN32 -#pragma once -#endif - -#include "boundbox.h" - -// -// Each cordon is a named collection of bounding boxes. -// -struct Cordon_t -{ - inline Cordon_t() - { - m_bActive = false; - } - - CUtlString m_szName; - bool m_bActive; // True means cull using this cordon when cordoning is enabled. - CUtlVector m_Boxes; -}; - -class CManifestMap -{ -public: - CManifestMap( void ); - char m_RelativeMapFileName[ MAX_PATH ]; - bool m_bTopLevelMap; -}; - -class CManifest -{ -public: - CManifest( void ); - - static ChunkFileResult_t LoadManifestMapKeyCallback( const char *szKey, const char *szValue, CManifestMap *pManifestMap ); - static ChunkFileResult_t LoadManifestVMFCallback( CChunkFile *pFile, CManifest *pManifest ); - static ChunkFileResult_t LoadManifestMapsCallback( CChunkFile *pFile, CManifest *pManifest ); - static ChunkFileResult_t LoadCordonBoxCallback( CChunkFile *pFile, Cordon_t *pCordon ); - static ChunkFileResult_t LoadCordonBoxKeyCallback( const char *szKey, const char *szValue, BoundBox *pBox ); - static ChunkFileResult_t LoadCordonKeyCallback( const char *szKey, const char *szValue, Cordon_t *pCordon ); - static ChunkFileResult_t LoadCordonCallback( CChunkFile *pFile, CManifest *pManifest ); - static ChunkFileResult_t LoadCordonsKeyCallback( const char *pszKey, const char *pszValue, CManifest *pManifest ); - static ChunkFileResult_t LoadCordonsCallback( CChunkFile *pFile, CManifest *pManifest ); - static ChunkFileResult_t LoadManifestCordoningPrefsCallback( CChunkFile *pFile, CManifest *pManifest ); - - bool LoadSubMaps( CMapFile *pMapFile, const char *pszFileName ); - epair_t *CreateEPair( char *pKey, char *pValue ); - bool LoadVMFManifest( const char *pszFileName ); - const char *GetInstancePath( ) { return m_InstancePath; } - - void CordonWorld( ); - -private: - bool LoadVMFManifestUserPrefs( const char *pszFileName ); - - - CUtlVector< CManifestMap * > m_Maps; - char m_InstancePath[ MAX_PATH ]; - bool m_bIsCordoning; - CUtlVector< Cordon_t > m_Cordons; - entity_t *m_CordoningMapEnt; -}; - -#endif // #ifndef __MANIFEST_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=====================================================================================// + +#ifndef __MANIFEST_H +#define __MANIFEST_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "boundbox.h" + +// +// Each cordon is a named collection of bounding boxes. +// +struct Cordon_t +{ + inline Cordon_t() + { + m_bActive = false; + } + + CUtlString m_szName; + bool m_bActive; // True means cull using this cordon when cordoning is enabled. + CUtlVector m_Boxes; +}; + +class CManifestMap +{ +public: + CManifestMap( void ); + char m_RelativeMapFileName[ MAX_PATH ]; + bool m_bTopLevelMap; +}; + +class CManifest +{ +public: + CManifest( void ); + + static ChunkFileResult_t LoadManifestMapKeyCallback( const char *szKey, const char *szValue, CManifestMap *pManifestMap ); + static ChunkFileResult_t LoadManifestVMFCallback( CChunkFile *pFile, CManifest *pManifest ); + static ChunkFileResult_t LoadManifestMapsCallback( CChunkFile *pFile, CManifest *pManifest ); + static ChunkFileResult_t LoadCordonBoxCallback( CChunkFile *pFile, Cordon_t *pCordon ); + static ChunkFileResult_t LoadCordonBoxKeyCallback( const char *szKey, const char *szValue, BoundBox *pBox ); + static ChunkFileResult_t LoadCordonKeyCallback( const char *szKey, const char *szValue, Cordon_t *pCordon ); + static ChunkFileResult_t LoadCordonCallback( CChunkFile *pFile, CManifest *pManifest ); + static ChunkFileResult_t LoadCordonsKeyCallback( const char *pszKey, const char *pszValue, CManifest *pManifest ); + static ChunkFileResult_t LoadCordonsCallback( CChunkFile *pFile, CManifest *pManifest ); + static ChunkFileResult_t LoadManifestCordoningPrefsCallback( CChunkFile *pFile, CManifest *pManifest ); + + bool LoadSubMaps( CMapFile *pMapFile, const char *pszFileName ); + epair_t *CreateEPair( char *pKey, char *pValue ); + bool LoadVMFManifest( const char *pszFileName ); + const char *GetInstancePath( ) { return m_InstancePath; } + + void CordonWorld( ); + +private: + bool LoadVMFManifestUserPrefs( const char *pszFileName ); + + + CUtlVector< CManifestMap * > m_Maps; + char m_InstancePath[ MAX_PATH ]; + bool m_bIsCordoning; + CUtlVector< Cordon_t > m_Cordons; + entity_t *m_CordoningMapEnt; +}; + +#endif // #ifndef __MANIFEST_H diff --git a/mp/src/utils/vbsp/map.cpp b/mp/src/utils/vbsp/map.cpp index 34219bd4..a5456c32 100644 --- a/mp/src/utils/vbsp/map.cpp +++ b/mp/src/utils/vbsp/map.cpp @@ -1,3304 +1,3304 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "vbsp.h" -#include "map_shared.h" -#include "disp_vbsp.h" -#include "tier1/strtools.h" -#include "builddisp.h" -#include "tier0/icommandline.h" -#include "KeyValues.h" -#include "materialsub.h" -#include "fgdlib/fgdlib.h" -#include "manifest.h" - -#ifdef VSVMFIO -#include "VmfImport.h" -#endif // VSVMFIO - - -// undefine to make plane finding use linear sort -#define USE_HASHING - -#define RENDER_NORMAL_EPSILON 0.00001 -#define RENDER_DIST_EPSILON 0.01f - -#define BRUSH_CLIP_EPSILON 0.01f // this should probably be the same - // as clip epsilon, but it is 0.1f and I - // currently don't know how that number was - // come to (cab) - this is 0.01 of an inch - // for clipping brush solids -struct LoadSide_t -{ - mapbrush_t *pBrush; - side_t *pSide; - int nSideIndex; - int nBaseFlags; - int nBaseContents; - Vector planepts[3]; - brush_texture_t td; -}; - - -extern qboolean onlyents; - - -CUtlVector< CMapFile * > g_Maps; -CMapFile *g_MainMap = NULL; -CMapFile *g_LoadingMap = NULL; - -char CMapFile::m_InstancePath[ MAX_PATH ] = ""; -int CMapFile::m_InstanceCount = 0; -int CMapFile::c_areaportals = 0; - -void CMapFile::Init( void ) -{ - entity_num = 0; - num_entities = 0; - - nummapplanes = 0; - memset( mapplanes, 0, sizeof( mapplanes ) ); - - nummapbrushes = 0; - memset( mapbrushes, 0, sizeof( mapbrushes ) ); - - nummapbrushsides = 0; - memset( brushsides, 0, sizeof( brushsides ) ); - - memset( side_brushtextures, 0, sizeof( side_brushtextures ) ); - - memset( planehash, 0, sizeof( planehash ) ); - - m_ConnectionPairs = NULL; - - m_StartMapOverlays = g_aMapOverlays.Count(); - m_StartMapWaterOverlays = g_aMapWaterOverlays.Count(); - - c_boxbevels = 0; - c_edgebevels = 0; - c_clipbrushes = 0; - g_ClipTexinfo = -1; -} - - -// All the brush sides referenced by info_no_dynamic_shadow entities. -CUtlVector g_NoDynamicShadowSides; - - -void TestExpandBrushes (void); - -ChunkFileResult_t LoadDispDistancesCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); -ChunkFileResult_t LoadDispDistancesKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); -ChunkFileResult_t LoadDispInfoCallback(CChunkFile *pFile, mapdispinfo_t **ppMapDispInfo ); -ChunkFileResult_t LoadDispInfoKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); -ChunkFileResult_t LoadDispNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); -ChunkFileResult_t LoadDispNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); -ChunkFileResult_t LoadDispOffsetsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); -ChunkFileResult_t LoadDispOffsetsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); -ChunkFileResult_t LoadDispAlphasCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); -ChunkFileResult_t LoadDispAlphasKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); -ChunkFileResult_t LoadDispTriangleTagsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); -ChunkFileResult_t LoadDispTriangleTagsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); - -#ifdef VSVMFIO -ChunkFileResult_t LoadDispOffsetNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); -ChunkFileResult_t LoadDispOffsetNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); -#endif // VSVMFIO - -ChunkFileResult_t LoadEntityCallback(CChunkFile *pFile, int nParam); -ChunkFileResult_t LoadEntityKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity); - -ChunkFileResult_t LoadConnectionsCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); -ChunkFileResult_t LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity); - -ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); -ChunkFileResult_t LoadSolidKeyCallback(const char *szKey, const char *szValue, mapbrush_t *pLoadBrush); - -ChunkFileResult_t LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo); -ChunkFileResult_t LoadSideKeyCallback(const char *szKey, const char *szValue, LoadSide_t *pSideInfo); - - - -/* -============================================================================= - -PLANE FINDING - -============================================================================= -*/ - - -/* -================= -PlaneTypeForNormal -================= -*/ -int PlaneTypeForNormal (Vector& normal) -{ - vec_t ax, ay, az; - -// NOTE: should these have an epsilon around 1.0? - if (normal[0] == 1.0 || normal[0] == -1.0) - return PLANE_X; - if (normal[1] == 1.0 || normal[1] == -1.0) - return PLANE_Y; - if (normal[2] == 1.0 || normal[2] == -1.0) - return PLANE_Z; - - ax = fabs(normal[0]); - ay = fabs(normal[1]); - az = fabs(normal[2]); - - if (ax >= ay && ax >= az) - return PLANE_ANYX; - if (ay >= ax && ay >= az) - return PLANE_ANYY; - return PLANE_ANYZ; -} - -/* -================ -PlaneEqual -================ -*/ -qboolean PlaneEqual (plane_t *p, Vector& normal, vec_t dist, float normalEpsilon, float distEpsilon) -{ -#if 1 - if ( - fabs(p->normal[0] - normal[0]) < normalEpsilon - && fabs(p->normal[1] - normal[1]) < normalEpsilon - && fabs(p->normal[2] - normal[2]) < normalEpsilon - && fabs(p->dist - dist) < distEpsilon ) - return true; -#else - if (p->normal[0] == normal[0] - && p->normal[1] == normal[1] - && p->normal[2] == normal[2] - && p->dist == dist) - return true; -#endif - return false; -} - -/* -================ -AddPlaneToHash -================ -*/ -void CMapFile::AddPlaneToHash (plane_t *p) -{ - int hash; - - hash = (int)fabs(p->dist) / 8; - hash &= (PLANE_HASHES-1); - - p->hash_chain = planehash[hash]; - planehash[hash] = p; -} - -/* -================ -CreateNewFloatPlane -================ -*/ -int CMapFile::CreateNewFloatPlane (Vector& normal, vec_t dist) -{ - plane_t *p, temp; - - if (VectorLength(normal) < 0.5) - g_MapError.ReportError ("FloatPlane: bad normal"); - // create a new plane - if (nummapplanes+2 > MAX_MAP_PLANES) - g_MapError.ReportError ("MAX_MAP_PLANES"); - - p = &mapplanes[nummapplanes]; - VectorCopy (normal, p->normal); - p->dist = dist; - p->type = (p+1)->type = PlaneTypeForNormal (p->normal); - - VectorSubtract (vec3_origin, normal, (p+1)->normal); - (p+1)->dist = -dist; - - nummapplanes += 2; - - // allways put axial planes facing positive first - if (p->type < 3) - { - if (p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0) - { - // flip order - temp = *p; - *p = *(p+1); - *(p+1) = temp; - - AddPlaneToHash (p); - AddPlaneToHash (p+1); - return nummapplanes - 1; - } - } - - AddPlaneToHash (p); - AddPlaneToHash (p+1); - return nummapplanes - 2; -} - - -/* -============== -SnapVector -============== -*/ -bool SnapVector (Vector& normal) -{ - int i; - - for (i=0 ; i<3 ; i++) - { - if ( fabs(normal[i] - 1) < RENDER_NORMAL_EPSILON ) - { - VectorClear (normal); - normal[i] = 1; - return true; - } - - if ( fabs(normal[i] - -1) < RENDER_NORMAL_EPSILON ) - { - VectorClear (normal); - normal[i] = -1; - return true; - } - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: Snaps normal to axis-aligned if it is within an epsilon of axial. -// Rounds dist to integer if it is within an epsilon of integer. -// Input : normal - Plane normal vector (assumed to be unit length). -// dist - Plane constant. -//----------------------------------------------------------------------------- -void SnapPlane(Vector &normal, vec_t &dist) -{ - SnapVector(normal); - - if (fabs(dist - RoundInt(dist)) < RENDER_DIST_EPSILON) - { - dist = RoundInt(dist); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Snaps normal to axis-aligned if it is within an epsilon of axial. -// Recalculates dist if the normal was snapped. Rounds dist to integer -// if it is within an epsilon of integer. -// Input : normal - Plane normal vector (assumed to be unit length). -// dist - Plane constant. -// p0, p1, p2 - Three points on the plane. -//----------------------------------------------------------------------------- -void SnapPlane(Vector &normal, vec_t &dist, const Vector &p0, const Vector &p1, const Vector &p2) -{ - if (SnapVector(normal)) - { - // - // Calculate a new plane constant using the snapped normal. Use the - // centroid of the three plane points to minimize error. This is like - // rotating the plane around the centroid. - // - Vector p3 = (p0 + p1 + p2) / 3.0f; - dist = normal.Dot(p3); - if ( g_snapAxialPlanes ) - { - dist = RoundInt(dist); - } - } - - if (fabs(dist - RoundInt(dist)) < RENDER_DIST_EPSILON) - { - dist = RoundInt(dist); - } -} - - -/* -============= -FindFloatPlane - -============= -*/ -#ifndef USE_HASHING -int CMapFile::FindFloatPlane (Vector& normal, vec_t dist) -{ - int i; - plane_t *p; - - SnapPlane(normal, dist); - for (i=0, p=mapplanes ; ihash_chain) - { - if (PlaneEqual (p, normal, dist, RENDER_NORMAL_EPSILON, RENDER_DIST_EPSILON)) - return p-mapplanes; - } - } - - return CreateNewFloatPlane (normal, dist); -} -#endif - - -//----------------------------------------------------------------------------- -// Purpose: Builds a plane normal and distance from three points on the plane. -// If the normal is nearly axial, it will be snapped to be axial. Looks -// up the plane in the unique planes. -// Input : p0, p1, p2 - Three points on the plane. -// Output : Returns the index of the plane in the planes list. -//----------------------------------------------------------------------------- -int CMapFile::PlaneFromPoints(const Vector &p0, const Vector &p1, const Vector &p2) -{ - Vector t1, t2, normal; - vec_t dist; - - VectorSubtract (p0, p1, t1); - VectorSubtract (p2, p1, t2); - CrossProduct (t1, t2, normal); - VectorNormalize (normal); - - dist = DotProduct (p0, normal); - - SnapPlane(normal, dist, p0, p1, p2); - - return FindFloatPlane (normal, dist); -} - - -/* -=========== -BrushContents -=========== -*/ -int BrushContents (mapbrush_t *b) -{ - int contents; - int unionContents = 0; - side_t *s; - int i; - - s = &b->original_sides[0]; - contents = s->contents; - unionContents = contents; - for (i=1 ; inumsides ; i++, s++) - { - s = &b->original_sides[i]; - - unionContents |= s->contents; -#if 0 - if (s->contents != contents) - { - Msg("Brush %i: mixed face contents\n", b->id); - break; - } -#endif - } - - // NOTE: we're making slime translucent so that it doesn't block lighting on things floating on its surface - int transparentContents = unionContents & (CONTENTS_WINDOW|CONTENTS_GRATE|CONTENTS_WATER|CONTENTS_SLIME); - if ( transparentContents ) - { - contents |= transparentContents | CONTENTS_TRANSLUCENT; - contents &= ~CONTENTS_SOLID; - } - - return contents; -} - - -//============================================================================ - -bool IsAreaPortal( char const *pClassName ) -{ - // If the class name starts with "func_areaportal", then it's considered an area portal. - char const *pBaseName = "func_areaportal"; - char const *pCur = pBaseName; - while( *pCur && *pClassName ) - { - if( *pCur != *pClassName ) - break; - - ++pCur; - ++pClassName; - } - - return *pCur == 0; -} - - -/* -================= -AddBrushBevels - -Adds any additional planes necessary to allow the brush to be expanded -against axial bounding boxes -================= -*/ -void CMapFile::AddBrushBevels (mapbrush_t *b) -{ - int axis, dir; - int i, j, k, l, order; - side_t sidetemp; - brush_texture_t tdtemp; - side_t *s, *s2; - Vector normal; - float dist; - winding_t *w, *w2; - Vector vec, vec2; - float d; - - // - // add the axial planes - // - order = 0; - for (axis=0 ; axis <3 ; axis++) - { - for (dir=-1 ; dir <= 1 ; dir+=2, order++) - { - // see if the plane is allready present - for (i=0, s=b->original_sides ; inumsides ; i++,s++) - { - if (mapplanes[s->planenum].normal[axis] == dir) - break; - } - - if (i == b->numsides) - { // add a new side - if (nummapbrushsides == MAX_MAP_BRUSHSIDES) - g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); - nummapbrushsides++; - b->numsides++; - VectorClear (normal); - normal[axis] = dir; - if (dir == 1) - dist = b->maxs[axis]; - else - dist = -b->mins[axis]; - s->planenum = FindFloatPlane (normal, dist); - s->texinfo = b->original_sides[0].texinfo; - s->contents = b->original_sides[0].contents; - s->bevel = true; - c_boxbevels++; - } - - // if the plane is not in it canonical order, swap it - if (i != order) - { - sidetemp = b->original_sides[order]; - b->original_sides[order] = b->original_sides[i]; - b->original_sides[i] = sidetemp; - - j = b->original_sides - brushsides; - tdtemp = side_brushtextures[j+order]; - side_brushtextures[j+order] = side_brushtextures[j+i]; - side_brushtextures[j+i] = tdtemp; - } - } - } - - // - // add the edge bevels - // - if (b->numsides == 6) - return; // pure axial - - // test the non-axial plane edges - for (i=6 ; inumsides ; i++) - { - s = b->original_sides + i; - w = s->winding; - if (!w) - continue; - for (j=0 ; jnumpoints ; j++) - { - k = (j+1)%w->numpoints; - VectorSubtract (w->p[j], w->p[k], vec); - if (VectorNormalize (vec) < 0.5) - continue; - SnapVector (vec); - for (k=0 ; k<3 ; k++) - if ( vec[k] == -1 || vec[k] == 1) - break; // axial - if (k != 3) - continue; // only test non-axial edges - - // try the six possible slanted axials from this edge - for (axis=0 ; axis <3 ; axis++) - { - for (dir=-1 ; dir <= 1 ; dir+=2) - { - // construct a plane - VectorClear (vec2); - vec2[axis] = dir; - CrossProduct (vec, vec2, normal); - if (VectorNormalize (normal) < 0.5) - continue; - dist = DotProduct (w->p[j], normal); - - // if all the points on all the sides are - // behind this plane, it is a proper edge bevel - for (k=0 ; knumsides ; k++) - { - // if this plane has allready been used, skip it - // NOTE: Use a larger tolerance for collision planes than for rendering planes - if ( PlaneEqual(&mapplanes[b->original_sides[k].planenum], normal, dist, 0.01f, 0.01f ) ) - break; - - w2 = b->original_sides[k].winding; - if (!w2) - continue; - for (l=0 ; lnumpoints ; l++) - { - d = DotProduct (w2->p[l], normal) - dist; - if (d > 0.1) - break; // point in front - } - if (l != w2->numpoints) - break; - } - - if (k != b->numsides) - continue; // wasn't part of the outer hull - // add this plane - if (nummapbrushsides == MAX_MAP_BRUSHSIDES) - g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); - nummapbrushsides++; - s2 = &b->original_sides[b->numsides]; - s2->planenum = FindFloatPlane (normal, dist); - s2->texinfo = b->original_sides[0].texinfo; - s2->contents = b->original_sides[0].contents; - s2->bevel = true; - c_edgebevels++; - b->numsides++; - } - } - } - } -} - -/* -================ -MakeBrushWindings - -makes basewindigs for sides and mins / maxs for the brush -================ -*/ -qboolean CMapFile::MakeBrushWindings (mapbrush_t *ob) -{ - int i, j; - winding_t *w; - side_t *side; - plane_t *plane; - - ClearBounds (ob->mins, ob->maxs); - - for (i=0 ; inumsides ; i++) - { - plane = &mapplanes[ob->original_sides[i].planenum]; - w = BaseWindingForPlane (plane->normal, plane->dist); - for (j=0 ; jnumsides && w; j++) - { - if (i == j) - continue; - if (ob->original_sides[j].bevel) - continue; - plane = &mapplanes[ob->original_sides[j].planenum^1]; -// ChopWindingInPlace (&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); - // adding an epsilon here, due to precision issues creating complex - // displacement surfaces (cab) - ChopWindingInPlace( &w, plane->normal, plane->dist, BRUSH_CLIP_EPSILON ); - } - - side = &ob->original_sides[i]; - side->winding = w; - if (w) - { - side->visible = true; - for (j=0 ; jnumpoints ; j++) - AddPointToBounds (w->p[j], ob->mins, ob->maxs); - } - } - - for (i=0 ; i<3 ; i++) - { - if (ob->mins[i] < MIN_COORD_INTEGER || ob->maxs[i] > MAX_COORD_INTEGER) - Msg("Brush %i: bounds out of range\n", ob->id); - if (ob->mins[i] > MAX_COORD_INTEGER || ob->maxs[i] < MIN_COORD_INTEGER) - Msg("Brush %i: no visible sides on brush\n", ob->id); - } - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Takes all of the brushes from the current entity and adds them to the -// world's brush list. Used by func_detail and func_areaportal. -// THIS ROUTINE MAY ONLY BE USED DURING ENTITY LOADING. -// Input : mapent - Entity whose brushes are to be moved to the world. -//----------------------------------------------------------------------------- -void CMapFile::MoveBrushesToWorld( entity_t *mapent ) -{ - int newbrushes; - int worldbrushes; - mapbrush_t *temp; - int i; - - // this is pretty gross, because the brushes are expected to be - // in linear order for each entity - - newbrushes = mapent->numbrushes; - worldbrushes = entities[0].numbrushes; - - temp = (mapbrush_t *)malloc(newbrushes*sizeof(mapbrush_t)); - memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); - -#if 0 // let them keep their original brush numbers - for (i=0 ; inumbrushes = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: Takes all of the brushes from the current entity and adds them to the -// world's brush list. Used by func_detail and func_areaportal. -// Input : mapent - Entity whose brushes are to be moved to the world. -//----------------------------------------------------------------------------- -void CMapFile::MoveBrushesToWorldGeneral( entity_t *mapent ) -{ - int newbrushes; - int worldbrushes; - mapbrush_t *temp; - int i; - - for( i = 0; i < nummapdispinfo; i++ ) - { - if ( mapdispinfo[ i ].entitynum == ( mapent - entities ) ) - { - mapdispinfo[ i ].entitynum = 0; - } - } - - // this is pretty gross, because the brushes are expected to be - // in linear order for each entity - newbrushes = mapent->numbrushes; - worldbrushes = entities[0].numbrushes; - - temp = (mapbrush_t *)malloc(newbrushes*sizeof(mapbrush_t)); - memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); - -#if 0 // let them keep their original brush numbers - for (i=0 ; ifirstbrush - worldbrushes) ); - - - // wwwxxxmmyyy - - // copy the new brushes down - memcpy (mapbrushes + worldbrushes, temp, sizeof(mapbrush_t) * newbrushes); - - // fix up indexes - entities[0].numbrushes += newbrushes; - for (i=1 ; ifirstbrush ) // if we use <=, then we'll remap the passed in ent, which we don't want to - { - entities[ i ].firstbrush += newbrushes; - } - } - free (temp); - - mapent->numbrushes = 0; -} - -//----------------------------------------------------------------------------- -// Purpose: Iterates the sides of brush and removed CONTENTS_DETAIL from each side -// Input : *brush - -//----------------------------------------------------------------------------- -void RemoveContentsDetailFromBrush( mapbrush_t *brush ) -{ - // Only valid on non-world brushes - Assert( brush->entitynum != 0 ); - - side_t *s; - int i; - - s = &brush->original_sides[0]; - for ( i=0 ; inumsides ; i++, s++ ) - { - if ( s->contents & CONTENTS_DETAIL ) - { - s->contents &= ~CONTENTS_DETAIL; - } - } - -} - -//----------------------------------------------------------------------------- -// Purpose: Iterates all brushes in an entity and removes CONTENTS_DETAIL from all brushes -// Input : *mapent - -//----------------------------------------------------------------------------- -void CMapFile::RemoveContentsDetailFromEntity( entity_t *mapent ) -{ - int i; - for ( i = 0; i < mapent->numbrushes; i++ ) - { - int brushnum = mapent->firstbrush + i; - - mapbrush_t *brush = &mapbrushes[ brushnum ]; - RemoveContentsDetailFromBrush( brush ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pFile - -// *pDisp - -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadDispDistancesCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) -{ - return(pFile->ReadChunk((KeyHandler_t)LoadDispDistancesKeyCallback, pMapDispInfo)); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : szKey - -// szValue - -// pDisp - -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadDispDistancesKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) -{ - if (!strnicmp(szKey, "row", 3)) - { - char szBuf[MAX_KEYVALUE_LEN]; - strcpy(szBuf, szValue); - - int nCols = (1 << pMapDispInfo->power) + 1; - int nRow = atoi(&szKey[3]); - - char *pszNext = strtok(szBuf, " "); - int nIndex = nRow * nCols; - - while (pszNext != NULL) - { - pMapDispInfo->dispDists[nIndex] = (float)atof(pszNext); - pszNext = strtok(NULL, " "); - nIndex++; - } - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: load in the displacement info "chunk" from the .map file into the -// vbsp map displacement info data structure -// Output : return the index of the map displacement info -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadDispInfoCallback(CChunkFile *pFile, mapdispinfo_t **ppMapDispInfo ) -{ - // - // check to see if we exceeded the maximum displacement info list size - // - if (nummapdispinfo > MAX_MAP_DISPINFO) - { - g_MapError.ReportError( "ParseDispInfoChunk: nummapdispinfo > MAX_MAP_DISPINFO" ); - } - - // get a pointer to the next available displacement info slot - mapdispinfo_t *pMapDispInfo = &mapdispinfo[nummapdispinfo]; - nummapdispinfo++; - - // - // Set up handlers for the subchunks that we are interested in. - // - CChunkHandlerMap Handlers; - Handlers.AddHandler("normals", (ChunkHandler_t)LoadDispNormalsCallback, pMapDispInfo); - Handlers.AddHandler("distances", (ChunkHandler_t)LoadDispDistancesCallback, pMapDispInfo); - Handlers.AddHandler("offsets", (ChunkHandler_t)LoadDispOffsetsCallback, pMapDispInfo); - Handlers.AddHandler("alphas", (ChunkHandler_t)LoadDispAlphasCallback, pMapDispInfo); - Handlers.AddHandler("triangle_tags", (ChunkHandler_t)LoadDispTriangleTagsCallback, pMapDispInfo); - -#ifdef VSVMFIO - Handlers.AddHandler("offset_normals", (ChunkHandler_t)LoadDispOffsetNormalsCallback, pMapDispInfo); -#endif // VSVMFIO - - // - // Read the displacement chunk. - // - pFile->PushHandlers(&Handlers); - ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadDispInfoKeyCallback, pMapDispInfo); - pFile->PopHandlers(); - - if (eResult == ChunkFile_Ok) - { - // return a pointer to the displacement info - *ppMapDispInfo = pMapDispInfo; - } - - return(eResult); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *szKey - -// *szValue - -// *mapent - -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadDispInfoKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) -{ - if (!stricmp(szKey, "power")) - { - CChunkFile::ReadKeyValueInt(szValue, pMapDispInfo->power); - } -#ifdef VSVMFIO - else if (!stricmp(szKey, "elevation")) - { - CChunkFile::ReadKeyValueFloat(szValue, pMapDispInfo->m_elevation); - } -#endif // VSVMFIO - else if (!stricmp(szKey, "uaxis")) - { - CChunkFile::ReadKeyValueVector3(szValue, pMapDispInfo->uAxis); - } - else if (!stricmp(szKey, "vaxis")) - { - CChunkFile::ReadKeyValueVector3(szValue, pMapDispInfo->vAxis); - } - else if( !stricmp( szKey, "startposition" ) ) - { - CChunkFile::ReadKeyValueVector3( szValue, pMapDispInfo->startPosition ); - } - else if( !stricmp( szKey, "flags" ) ) - { - CChunkFile::ReadKeyValueInt( szValue, pMapDispInfo->flags ); - } -#if 0 // old data - else if (!stricmp( szKey, "alpha" ) ) - { - CChunkFile::ReadKeyValueVector4( szValue, pMapDispInfo->alphaValues ); - } -#endif - else if (!stricmp(szKey, "mintess")) - { - CChunkFile::ReadKeyValueInt(szValue, pMapDispInfo->minTess); - } - else if (!stricmp(szKey, "smooth")) - { - CChunkFile::ReadKeyValueFloat(szValue, pMapDispInfo->smoothingAngle); - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pFile - -// *pDisp - -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadDispNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) -{ - return(pFile->ReadChunk((KeyHandler_t)LoadDispNormalsKeyCallback, pMapDispInfo)); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *szKey - -// *szValue - -// *pDisp - -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadDispNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) -{ - if (!strnicmp(szKey, "row", 3)) - { - char szBuf[MAX_KEYVALUE_LEN]; - strcpy(szBuf, szValue); - - int nCols = (1 << pMapDispInfo->power) + 1; - int nRow = atoi(&szKey[3]); - - char *pszNext0 = strtok(szBuf, " "); - char *pszNext1 = strtok(NULL, " "); - char *pszNext2 = strtok(NULL, " "); - - int nIndex = nRow * nCols; - - while ((pszNext0 != NULL) && (pszNext1 != NULL) && (pszNext2 != NULL)) - { - pMapDispInfo->vectorDisps[nIndex][0] = (float)atof(pszNext0); - pMapDispInfo->vectorDisps[nIndex][1] = (float)atof(pszNext1); - pMapDispInfo->vectorDisps[nIndex][2] = (float)atof(pszNext2); - - pszNext0 = strtok(NULL, " "); - pszNext1 = strtok(NULL, " "); - pszNext2 = strtok(NULL, " "); - - nIndex++; - } - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *szKey - -// *szValue - -// *pDisp - -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadDispOffsetsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) -{ - return(pFile->ReadChunk((KeyHandler_t)LoadDispOffsetsKeyCallback, pMapDispInfo)); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *szKey - -// *szValue - -// *pDisp - -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadDispOffsetsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) -{ - if (!strnicmp(szKey, "row", 3)) - { - char szBuf[MAX_KEYVALUE_LEN]; - strcpy(szBuf, szValue); - - int nCols = (1 << pMapDispInfo->power) + 1; - int nRow = atoi(&szKey[3]); - - char *pszNext0 = strtok(szBuf, " "); - char *pszNext1 = strtok(NULL, " "); - char *pszNext2 = strtok(NULL, " "); - - int nIndex = nRow * nCols; - - while ((pszNext0 != NULL) && (pszNext1 != NULL) && (pszNext2 != NULL)) - { - pMapDispInfo->vectorOffsets[nIndex][0] = (float)atof(pszNext0); - pMapDispInfo->vectorOffsets[nIndex][1] = (float)atof(pszNext1); - pMapDispInfo->vectorOffsets[nIndex][2] = (float)atof(pszNext2); - - pszNext0 = strtok(NULL, " "); - pszNext1 = strtok(NULL, " "); - pszNext2 = strtok(NULL, " "); - - nIndex++; - } - } - - return(ChunkFile_Ok); -} - - -#ifdef VSVMFIO -ChunkFileResult_t LoadDispOffsetNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) -{ - return(pFile->ReadChunk((KeyHandler_t)LoadDispOffsetNormalsKeyCallback, pMapDispInfo)); -} - - -ChunkFileResult_t LoadDispOffsetNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) -{ - if (!strnicmp(szKey, "row", 3)) - { - char szBuf[MAX_KEYVALUE_LEN]; - strcpy(szBuf, szValue); - - int nCols = (1 << pMapDispInfo->power) + 1; - int nRow = atoi(&szKey[3]); - - char *pszNext0 = strtok(szBuf, " "); - char *pszNext1 = strtok(NULL, " "); - char *pszNext2 = strtok(NULL, " "); - - int nIndex = nRow * nCols; - - while ((pszNext0 != NULL) && (pszNext1 != NULL) && (pszNext2 != NULL)) - { - pMapDispInfo->m_offsetNormals[nIndex][0] = (float)atof(pszNext0); - pMapDispInfo->m_offsetNormals[nIndex][1] = (float)atof(pszNext1); - pMapDispInfo->m_offsetNormals[nIndex][2] = (float)atof(pszNext2); - - pszNext0 = strtok(NULL, " "); - pszNext1 = strtok(NULL, " "); - pszNext2 = strtok(NULL, " "); - - nIndex++; - } - } - - return(ChunkFile_Ok); -} -#endif // VSVMFIO - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *szKey - -// *szValue - -// *pDisp - -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadDispAlphasCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) -{ - return(pFile->ReadChunk((KeyHandler_t)LoadDispAlphasKeyCallback, pMapDispInfo)); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *szKey - -// *szValue - -// *pDisp - -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadDispAlphasKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) -{ - if (!strnicmp(szKey, "row", 3)) - { - char szBuf[MAX_KEYVALUE_LEN]; - strcpy(szBuf, szValue); - - int nCols = (1 << pMapDispInfo->power) + 1; - int nRow = atoi(&szKey[3]); - - char *pszNext0 = strtok(szBuf, " "); - - int nIndex = nRow * nCols; - - while (pszNext0 != NULL) - { - pMapDispInfo->alphaValues[nIndex] = (float)atof(pszNext0); - pszNext0 = strtok(NULL, " "); - nIndex++; - } - } - - return(ChunkFile_Ok); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadDispTriangleTagsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) -{ - return(pFile->ReadChunk((KeyHandler_t)LoadDispTriangleTagsKeyCallback, pMapDispInfo)); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadDispTriangleTagsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) -{ - if ( !strnicmp( szKey, "row", 3 ) ) - { - char szBuf[MAX_KEYVALUE_LEN]; - strcpy( szBuf, szValue ); - - int nCols = ( 1 << pMapDispInfo->power ); - int nRow = atoi( &szKey[3] ); - - char *pszNext = strtok( szBuf, " " ); - - int nIndex = nRow * nCols; - int iTri = nIndex * 2; - - while ( pszNext != NULL ) - { - // Collapse the tags here! - unsigned short nTriTags = ( unsigned short )atoi( pszNext ); - - // Walkable - bool bWalkable = ( ( nTriTags & COREDISPTRI_TAG_WALKABLE ) != 0 ); - if ( ( ( nTriTags & COREDISPTRI_TAG_FORCE_WALKABLE_BIT ) != 0 ) ) - { - bWalkable = ( ( nTriTags & COREDISPTRI_TAG_FORCE_WALKABLE_VAL ) != 0 ); - } - - // Buildable - bool bBuildable = ( ( nTriTags & COREDISPTRI_TAG_BUILDABLE ) != 0 ); - if ( ( ( nTriTags & COREDISPTRI_TAG_FORCE_BUILDABLE_BIT ) != 0 ) ) - { - bBuildable = ( ( nTriTags & COREDISPTRI_TAG_FORCE_BUILDABLE_VAL ) != 0 ); - } - - nTriTags = 0; - if ( bWalkable ) - { - nTriTags |= DISPTRI_TAG_WALKABLE; - } - - if ( bBuildable ) - { - nTriTags |= DISPTRI_TAG_BUILDABLE; - } - - pMapDispInfo->triTags[iTri] = nTriTags; - pszNext = strtok( NULL, " " ); - iTri++; - } - } - - return( ChunkFile_Ok ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : brushSideID - -// Output : int -//----------------------------------------------------------------------------- -int CMapFile::SideIDToIndex( int brushSideID ) -{ - int i; - for ( i = 0; i < nummapbrushsides; i++ ) - { - if ( brushsides[i].id == brushSideID ) - { - return i; - } - } - Assert( 0 ); - return -1; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *mapent - -// *key - -//----------------------------------------------------------------------------- -void ConvertSideList( entity_t *mapent, char *key ) -{ - char *pszSideList = ValueForKey( mapent, key ); - - if (pszSideList) - { - char *pszTmpList = ( char* )_alloca( strlen( pszSideList ) + 1 ); - strcpy( pszTmpList, pszSideList ); - - bool bFirst = true; - char szNewValue[1024]; - szNewValue[0] = '\0'; - - const char *pszScan = strtok( pszTmpList, " " ); - if ( !pszScan ) - return; - do - { - int nSideID; - - if ( sscanf( pszScan, "%d", &nSideID ) == 1 ) - { - int nIndex = g_LoadingMap->SideIDToIndex(nSideID); - if (nIndex != -1) - { - if (!bFirst) - { - strcat( szNewValue, " " ); - } - else - { - bFirst = false; - } - - char szIndex[15]; - itoa( nIndex, szIndex, 10 ); - strcat( szNewValue, szIndex ); - } - } - } while ( ( pszScan = strtok( NULL, " " ) ) ); - - SetKeyValue( mapent, key, szNewValue ); - } -} - - -// Add all the sides referenced by info_no_dynamic_shadows entities to g_NoDynamicShadowSides. -ChunkFileResult_t HandleNoDynamicShadowsEnt( entity_t *pMapEnt ) -{ - // Get the list of the sides. - char *pSideList = ValueForKey( pMapEnt, "sides" ); - - // Parse the side list. - char *pScan = strtok( pSideList, " " ); - if( pScan ) - { - do - { - int brushSideID; - if( sscanf( pScan, "%d", &brushSideID ) == 1 ) - { - if ( g_NoDynamicShadowSides.Find( brushSideID ) == -1 ) - g_NoDynamicShadowSides.AddToTail( brushSideID ); - } - } while( ( pScan = strtok( NULL, " " ) ) ); - } - - // Clear out this entity. - pMapEnt->epairs = NULL; - return ( ChunkFile_Ok ); -} - - -static ChunkFileResult_t LoadOverlayDataTransitionKeyCallback( const char *szKey, const char *szValue, mapoverlay_t *pOverlay ) -{ - if ( !stricmp( szKey, "material" ) ) - { - // Get the material name. - const char *pMaterialName = szValue; - if( g_ReplaceMaterials ) - { - pMaterialName = ReplaceMaterialName( szValue ); - } - - Assert( strlen( pMaterialName ) < OVERLAY_MAP_STRLEN ); - if ( strlen( pMaterialName ) >= OVERLAY_MAP_STRLEN ) - { - Error( "Overlay Material Name (%s) > OVERLAY_MAP_STRLEN (%d)", pMaterialName, OVERLAY_MAP_STRLEN ); - return ChunkFile_Fail; - } - strcpy( pOverlay->szMaterialName, pMaterialName ); - } - else if ( !stricmp( szKey, "StartU") ) - { - CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flU[0] ); - } - else if ( !stricmp( szKey, "EndU" ) ) - { - CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flU[1] ); - } - else if ( !stricmp( szKey, "StartV" ) ) - { - CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flV[0] ); - } - else if ( !stricmp( szKey, "EndV" ) ) - { - CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flV[1] ); - } - else if ( !stricmp( szKey, "BasisOrigin" ) ) - { - CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecOrigin ); - } - else if ( !stricmp( szKey, "BasisU" ) ) - { - CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecBasis[0] ); - } - else if ( !stricmp( szKey, "BasisV" ) ) - { - CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecBasis[1] ); - } - else if ( !stricmp( szKey, "BasisNormal" ) ) - { - CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecBasis[2] ); - } - else if ( !stricmp( szKey, "uv0" ) ) - { - CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[0] ); - } - else if ( !stricmp( szKey, "uv1" ) ) - { - CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[1] ); - } - else if ( !stricmp( szKey, "uv2" ) ) - { - CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[2] ); - } - else if ( !stricmp( szKey, "uv3" ) ) - { - CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[3] ); - } - else if ( !stricmp( szKey, "sides" ) ) - { - const char *pSideList = szValue; - char *pTmpList = ( char* )_alloca( strlen( pSideList ) + 1 ); - strcpy( pTmpList, pSideList ); - const char *pScan = strtok( pTmpList, " " ); - if ( !pScan ) - return ChunkFile_Fail; - - pOverlay->aSideList.Purge(); - pOverlay->aFaceList.Purge(); - - do - { - int nSideId; - if ( sscanf( pScan, "%d", &nSideId ) == 1 ) - { - pOverlay->aSideList.AddToTail( nSideId ); - } - } while ( ( pScan = strtok( NULL, " " ) ) ); - } - - return ChunkFile_Ok; -} - -static ChunkFileResult_t LoadOverlayDataTransitionCallback( CChunkFile *pFile, int nParam ) -{ - int iOverlay = g_aMapWaterOverlays.AddToTail(); - mapoverlay_t *pOverlay = &g_aMapWaterOverlays[iOverlay]; - if ( !pOverlay ) - return ChunkFile_Fail; - - pOverlay->nId = ( MAX_MAP_OVERLAYS + 1 ) + g_aMapWaterOverlays.Count() - 1; - pOverlay->m_nRenderOrder = 0; - - ChunkFileResult_t eResult = pFile->ReadChunk( ( KeyHandler_t )LoadOverlayDataTransitionKeyCallback, pOverlay ); - return eResult; -} - -static ChunkFileResult_t LoadOverlayTransitionCallback( CChunkFile *pFile, int nParam ) -{ - CChunkHandlerMap Handlers; - Handlers.AddHandler( "overlaydata", ( ChunkHandler_t )LoadOverlayDataTransitionCallback, 0 ); - pFile->PushHandlers( &Handlers ); - - ChunkFileResult_t eResult = pFile->ReadChunk( NULL, NULL ); - - pFile->PopHandlers(); - - return eResult; -} - -//----------------------------------------------------------------------------- -// Purpose: Iterates all brushes in a ladder entity, generates its mins and maxs. -// These are stored in the object, since the brushes are going to go away. -// Input : *mapent - -//----------------------------------------------------------------------------- -void CMapFile::AddLadderKeys( entity_t *mapent ) -{ - Vector mins, maxs; - ClearBounds( mins, maxs ); - - int i; - for ( i = 0; i < mapent->numbrushes; i++ ) - { - int brushnum = mapent->firstbrush + i; - mapbrush_t *brush = &mapbrushes[ brushnum ]; - - AddPointToBounds( brush->mins, mins, maxs ); - AddPointToBounds( brush->maxs, mins, maxs ); - } - - char buf[16]; - - Q_snprintf( buf, sizeof(buf), "%2.2f", mins.x ); - SetKeyValue( mapent, "mins.x", buf ); - - Q_snprintf( buf, sizeof(buf), "%2.2f", mins.y ); - SetKeyValue( mapent, "mins.y", buf ); - - Q_snprintf( buf, sizeof(buf), "%2.2f", mins.z ); - SetKeyValue( mapent, "mins.z", buf ); - - Q_snprintf( buf, sizeof(buf), "%2.2f", maxs.x ); - SetKeyValue( mapent, "maxs.x", buf ); - - Q_snprintf( buf, sizeof(buf), "%2.2f", maxs.y ); - SetKeyValue( mapent, "maxs.y", buf ); - - Q_snprintf( buf, sizeof(buf), "%2.2f", maxs.z ); - SetKeyValue( mapent, "maxs.z", buf ); -} - -ChunkFileResult_t LoadEntityCallback(CChunkFile *pFile, int nParam) -{ - return g_LoadingMap->LoadEntityCallback( pFile, nParam ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pFile - -// ulParam - -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t CMapFile::LoadEntityCallback(CChunkFile *pFile, int nParam) -{ - if (num_entities == MAX_MAP_ENTITIES) - { - // Exits. - g_MapError.ReportError ("num_entities == MAX_MAP_ENTITIES"); - } - - entity_t *mapent = &entities[num_entities]; - num_entities++; - memset(mapent, 0, sizeof(*mapent)); - mapent->firstbrush = nummapbrushes; - mapent->numbrushes = 0; - //mapent->portalareas[0] = -1; - //mapent->portalareas[1] = -1; - - LoadEntity_t LoadEntity; - LoadEntity.pEntity = mapent; - - // No default flags/contents - LoadEntity.nBaseFlags = 0; - LoadEntity.nBaseContents = 0; - - // - // Set up handlers for the subchunks that we are interested in. - // - CChunkHandlerMap Handlers; - Handlers.AddHandler("solid", (ChunkHandler_t)::LoadSolidCallback, &LoadEntity); - Handlers.AddHandler("connections", (ChunkHandler_t)LoadConnectionsCallback, &LoadEntity); - Handlers.AddHandler( "overlaytransition", ( ChunkHandler_t )LoadOverlayTransitionCallback, 0 ); - - // - // Read the entity chunk. - // - pFile->PushHandlers(&Handlers); - ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadEntityKeyCallback, &LoadEntity); - pFile->PopHandlers(); - - if (eResult == ChunkFile_Ok) - { - GetVectorForKey (mapent, "origin", mapent->origin); - - const char *pMinDXLevelStr = ValueForKey( mapent, "mindxlevel" ); - const char *pMaxDXLevelStr = ValueForKey( mapent, "maxdxlevel" ); - if( *pMinDXLevelStr != '\0' || *pMaxDXLevelStr != '\0' ) - { - int min = 0; - int max = 0; - if( *pMinDXLevelStr ) - { - min = atoi( pMinDXLevelStr ); - } - if( *pMaxDXLevelStr ) - { - max = atoi( pMaxDXLevelStr ); - } - - // Set min and max to default values. - if( min == 0 ) - { - min = g_nDXLevel; - } - if( max == 0 ) - { - max = g_nDXLevel; - } - if( ( g_nDXLevel != 0 ) && ( g_nDXLevel < min || g_nDXLevel > max ) ) - { - mapent->numbrushes = 0; - mapent->epairs = NULL; - return(ChunkFile_Ok); - } - } - - // offset all of the planes and texinfo - if ( mapent->origin[0] || mapent->origin[1] || mapent->origin[2] ) - { - for (int i=0 ; inumbrushes ; i++) - { - mapbrush_t *b = &mapbrushes[mapent->firstbrush + i]; - for (int j=0 ; jnumsides ; j++) - { - side_t *s = &b->original_sides[j]; - vec_t newdist = mapplanes[s->planenum].dist - DotProduct (mapplanes[s->planenum].normal, mapent->origin); - s->planenum = FindFloatPlane (mapplanes[s->planenum].normal, newdist); - if ( !onlyents ) - { - s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], &side_brushtextures[s-brushsides], mapent->origin); - } - } - MakeBrushWindings (b); - } - } - - // - // func_detail brushes are moved into the world entity. The CONTENTS_DETAIL flag was set by the loader. - // - const char *pClassName = ValueForKey( mapent, "classname" ); - - if ( !strcmp( "func_detail", pClassName ) ) - { - MoveBrushesToWorld (mapent); - mapent->numbrushes = 0; - - // clear out this entity - mapent->epairs = NULL; - return(ChunkFile_Ok); - } - - // these get added to a list for processing the portal file - // but aren't necessary to emit to the BSP - if ( !strcmp( "func_viscluster", pClassName ) ) - { - AddVisCluster(mapent); - return(ChunkFile_Ok); - } - - // - // func_ladder brushes are moved into the world entity. We convert the func_ladder to an info_ladder - // that holds the ladder's mins and maxs, and leave the entity. This helps the bots figure out ladders. - // - if ( !strcmp( "func_ladder", pClassName ) ) - { - AddLadderKeys( mapent ); - - MoveBrushesToWorld (mapent); - - // Convert to info_ladder entity - SetKeyValue( mapent, "classname", "info_ladder" ); - - return(ChunkFile_Ok); - } - - if( !strcmp( "env_cubemap", pClassName ) ) - { - if( ( g_nDXLevel == 0 ) || ( g_nDXLevel >= 70 ) ) - { - const char *pSideListStr = ValueForKey( mapent, "sides" ); - int size; - size = IntForKey( mapent, "cubemapsize" ); - Cubemap_InsertSample( mapent->origin, size ); - Cubemap_SaveBrushSides( pSideListStr ); - } - // clear out this entity - mapent->epairs = NULL; - return(ChunkFile_Ok); - } - - if ( !strcmp( "test_sidelist", pClassName ) ) - { - ConvertSideList(mapent, "sides"); - return ChunkFile_Ok; - } - - if ( !strcmp( "info_overlay", pClassName ) ) - { - int iAccessorID = Overlay_GetFromEntity( mapent ); - - if ( iAccessorID < 0 ) - { - // Clear out this entity. - mapent->epairs = NULL; - } - else - { - // Convert to info_overlay_accessor entity - SetKeyValue( mapent, "classname", "info_overlay_accessor" ); - - // Remember the id for accessing the overlay - char buf[16]; - Q_snprintf( buf, sizeof(buf), "%i", iAccessorID ); - SetKeyValue( mapent, "OverlayID", buf ); - } - - return ( ChunkFile_Ok ); - } - - if ( !strcmp( "info_overlay_transition", pClassName ) ) - { - // Clear out this entity. - mapent->epairs = NULL; - return ( ChunkFile_Ok ); - } - - if ( Q_stricmp( pClassName, "info_no_dynamic_shadow" ) == 0 ) - { - return HandleNoDynamicShadowsEnt( mapent ); - } - - if ( Q_stricmp( pClassName, "func_instance_parms" ) == 0 ) - { - // Clear out this entity. - mapent->epairs = NULL; - return ( ChunkFile_Ok ); - } - - // areaportal entities move their brushes, but don't eliminate - // the entity - if( IsAreaPortal( pClassName ) ) - { - char str[128]; - - if (mapent->numbrushes != 1) - { - Error ("Entity %i: func_areaportal can only be a single brush", num_entities-1); - } - - mapbrush_t *b = &mapbrushes[nummapbrushes-1]; - b->contents = CONTENTS_AREAPORTAL; - c_areaportals++; - mapent->areaportalnum = c_areaportals; - - // set the portal number as "portalnumber" - sprintf (str, "%i", c_areaportals); - SetKeyValue (mapent, "portalnumber", str); - - MoveBrushesToWorld (mapent); - return(ChunkFile_Ok); - } - -#ifdef VSVMFIO - if ( !Q_stricmp( pClassName, "light" ) ) - { - CVmfImport::GetVmfImporter()->ImportLightCallback( - ValueForKey( mapent, "hammerid" ), - ValueForKey( mapent, "origin" ), - ValueForKey( mapent, "_light" ), - ValueForKey( mapent, "_lightHDR" ), - ValueForKey( mapent, "_lightscaleHDR" ), - ValueForKey( mapent, "_quadratic_attn" ) ); - } - - if ( !Q_stricmp( pClassName, "light_spot" ) ) - { - CVmfImport::GetVmfImporter()->ImportLightSpotCallback( - ValueForKey( mapent, "hammerid" ), - ValueForKey( mapent, "origin" ), - ValueForKey( mapent, "angles" ), - ValueForKey( mapent, "pitch" ), - ValueForKey( mapent, "_light" ), - ValueForKey( mapent, "_lightHDR" ), - ValueForKey( mapent, "_lightscaleHDR" ), - ValueForKey( mapent, "_quadratic_attn" ), - ValueForKey( mapent, "_inner_cone" ), - ValueForKey( mapent, "_cone" ), - ValueForKey( mapent, "_exponent" ) ); - } - - if ( !Q_stricmp( pClassName, "light_dynamic" ) ) - { - CVmfImport::GetVmfImporter()->ImportLightDynamicCallback( - ValueForKey( mapent, "hammerid" ), - ValueForKey( mapent, "origin" ), - ValueForKey( mapent, "angles" ), - ValueForKey( mapent, "pitch" ), - ValueForKey( mapent, "_light" ), - ValueForKey( mapent, "_quadratic_attn" ), - ValueForKey( mapent, "_inner_cone" ), - ValueForKey( mapent, "_cone" ), - ValueForKey( mapent, "brightness" ), - ValueForKey( mapent, "distance" ), - ValueForKey( mapent, "spotlight_radius" ) ); - } - - if ( !Q_stricmp( pClassName, "light_environment" ) ) - { - CVmfImport::GetVmfImporter()->ImportLightEnvironmentCallback( - ValueForKey( mapent, "hammerid" ), - ValueForKey( mapent, "origin" ), - ValueForKey( mapent, "angles" ), - ValueForKey( mapent, "pitch" ), - ValueForKey( mapent, "_light" ), - ValueForKey( mapent, "_lightHDR" ), - ValueForKey( mapent, "_lightscaleHDR" ), - ValueForKey( mapent, "_ambient" ), - ValueForKey( mapent, "_ambientHDR" ), - ValueForKey( mapent, "_AmbientScaleHDR" ), - ValueForKey( mapent, "SunSpreadAngle" ) ); - } - - const char *pModel = ValueForKey( mapent, "model" ); - if ( pModel && Q_strlen( pModel ) ) - { - CVmfImport::GetVmfImporter()->ImportModelCallback( - pModel, - ValueForKey( mapent, "hammerid" ), - ValueForKey( mapent, "angles" ), - ValueForKey( mapent, "origin" ), - MDagPath() ); - } -#endif // VSVMFIO - - // If it's not in the world at this point, unmark CONTENTS_DETAIL from all sides... - if ( mapent != &entities[ 0 ] ) - { - RemoveContentsDetailFromEntity( mapent ); - } - - return(ChunkFile_Ok); - } - - return(eResult); -} - - -entity_t* EntityByName( char const *pTestName ) -{ - if( !pTestName ) - return 0; - - for( int i=0; i < g_MainMap->num_entities; i++ ) - { - entity_t *e = &g_MainMap->entities[i]; - - const char *pName = ValueForKey( e, "targetname" ); - if( stricmp( pName, pTestName ) == 0 ) - return e; - } - - return 0; -} - - -void CMapFile::ForceFuncAreaPortalWindowContents() -{ - // Now go through all areaportal entities and force CONTENTS_WINDOW - // on the brushes of the bmodels they point at. - char *targets[] = {"target", "BackgroundBModel"}; - int nTargets = sizeof(targets) / sizeof(targets[0]); - - for( int i=0; i < num_entities; i++ ) - { - entity_t *e = &entities[i]; - - const char *pClassName = ValueForKey( e, "classname" ); - - // Don't do this on "normal" func_areaportal entities. Those are tied to doors - // and should be opaque when closed. But areaportal windows (and any other - // distance-based areaportals) should be windows because they are normally open/transparent - if( !IsAreaPortal( pClassName ) || !Q_stricmp( pClassName, "func_areaportal" ) ) - continue; - -// const char *pTestEntName = ValueForKey( e, "targetname" ); - - for( int iTarget=0; iTarget < nTargets; iTarget++ ) - { - char const *pEntName = ValueForKey( e, targets[iTarget] ); - if( !pEntName[0] ) - continue; - - entity_t *pBrushEnt = EntityByName( pEntName ); - if( !pBrushEnt ) - continue; - - for( int iBrush=0; iBrush < pBrushEnt->numbrushes; iBrush++ ) - { - mapbrushes[pBrushEnt->firstbrush + iBrush].contents &= ~CONTENTS_SOLID; - mapbrushes[pBrushEnt->firstbrush + iBrush].contents |= CONTENTS_TRANSLUCENT | CONTENTS_WINDOW; - } - } - } -} - - -// ============ Instancing ============ - -// #define MERGE_INSTANCE_DEBUG_INFO 1 - -#define INSTANCE_VARIABLE_KEY "replace" - -static GameData GD; - -//----------------------------------------------------------------------------- -// Purpose: this function will read in a standard key / value file -// Input : pFilename - the absolute name of the file to read -// Output : returns the KeyValues of the file, NULL if the file could not be read. -//----------------------------------------------------------------------------- -static KeyValues *ReadKeyValuesFile( const char *pFilename ) -{ - // Read in the gameinfo.txt file and null-terminate it. - FILE *fp = fopen( pFilename, "rb" ); - if ( !fp ) - return NULL; - CUtlVector buf; - fseek( fp, 0, SEEK_END ); - buf.SetSize( ftell( fp ) + 1 ); - fseek( fp, 0, SEEK_SET ); - fread( buf.Base(), 1, buf.Count()-1, fp ); - fclose( fp ); - buf[buf.Count()-1] = 0; - - KeyValues *kv = new KeyValues( "" ); - if ( !kv->LoadFromBuffer( pFilename, buf.Base() ) ) - { - kv->deleteThis(); - return NULL; - } - - return kv; -} - - -//----------------------------------------------------------------------------- -// Purpose: this function will set a secondary lookup path for instances. -// Input : pszInstancePath - the secondary lookup path -//----------------------------------------------------------------------------- -void CMapFile::SetInstancePath( const char *pszInstancePath ) -{ - strcpy( m_InstancePath, pszInstancePath ); - V_strlower( m_InstancePath ); - V_FixSlashes( m_InstancePath ); -} - - -//----------------------------------------------------------------------------- -// Purpose: This function will attempt to find a full path given the base and relative names. -// Input : pszBaseFileName - the base file that referenced this instance -// pszInstanceFileName - the relative file name of this instance -// Output : Returns true if it was able to locate the file -// pszOutFileName - the full path to the file name if located -//----------------------------------------------------------------------------- -bool CMapFile::DeterminePath( const char *pszBaseFileName, const char *pszInstanceFileName, char *pszOutFileName ) -{ - char szInstanceFileNameFixed[ MAX_PATH ]; - const char *pszMapPath = "\\maps\\"; - - strcpy( szInstanceFileNameFixed, pszInstanceFileName ); - V_SetExtension( szInstanceFileNameFixed, ".vmf", sizeof( szInstanceFileNameFixed ) ); - V_FixSlashes( szInstanceFileNameFixed ); - - // first, try to find a relative location based upon the Base file name - strcpy( pszOutFileName, pszBaseFileName ); - V_StripFilename( pszOutFileName ); - - strcat( pszOutFileName, "\\" ); - strcat( pszOutFileName, szInstanceFileNameFixed ); - - if ( g_pFullFileSystem->FileExists( pszOutFileName ) ) - { - return true; - } - - // second, try to find the master 'maps' directory and make it relative from that - strcpy( pszOutFileName, pszBaseFileName ); - V_StripFilename( pszOutFileName ); - V_RemoveDotSlashes( pszOutFileName ); - V_FixDoubleSlashes( pszOutFileName ); - V_strlower( pszOutFileName ); - strcat( pszOutFileName, "\\" ); - - char *pos = strstr( pszOutFileName, pszMapPath ); - if ( pos ) - { - pos += strlen( pszMapPath ); - *pos = 0; - strcat( pszOutFileName, szInstanceFileNameFixed ); - - if ( g_pFullFileSystem->FileExists( pszOutFileName ) ) - { - return true; - } - } - - if ( m_InstancePath[ 0 ] != 0 ) - { - sprintf( szInstanceFileNameFixed, "%s%s", m_InstancePath, pszInstanceFileName ); - - if ( g_pFullFileSystem->FileExists( szInstanceFileNameFixed, "GAME" ) ) - { - char FullPath[ MAX_PATH ]; - g_pFullFileSystem->RelativePathToFullPath( szInstanceFileNameFixed, "GAME", FullPath, sizeof( FullPath ) ); - strcpy( pszOutFileName, FullPath ); - - return true; - } - } - - pszOutFileName[ 0 ] = 0; - - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: this function will check the main map for any func_instances. It will -// also attempt to load in the gamedata file for instancing remapping help. -// Input : none -// Output : none -//----------------------------------------------------------------------------- -void CMapFile::CheckForInstances( const char *pszFileName ) -{ - if ( this != g_MainMap ) - { // all sub-instances will be appended to the main map master list as they are read in - // so the main loop below will naturally get to the appended ones. - return; - } - - char GameInfoPath[ MAX_PATH ]; - - g_pFullFileSystem->RelativePathToFullPath( "gameinfo.txt", "MOD", GameInfoPath, sizeof( GameInfoPath ) ); - KeyValues *GameInfoKV = ReadKeyValuesFile( GameInfoPath ); - if ( !GameInfoKV ) - { - Msg( "Could not locate gameinfo.txt for Instance Remapping at %s\n", GameInfoPath ); - return; - } - - const char *InstancePath = GameInfoKV->GetString( "InstancePath", NULL ); - if ( InstancePath ) - { - CMapFile::SetInstancePath( InstancePath ); - } - - const char *GameDataFile = GameInfoKV->GetString( "GameData", NULL ); - if ( !GameDataFile ) - { - Msg( "Could not locate 'GameData' key in %s\n", GameInfoPath ); - return; - } - - char FDGPath[ MAX_PATH ]; - if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, "EXECUTABLE_PATH", FDGPath, sizeof( FDGPath ) ) ) - { - if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, "", FDGPath, sizeof( FDGPath ) ) ) - { - Msg( "Could not locate GameData file %s\n", GameDataFile ); - } - } - - GD.Load( FDGPath ); - - // this list will grow as instances are merged onto it. sub-instances are merged and - // automatically done in this processing. - for ( int i = 0; i < num_entities; i++ ) - { - char *pEntity = ValueForKey( &entities[ i ], "classname" ); - if ( !strcmp( pEntity, "func_instance" ) ) - { - char *pInstanceFile = ValueForKey( &entities[ i ], "file" ); - if ( pInstanceFile[ 0 ] ) - { - char InstancePath[ MAX_PATH ]; - bool bLoaded = false; - - if ( DeterminePath( pszFileName, pInstanceFile, InstancePath ) ) - { - if ( LoadMapFile( InstancePath ) ) - { - MergeInstance( &entities[ i ], g_LoadingMap ); - delete g_LoadingMap; - bLoaded = true; - } - } - - if ( bLoaded == false ) - { - Color red( 255, 0, 0, 255 ); - - ColorSpewMessage( SPEW_ERROR, &red, "Could not open instance file %s\n", pInstanceFile ); - } - } - - entities[ i ].numbrushes = 0; - entities[ i ].epairs = NULL; - } - } - - g_LoadingMap = this; -} - - -//----------------------------------------------------------------------------- -// Purpose: this function will do all of the necessary work to merge the instance -// into the main map. -// Input : pInstanceEntity - the entity of the func_instance -// Instance - the map file of the instance -// Output : none -//----------------------------------------------------------------------------- -void CMapFile::MergeInstance( entity_t *pInstanceEntity, CMapFile *Instance ) -{ - matrix3x4_t mat; - QAngle angles; - Vector OriginOffset = pInstanceEntity->origin; - - m_InstanceCount++; - - GetAnglesForKey( pInstanceEntity, "angles", angles ); - AngleMatrix( angles, OriginOffset, mat ); - -#ifdef MERGE_INSTANCE_DEBUG_INFO - Msg( "Instance Remapping: O:( %g, %g, %g ) A:( %g, %g, %g )\n", OriginOffset.x, OriginOffset.y, OriginOffset.z, angles.x, angles.y, angles.z ); -#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO - MergePlanes( pInstanceEntity, Instance, OriginOffset, angles, mat ); - MergeBrushes( pInstanceEntity, Instance, OriginOffset, angles, mat ); - MergeBrushSides( pInstanceEntity, Instance, OriginOffset, angles, mat ); - MergeEntities( pInstanceEntity, Instance, OriginOffset, angles, mat ); - MergeOverlays( pInstanceEntity, Instance, OriginOffset, angles, mat ); -} - - -//----------------------------------------------------------------------------- -// Purpose: this function will merge in the map planes from the instance into -// the main map. -// Input : pInstanceEntity - the entity of the func_instance -// Instance - the map file of the instance -// InstanceOrigin - the translation of the instance -// InstanceAngle - the rotation of the instance -// InstanceMatrix - the translation / rotation matrix of the instance -// Output : none -//----------------------------------------------------------------------------- -void CMapFile::MergePlanes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) -{ - // Each pair of planes needs to be added to the main map - for ( int i = 0; i < Instance->nummapplanes; i += 2 ) - { - FindFloatPlane( Instance->mapplanes[i].normal, Instance->mapplanes[i].dist ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: this function will merge in the map brushes from the instance into -// the main map. -// Input : pInstanceEntity - the entity of the func_instance -// Instance - the map file of the instance -// InstanceOrigin - the translation of the instance -// InstanceAngle - the rotation of the instance -// InstanceMatrix - the translation / rotation matrix of the instance -// Output : none -//----------------------------------------------------------------------------- -void CMapFile::MergeBrushes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) -{ - int max_brush_id = 0; - - for( int i = 0; i < nummapbrushes; i++ ) - { - if ( mapbrushes[ i ].id > max_brush_id ) - { - max_brush_id = mapbrushes[ i ].id; - } - } - - for( int i = 0; i < Instance->nummapbrushes; i++ ) - { - mapbrushes[ nummapbrushes + i ] = Instance->mapbrushes[ i ]; - - mapbrush_t *brush = &mapbrushes[ nummapbrushes + i ]; - brush->entitynum += num_entities; - brush->brushnum += nummapbrushes; - - if ( i < Instance->entities[ 0 ].numbrushes || ( brush->contents & CONTENTS_LADDER ) != 0 ) - { // world spawn brushes as well as ladders we physically move - Vector minsIn = brush->mins; - Vector maxsIn = brush->maxs; - - TransformAABB( InstanceMatrix, minsIn, maxsIn, brush->mins, brush->maxs ); - } - else - { - } - brush->id += max_brush_id; - - int index = brush->original_sides - Instance->brushsides; - brush->original_sides = &brushsides[ nummapbrushsides + index ]; - } - - nummapbrushes += Instance->nummapbrushes; -} - - -//----------------------------------------------------------------------------- -// Purpose: this function will merge in the map sides from the instance into -// the main map. -// Input : pInstanceEntity - the entity of the func_instance -// Instance - the map file of the instance -// InstanceOrigin - the translation of the instance -// InstanceAngle - the rotation of the instance -// InstanceMatrix - the translation / rotation matrix of the instance -// Output : none -//----------------------------------------------------------------------------- -void CMapFile::MergeBrushSides( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) -{ - int max_side_id = 0; - - for( int i = 0; i < nummapbrushsides; i++ ) - { - if ( brushsides[ i ].id > max_side_id ) - { - max_side_id = brushsides[ i ].id; - } - } - - for( int i = 0; i < Instance->nummapbrushsides; i++ ) - { - brushsides[ nummapbrushsides + i ] = Instance->brushsides[ i ]; - - side_t *side = &brushsides[ nummapbrushsides + i ]; - // The planes got merged & remapped. So you need to search for the output plane index on each side - // NOTE: You could optimize this by saving off an index map in MergePlanes - side->planenum = FindFloatPlane( Instance->mapplanes[side->planenum].normal, Instance->mapplanes[side->planenum].dist ); - side->id += max_side_id; - - // this could be pre-processed into a list for quicker checking - bool bNeedsTranslation = ( side->pMapDisp && side->pMapDisp->entitynum == 0 ); - if ( !bNeedsTranslation ) - { // check for sides that are part of the world spawn - those need translating - for( int j = 0; j < Instance->entities[ 0 ].numbrushes; j++ ) - { - int loc = Instance->mapbrushes[ j ].original_sides - Instance->brushsides; - - if ( i >= loc && i < ( loc + Instance->mapbrushes[ j ].numsides ) ) - { - bNeedsTranslation = true; - break; - } - } - } - if ( !bNeedsTranslation ) - { // sides for ladders are outside of the world spawn, but also need translating - for( int j = Instance->entities[ 0 ].numbrushes; j < Instance->nummapbrushes; j++ ) - { - int loc = Instance->mapbrushes[ j ].original_sides - Instance->brushsides; - - if ( i >= loc && i < ( loc + Instance->mapbrushes[ j ].numsides ) && ( Instance->mapbrushes[ j ].contents & CONTENTS_LADDER ) != 0 ) - { - bNeedsTranslation = true; - break; - } - } - } - if ( bNeedsTranslation ) - { // we only want to do the adjustment on world spawn brushes, not entity brushes - if ( side->winding ) - { - for( int point = 0; point < side->winding->numpoints; point++ ) - { - Vector inPoint = side->winding->p[ point ]; - VectorTransform( inPoint, InstanceMatrix, side->winding->p[ point ] ); - } - } - - int planenum = side->planenum; - cplane_t inPlane, outPlane; - inPlane.normal = mapplanes[ planenum ].normal; - inPlane.dist = mapplanes[ planenum ].dist; - - MatrixTransformPlane( InstanceMatrix, inPlane, outPlane ); - planenum = FindFloatPlane( outPlane.normal, outPlane.dist ); - side->planenum = planenum; - - brush_texture_t bt = Instance->side_brushtextures[ i ]; - - VectorRotate( Instance->side_brushtextures[ i ].UAxis, InstanceMatrix, bt.UAxis ); - VectorRotate( Instance->side_brushtextures[ i ].VAxis, InstanceMatrix, bt.VAxis ); - bt.shift[ 0 ] -= InstanceOrigin.Dot( bt.UAxis ) / bt.textureWorldUnitsPerTexel[ 0 ]; - bt.shift[ 1 ] -= InstanceOrigin.Dot( bt.VAxis ) / bt.textureWorldUnitsPerTexel[ 1 ]; - - if ( !onlyents ) - { - side->texinfo = TexinfoForBrushTexture ( &mapplanes[ side->planenum ], &bt, vec3_origin ); - } - } - - if ( side->pMapDisp ) - { - mapdispinfo_t *disp = side->pMapDisp; - - disp->brushSideID = side->id; - Vector inPoint = disp->startPosition; - VectorTransform( inPoint, InstanceMatrix, disp->startPosition ); - - disp->face.originalface = side; - disp->face.texinfo = side->texinfo; - disp->face.planenum = side->planenum; - disp->entitynum += num_entities; - - for( int point = 0; point < disp->face.w->numpoints; point++ ) - { - Vector inPoint = disp->face.w->p[ point ]; - VectorTransform( inPoint, InstanceMatrix, disp->face.w->p[ point ] ); - } - - } - } - - nummapbrushsides += Instance->nummapbrushsides; -} - - -//----------------------------------------------------------------------------- -// Purpose: this function will look for replace parameters in the function instance -// to see if there is anything in the epair that should be replaced. -// Input : pPair - the epair with the value -// pInstanceEntity - the func_instance that may ahve replace keywords -// Output : pPair - the value field may be updated -//----------------------------------------------------------------------------- -void CMapFile::ReplaceInstancePair( epair_t *pPair, entity_t *pInstanceEntity ) -{ - char Value[ MAX_KEYVALUE_LEN ], NewValue[ MAX_KEYVALUE_LEN ]; - bool Overwritten = false; - - strcpy( NewValue, pPair->value ); - for ( epair_t *epInstance = pInstanceEntity->epairs; epInstance != NULL; epInstance = epInstance->next ) - { - if ( strnicmp( epInstance->key, INSTANCE_VARIABLE_KEY, strlen( INSTANCE_VARIABLE_KEY ) ) == 0 ) - { - char InstanceVariable[ MAX_KEYVALUE_LEN ]; - - strcpy( InstanceVariable, epInstance->value ); - - char *ValuePos = strchr( InstanceVariable, ' ' ); - if ( !ValuePos ) - { - continue; - } - *ValuePos = 0; - ValuePos++; - - strcpy( Value, NewValue ); - if ( !V_StrSubst( Value, InstanceVariable, ValuePos, NewValue, sizeof( NewValue ), false ) ) - { - Overwritten = true; - break; - } - } - } - - if ( !Overwritten && strcmp( pPair->value, NewValue ) != 0 ) - { - free( pPair->value ); - pPair->value = copystring( NewValue ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: this function will merge in the entities from the instance into -// the main map. -// Input : pInstanceEntity - the entity of the func_instance -// Instance - the map file of the instance -// InstanceOrigin - the translation of the instance -// InstanceAngle - the rotation of the instance -// InstanceMatrix - the translation / rotation matrix of the instance -// Output : none -//----------------------------------------------------------------------------- -void CMapFile::MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) -{ - int max_entity_id = 0; - char temp[ 2048 ]; - char NameFixup[ 128 ]; - entity_t *WorldspawnEnt = NULL; - GameData::TNameFixup FixupStyle; - - char *pTargetName = ValueForKey( pInstanceEntity, "targetname" ); - char *pName = ValueForKey( pInstanceEntity, "name" ); - if ( pTargetName[ 0 ] ) - { - sprintf( NameFixup, "%s", pTargetName ); - } - else if ( pName[ 0 ] ) - { - sprintf( NameFixup, "%s", pName ); - } - else - { - sprintf( NameFixup, "InstanceAuto%d", m_InstanceCount ); - } - - for( int i = 0; i < num_entities; i++ ) - { - char *pID = ValueForKey( &entities[ i ], "hammerid" ); - if ( pID[ 0 ] ) - { - int value = atoi( pID ); - if ( value > max_entity_id ) - { - max_entity_id = value; - } - } - } - - FixupStyle = ( GameData::TNameFixup )( IntForKey( pInstanceEntity, "fixup_style" ) ); - - for( int i = 0; i < Instance->num_entities; i++ ) - { - entities[ num_entities + i ] = Instance->entities[ i ]; - - entity_t *entity = &entities[ num_entities + i ]; - entity->firstbrush += ( nummapbrushes - Instance->nummapbrushes ); - - char *pID = ValueForKey( entity, "hammerid" ); - if ( pID[ 0 ] ) - { - int value = atoi( pID ); - value += max_entity_id; - sprintf( temp, "%d", value ); - - SetKeyValue( entity, "hammerid", temp ); - } - - char *pEntity = ValueForKey( entity, "classname" ); - if ( strcmpi( pEntity, "worldspawn" ) == 0 ) - { - WorldspawnEnt = entity; - } - else - { - Vector inOrigin = entity->origin; - VectorTransform( inOrigin, InstanceMatrix, entity->origin ); - - // search for variables coming from the func_instance to replace inside of the instance - // this is done before entity fixup, so fixup may occur on the replaced value. Not sure if this is a desired order of operation yet. - for ( epair_t *ep = entity->epairs; ep != NULL; ep = ep->next ) - { - ReplaceInstancePair( ep, pInstanceEntity ); - } - -#ifdef MERGE_INSTANCE_DEBUG_INFO - Msg( "Remapping class %s\n", pEntity ); -#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO - GDclass *EntClass = GD.BeginInstanceRemap( pEntity, NameFixup, InstanceOrigin, InstanceAngle ); - if ( EntClass ) - { - for( int i = 0; i < EntClass->GetVariableCount(); i++ ) - { - GDinputvariable *EntVar = EntClass->GetVariableAt( i ); - char *pValue = ValueForKey( entity, ( char * )EntVar->GetName() ); - if ( GD.RemapKeyValue( EntVar->GetName(), pValue, temp, FixupStyle ) ) - { -#ifdef MERGE_INSTANCE_DEBUG_INFO - Msg( " %d. Remapped %s: from %s to %s\n", i, EntVar->GetName(), pValue, temp ); -#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO - SetKeyValue( entity, EntVar->GetName(), temp ); - } - else - { -#ifdef MERGE_INSTANCE_DEBUG_INFO - Msg( " %d. Ignored %s: %s\n", i, EntVar->GetName(), pValue ); -#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO - } - } - } - - if ( strcmpi( pEntity, "func_simpleladder" ) == 0 ) - { // hate having to do this, but the key values are so screwed up - AddLadderKeys( entity ); -/* Vector vInNormal, vOutNormal; - - vInNormal.x = FloatForKey( entity, "normal.x" ); - vInNormal.y = FloatForKey( entity, "normal.y" ); - vInNormal.z = FloatForKey( entity, "normal.z" ); - VectorRotate( vInNormal, InstanceMatrix, vOutNormal ); - - Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.x ); - SetKeyValue( entity, "normal.x", temp ); - - Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.y ); - SetKeyValue( entity, "normal.y", temp ); - - Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.z ); - SetKeyValue( entity, "normal.z", temp );*/ - } - } - -#ifdef MERGE_INSTANCE_DEBUG_INFO - Msg( "Instance Entity %d remapped to %d\n", i, num_entities + i ); - Msg( " FirstBrush: from %d to %d\n", Instance->entities[ i ].firstbrush, entity->firstbrush ); - Msg( " KV Pairs:\n" ); - for ( epair_t *ep = entity->epairs; ep->next != NULL; ep = ep->next ) - { - Msg( " %s %s\n", ep->key, ep->value ); - } -#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO - } - - // search for variables coming from the func_instance to replace inside of the instance - // this is done before connection fix up, so fix up may occur on the replaced value. Not sure if this is a desired order of operation yet. - for( CConnectionPairs *Connection = Instance->m_ConnectionPairs; Connection; Connection = Connection->m_Next ) - { - ReplaceInstancePair( Connection->m_Pair, pInstanceEntity ); - } - - for( CConnectionPairs *Connection = Instance->m_ConnectionPairs; Connection; Connection = Connection->m_Next ) - { - char *newValue, *oldValue; - char origValue[ 4096 ]; - int extraLen = 0; - - oldValue = Connection->m_Pair->value; - strcpy( origValue, oldValue ); - char *pos = strchr( origValue, ',' ); - if ( pos ) - { // null terminate the first field - *pos = NULL; - extraLen = strlen( pos + 1) + 1; // for the comma we just null'd - } - - if ( GD.RemapNameField( origValue, temp, FixupStyle ) ) - { - newValue = new char [ strlen( temp ) + extraLen + 1 ]; - strcpy( newValue, temp ); - if ( pos ) - { - strcat( newValue, "," ); - strcat( newValue, pos + 1 ); - } - - Connection->m_Pair->value = newValue; - delete oldValue; - } - } - - num_entities += Instance->num_entities; - - MoveBrushesToWorldGeneral( WorldspawnEnt ); - WorldspawnEnt->numbrushes = 0; - WorldspawnEnt->epairs = NULL; -} - - -//----------------------------------------------------------------------------- -// Purpose: this function will translate overlays from the instance into -// the main map. -// Input : InstanceEntityNum - the entity number of the func_instance -// Instance - the map file of the instance -// InstanceOrigin - the translation of the instance -// InstanceAngle - the rotation of the instance -// InstanceMatrix - the translation / rotation matrix of the instance -// Output : none -//----------------------------------------------------------------------------- -void CMapFile::MergeOverlays( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) -{ - for( int i = Instance->m_StartMapOverlays; i < g_aMapOverlays.Count(); i++ ) - { - Overlay_Translate( &g_aMapOverlays[ i ], InstanceOrigin, InstanceAngle, InstanceMatrix ); - } - for( int i = Instance->m_StartMapWaterOverlays; i < g_aMapWaterOverlays.Count(); i++ ) - { - Overlay_Translate( &g_aMapWaterOverlays[ i ], InstanceOrigin, InstanceAngle, InstanceMatrix ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Loads a VMF or MAP file. If the file has a .MAP extension, the MAP -// loader is used, otherwise the file is assumed to be in VMF format. -// Input : pszFileName - Full path of the map file to load. -//----------------------------------------------------------------------------- -bool LoadMapFile( const char *pszFileName ) -{ - bool bLoadingManifest = false; - CManifest *pMainManifest = NULL; - ChunkFileResult_t eResult; - - // - // Dummy this up for the texture handling. This can be removed when old .MAP file - // support is removed. - // - g_nMapFileVersion = 400; - - const char *pszExtension =V_GetFileExtension( pszFileName ); - if ( pszExtension && strcmpi( pszExtension, "vmm" ) == 0 ) - { - pMainManifest = new CManifest(); - if ( pMainManifest->LoadVMFManifest( pszFileName ) ) - { - eResult = ChunkFile_Ok; - pszFileName = pMainManifest->GetInstancePath(); - } - else - { - eResult = ChunkFile_Fail; - } - bLoadingManifest = true; - } - else - { - // - // Open the file. - // - CChunkFile File; - eResult = File.Open(pszFileName, ChunkFile_Read); - - // - // Read the file. - // - if (eResult == ChunkFile_Ok) - { - int index = g_Maps.AddToTail( new CMapFile() ); - g_LoadingMap = g_Maps[ index ]; - if ( g_MainMap == NULL ) - { - g_MainMap = g_LoadingMap; - } - - if ( g_MainMap == g_LoadingMap || verbose ) - { - Msg( "Loading %s\n", pszFileName ); - } - - - // reset the displacement info count - // nummapdispinfo = 0; - - // - // Set up handlers for the subchunks that we are interested in. - // - CChunkHandlerMap Handlers; - Handlers.AddHandler("world", (ChunkHandler_t)LoadEntityCallback, 0); - Handlers.AddHandler("entity", (ChunkHandler_t)LoadEntityCallback, 0); - - File.PushHandlers(&Handlers); - - // - // Read the sub-chunks. We ignore keys in the root of the file. - // - while (eResult == ChunkFile_Ok) - { - eResult = File.ReadChunk(); - } - - File.PopHandlers(); - } - else - { - Error("Error opening %s: %s.\n", pszFileName, File.GetErrorText(eResult)); - } - } - - if ((eResult == ChunkFile_Ok) || (eResult == ChunkFile_EOF)) - { - // Update the overlay/side list(s). - Overlay_UpdateSideLists( g_LoadingMap->m_StartMapOverlays ); - OverlayTransition_UpdateSideLists( g_LoadingMap->m_StartMapWaterOverlays ); - - g_LoadingMap->CheckForInstances( pszFileName ); - - if ( pMainManifest ) - { - pMainManifest->CordonWorld(); - } - - ClearBounds (g_LoadingMap->map_mins, g_LoadingMap->map_maxs); - for (int i=0 ; ientities[0].numbrushes ; i++) - { - // HLTOOLS: Raise map limits - if (g_LoadingMap->mapbrushes[i].mins[0] > MAX_COORD_INTEGER) - { - continue; // no valid points - } - - AddPointToBounds (g_LoadingMap->mapbrushes[i].mins, g_LoadingMap->map_mins, g_LoadingMap->map_maxs); - AddPointToBounds (g_LoadingMap->mapbrushes[i].maxs, g_LoadingMap->map_mins, g_LoadingMap->map_maxs); - } - - qprintf ("%5i brushes\n", g_LoadingMap->nummapbrushes); - qprintf ("%5i clipbrushes\n", g_LoadingMap->c_clipbrushes); - qprintf ("%5i total sides\n", g_LoadingMap->nummapbrushsides); - qprintf ("%5i boxbevels\n", g_LoadingMap->c_boxbevels); - qprintf ("%5i edgebevels\n", g_LoadingMap->c_edgebevels); - qprintf ("%5i entities\n", g_LoadingMap->num_entities); - qprintf ("%5i planes\n", g_LoadingMap->nummapplanes); - qprintf ("%5i areaportals\n", g_LoadingMap->c_areaportals); - qprintf ("size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", g_LoadingMap->map_mins[0],g_LoadingMap->map_mins[1],g_LoadingMap->map_mins[2], - g_LoadingMap->map_maxs[0],g_LoadingMap->map_maxs[1],g_LoadingMap->map_maxs[2]); - - //TestExpandBrushes(); - - // Clear the error reporting - g_MapError.ClearState(); - } - - if ( g_MainMap == g_LoadingMap ) - { - num_entities = g_MainMap->num_entities; - memcpy( entities, g_MainMap->entities, sizeof( g_MainMap->entities ) ); - } - g_LoadingMap->ForceFuncAreaPortalWindowContents(); - - return ( ( eResult == ChunkFile_Ok ) || ( eResult == ChunkFile_EOF ) ); -} - -ChunkFileResult_t LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo) -{ - return g_LoadingMap->LoadSideCallback( pFile, pSideInfo ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pFile - -// pParent - -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t CMapFile::LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo) -{ - if (nummapbrushsides == MAX_MAP_BRUSHSIDES) - { - g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); - } - - pSideInfo->pSide = &brushsides[nummapbrushsides]; - - side_t *side = pSideInfo->pSide; - mapbrush_t *b = pSideInfo->pBrush; - g_MapError.BrushSide( pSideInfo->nSideIndex++ ); - - // initialize the displacement info - pSideInfo->pSide->pMapDisp = NULL; - - // - // Set up handlers for the subchunks that we are interested in. - // - CChunkHandlerMap Handlers; - Handlers.AddHandler( "dispinfo", ( ChunkHandler_t )LoadDispInfoCallback, &side->pMapDisp ); - - // - // Read the side chunk. - // - pFile->PushHandlers(&Handlers); - ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadSideKeyCallback, pSideInfo); - pFile->PopHandlers(); - - if (eResult == ChunkFile_Ok) - { - side->contents |= pSideInfo->nBaseContents; - side->surf |= pSideInfo->nBaseFlags; - pSideInfo->td.flags |= pSideInfo->nBaseFlags; - - if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) - { - side->contents |= CONTENTS_DETAIL; - } - - if (fulldetail ) - { - side->contents &= ~CONTENTS_DETAIL; - } - - if (!(side->contents & (ALL_VISIBLE_CONTENTS | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) ) - { - side->contents |= CONTENTS_SOLID; - } - - // hints and skips are never detail, and have no content - if (side->surf & (SURF_HINT|SURF_SKIP) ) - { - side->contents = 0; - } - - // - // find the plane number - // - int planenum = PlaneFromPoints(pSideInfo->planepts[0], pSideInfo->planepts[1], pSideInfo->planepts[2]); - if (planenum != -1) - { - // - // See if the plane has been used already. - // - int k; - for ( k = 0; k < b->numsides; k++) - { - side_t *s2 = b->original_sides + k; - if (s2->planenum == planenum) - { - g_MapError.ReportWarning("duplicate plane"); - break; - } - if ( s2->planenum == (planenum^1) ) - { - g_MapError.ReportWarning("mirrored plane"); - break; - } - } - - // - // If the plane hasn't been used already, keep this side. - // - if (k == b->numsides) - { - side = b->original_sides + b->numsides; - side->planenum = planenum; - if ( !onlyents ) - { - side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], &pSideInfo->td, vec3_origin); - } - - // save the td off in case there is an origin brush and we - // have to recalculate the texinfo - if (nummapbrushsides == MAX_MAP_BRUSHSIDES) - g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); - side_brushtextures[nummapbrushsides] = pSideInfo->td; - nummapbrushsides++; - b->numsides++; - -#ifdef VSVMFIO - // Tell Maya We Have Another Side - if ( CVmfImport::GetVmfImporter() ) - { - CVmfImport::GetVmfImporter()->AddSideCallback( - b, side, pSideInfo->td, - pSideInfo->planepts[ 0 ], pSideInfo->planepts[ 1 ], pSideInfo->planepts[ 2 ] ); - } -#endif // VSVMFIO - - } - } - else - { - g_MapError.ReportWarning("plane with no normal"); - } - } - - return(eResult); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : szKey - -// szValue - -// pSideInfo - -// Output : -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadSideKeyCallback(const char *szKey, const char *szValue, LoadSide_t *pSideInfo) -{ - if (!stricmp(szKey, "plane")) - { - int nRead = sscanf(szValue, "(%f %f %f) (%f %f %f) (%f %f %f)", - &pSideInfo->planepts[0][0], &pSideInfo->planepts[0][1], &pSideInfo->planepts[0][2], - &pSideInfo->planepts[1][0], &pSideInfo->planepts[1][1], &pSideInfo->planepts[1][2], - &pSideInfo->planepts[2][0], &pSideInfo->planepts[2][1], &pSideInfo->planepts[2][2]); - - if (nRead != 9) - { - g_MapError.ReportError("parsing plane definition"); - } - } - else if (!stricmp(szKey, "material")) - { - // Get the material name. - if( g_ReplaceMaterials ) - { - szValue = ReplaceMaterialName( szValue ); - } - - strcpy(pSideInfo->td.name, szValue); - g_MapError.TextureState(szValue); - - // Find default flags and values for this material. - int mt = FindMiptex(pSideInfo->td.name); - pSideInfo->td.flags = textureref[mt].flags; - pSideInfo->td.lightmapWorldUnitsPerLuxel = textureref[mt].lightmapWorldUnitsPerLuxel; - - pSideInfo->pSide->contents = textureref[mt].contents; - pSideInfo->pSide->surf = pSideInfo->td.flags; - } - else if (!stricmp(szKey, "uaxis")) - { - int nRead = sscanf(szValue, "[%f %f %f %f] %f", &pSideInfo->td.UAxis[0], &pSideInfo->td.UAxis[1], &pSideInfo->td.UAxis[2], &pSideInfo->td.shift[0], &pSideInfo->td.textureWorldUnitsPerTexel[0]); - if (nRead != 5) - { - g_MapError.ReportError("parsing U axis definition"); - } - } - else if (!stricmp(szKey, "vaxis")) - { - int nRead = sscanf(szValue, "[%f %f %f %f] %f", &pSideInfo->td.VAxis[0], &pSideInfo->td.VAxis[1], &pSideInfo->td.VAxis[2], &pSideInfo->td.shift[1], &pSideInfo->td.textureWorldUnitsPerTexel[1]); - if (nRead != 5) - { - g_MapError.ReportError("parsing V axis definition"); - } - } - else if (!stricmp(szKey, "lightmapscale")) - { - pSideInfo->td.lightmapWorldUnitsPerLuxel = atoi(szValue); - if (pSideInfo->td.lightmapWorldUnitsPerLuxel == 0.0f) - { - g_MapError.ReportWarning("luxel size of 0"); - pSideInfo->td.lightmapWorldUnitsPerLuxel = g_defaultLuxelSize; - } - pSideInfo->td.lightmapWorldUnitsPerLuxel *= g_luxelScale; - if (pSideInfo->td.lightmapWorldUnitsPerLuxel < g_minLuxelScale) - { - pSideInfo->td.lightmapWorldUnitsPerLuxel = g_minLuxelScale; - } - } - else if (!stricmp(szKey, "contents")) - { - pSideInfo->pSide->contents |= atoi(szValue); - } - else if (!stricmp(szKey, "flags")) - { - pSideInfo->td.flags |= atoi(szValue); - pSideInfo->pSide->surf = pSideInfo->td.flags; - } - else if (!stricmp(szKey, "id")) - { - pSideInfo->pSide->id = atoi( szValue ); - } - else if (!stricmp(szKey, "smoothing_groups")) - { - pSideInfo->pSide->smoothingGroups = atoi( szValue ); - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: Reads the connections chunk of the entity. -// Input : pFile - Chunk file to load from. -// pLoadEntity - Structure to receive loaded entity information. -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadConnectionsCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity) -{ - return(pFile->ReadChunk((KeyHandler_t)LoadConnectionsKeyCallback, pLoadEntity)); -} - - -//----------------------------------------------------------------------------- -// Purpose: Parses a key/value pair from the entity connections chunk. -// Input : szKey - Key indicating the name of the entity output. -// szValue - Comma delimited fields in the following format: -// ,,,, -// pLoadEntity - Structure to receive loaded entity information. -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity) -{ - return g_LoadingMap->LoadConnectionsKeyCallback( szKey, szValue, pLoadEntity ); -} - -ChunkFileResult_t CMapFile::LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity) -{ - // - // Create new input and fill it out. - // - epair_t *pOutput = new epair_t; - - pOutput->key = new char [strlen(szKey) + 1]; - pOutput->value = new char [strlen(szValue) + 1]; - - strcpy(pOutput->key, szKey); - strcpy(pOutput->value, szValue); - - m_ConnectionPairs = new CConnectionPairs( pOutput, m_ConnectionPairs ); - - // - // Append it to the end of epairs list. - // - pOutput->next = NULL; - - if (!pLoadEntity->pEntity->epairs) - { - pLoadEntity->pEntity->epairs = pOutput; - } - else - { - epair_t *ep; - for ( ep = pLoadEntity->pEntity->epairs; ep->next != NULL; ep = ep->next ) - { - } - ep->next = pOutput; - } - - return(ChunkFile_Ok); -} - - -ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity) -{ - return g_LoadingMap->LoadSolidCallback( pFile, pLoadEntity ); -}; - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pFile - -// pParent - -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t CMapFile::LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity) -{ - if (nummapbrushes == MAX_MAP_BRUSHES) - { - g_MapError.ReportError ("nummapbrushes == MAX_MAP_BRUSHES"); - } - - mapbrush_t *b = &mapbrushes[nummapbrushes]; - b->original_sides = &brushsides[nummapbrushsides]; - b->entitynum = num_entities-1; - b->brushnum = nummapbrushes - pLoadEntity->pEntity->firstbrush; - - LoadSide_t SideInfo; - SideInfo.pBrush = b; - SideInfo.nSideIndex = 0; - SideInfo.nBaseContents = pLoadEntity->nBaseContents; - SideInfo.nBaseFlags = pLoadEntity->nBaseFlags; - - // - // Set up handlers for the subchunks that we are interested in. - // - CChunkHandlerMap Handlers; - Handlers.AddHandler("side", (ChunkHandler_t)::LoadSideCallback, &SideInfo); - - // - // Read the solid chunk. - // - pFile->PushHandlers(&Handlers); - ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadSolidKeyCallback, b); - pFile->PopHandlers(); - - if (eResult == ChunkFile_Ok) - { - // get the content for the entire brush - b->contents = BrushContents (b); - - // allow detail brushes to be removed - if (nodetail && (b->contents & CONTENTS_DETAIL) && !HasDispInfo( b ) ) - { - b->numsides = 0; - return(ChunkFile_Ok); - } - - // allow water brushes to be removed - if (nowater && (b->contents & MASK_WATER) ) - { - b->numsides = 0; - return(ChunkFile_Ok); - } - - // create windings for sides and bounds for brush - MakeBrushWindings (b); - - // - // brushes that will not be visible at all will never be - // used as bsp splitters - // - // only do this on the world entity - // - if ( b->entitynum == 0 ) - { - if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) - { - if ( g_ClipTexinfo < 0 ) - { - g_ClipTexinfo = b->original_sides[0].texinfo; - } - c_clipbrushes++; - for (int i=0 ; inumsides ; i++) - { - b->original_sides[i].texinfo = TEXINFO_NODE; - } - } - } - - // - // origin brushes are removed, but they set - // the rotation origin for the rest of the brushes - // in the entity. After the entire entity is parsed, - // the planenums and texinfos will be adjusted for - // the origin brush - // - if (b->contents & CONTENTS_ORIGIN) - { - char string[32]; - Vector origin; - - if (num_entities == 1) - { - Error("Brush %i: origin brushes not allowed in world", b->id); - } - - VectorAdd (b->mins, b->maxs, origin); - VectorScale (origin, 0.5, origin); - - sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); - SetKeyValue (&entities[b->entitynum], "origin", string); - - VectorCopy (origin, entities[b->entitynum].origin); - - // don't keep this brush - b->numsides = 0; - - return(ChunkFile_Ok); - } - -#ifdef VSVMFIO - if ( CVmfImport::GetVmfImporter() ) - { - CVmfImport::GetVmfImporter()->MapBrushToMayaCallback( b ); - } -#endif // VSVMFIO - - // - // find a map brushes with displacement surfaces and remove them from the "world" - // - if( HasDispInfo( b ) ) - { - // add the base face data to the displacement surface - DispGetFaceInfo( b ); - - // don't keep this brush - b->numsides = 0; - - return( ChunkFile_Ok ); - } - - AddBrushBevels (b); - - nummapbrushes++; - pLoadEntity->pEntity->numbrushes++; - } - else - { - return eResult; - } - - return(ChunkFile_Ok); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : pFile - -// parent - -// Output : ChunkFileResult_t -//----------------------------------------------------------------------------- -ChunkFileResult_t LoadSolidKeyCallback(const char *szKey, const char *szValue, mapbrush_t *pLoadBrush) -{ - if (!stricmp(szKey, "id")) - { - pLoadBrush->id = atoi(szValue); - g_MapError.BrushState(pLoadBrush->id); - } - - return ChunkFile_Ok; -} - - -/* -================ -TestExpandBrushes - -Expands all the brush planes and saves a new map out -================ -*/ -void CMapFile::TestExpandBrushes (void) -{ - FILE *f; - side_t *s; - int i, j, bn; - winding_t *w; - char *name = "expanded.map"; - mapbrush_t *brush; - vec_t dist; - - Msg ("writing %s\n", name); - f = fopen (name, "wb"); - if (!f) - Error ("Can't write %s\b", name); - - fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); - fprintf( f, "\"mapversion\" \"220\"\n\"sounds\" \"1\"\n\"MaxRange\" \"4096\"\n\"mapversion\" \"220\"\n\"wad\" \"vert.wad;dev.wad;generic.wad;spire.wad;urb.wad;cit.wad;water.wad\"\n" ); - - - for (bn=0 ; bnnumsides ; i++) - { - s = brush->original_sides + i; - dist = mapplanes[s->planenum].dist; - for (j=0 ; j<3 ; j++) - dist += fabs( 16 * mapplanes[s->planenum].normal[j] ); - - w = BaseWindingForPlane (mapplanes[s->planenum].normal, dist); - - fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); - fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); - fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); - - fprintf (f, "%s [ 0 0 1 -512 ] [ 0 -1 0 -256 ] 0 1 1 \n", - TexDataStringTable_GetString( GetTexData( texinfo[s->texinfo].texdata )->nameStringTableID ) ); - - FreeWinding (w); - } - fprintf (f, "}\n"); - } - fprintf (f, "}\n"); - - fclose (f); - - Error ("can't proceed after expanding brushes"); -} - - -//----------------------------------------------------------------------------- -// Purpose: load in the displacement info "chunk" from the .map file into the -// vbsp map displacement info data structure -// Output: return the pointer to the displacement map -//----------------------------------------------------------------------------- -mapdispinfo_t *ParseDispInfoChunk( void ) -{ - int i, j; - int vertCount; - mapdispinfo_t *pMapDispInfo; - - // - // check to see if we exceeded the maximum displacement info list size - // - if( nummapdispinfo > MAX_MAP_DISPINFO ) - g_MapError.ReportError( "ParseDispInfoChunk: nummapdispinfo > MAX_MAP_DISPINFO"); - - // get a pointer to the next available displacement info slot - pMapDispInfo = &mapdispinfo[nummapdispinfo]; - nummapdispinfo++; - - // - // get the chunk opener - "{" - // - GetToken( false ); - if( strcmp( token, "{" ) ) - g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - {" ); - - // - // - // get the displacement info attribs - // - // - - // power - GetToken( true ); - pMapDispInfo->power = atoi( token ); - - // u and v mapping axes - for( i = 0; i < 2; i++ ) - { - GetToken( false ); - if( strcmp( token, "[" ) ) - g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - [" ); - - for( j = 0; j < 3; j++ ) - { - GetToken( false ); - - if( i == 0 ) - { - pMapDispInfo->uAxis[j] = atof( token ); - } - else - { - pMapDispInfo->vAxis[j] = atof( token ); - } - } - - GetToken( false ); - if( strcmp( token, "]" ) ) - g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - ]" ); - } - - // max displacement value - if( g_nMapFileVersion < 350 ) - { - GetToken( false ); - pMapDispInfo->maxDispDist = atof( token ); - } - - // minimum tesselation value - GetToken( false ); - pMapDispInfo->minTess = atoi( token ); - - // light smoothing angle - GetToken( false ); - pMapDispInfo->smoothingAngle = atof( token ); - - // - // get the displacement info displacement normals - // - GetToken( true ); - pMapDispInfo->vectorDisps[0][0] = atof( token ); - GetToken( false ); - pMapDispInfo->vectorDisps[0][1] = atof( token ); - GetToken( false ); - pMapDispInfo->vectorDisps[0][2] = atof( token ); - - vertCount = ( ( ( 1 << pMapDispInfo->power ) + 1 ) * ( ( 1 << pMapDispInfo->power ) + 1 ) ); - for( i = 1; i < vertCount; i++ ) - { - GetToken( false ); - pMapDispInfo->vectorDisps[i][0] = atof( token ); - GetToken( false ); - pMapDispInfo->vectorDisps[i][1] = atof( token ); - GetToken( false ); - pMapDispInfo->vectorDisps[i][2] = atof( token ); - } - - // - // get the displacement info displacement values - // - GetToken( true ); - pMapDispInfo->dispDists[0] = atof( token ); - - for( i = 1; i < vertCount; i++ ) - { - GetToken( false ); - pMapDispInfo->dispDists[i] = atof( token ); - } - - // - // get the chunk closer - "}" - // - GetToken( true ); - if( strcmp( token, "}" ) ) - g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - }" ); - - // return the index of the displacement info slot - return pMapDispInfo; -} - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "map_shared.h" +#include "disp_vbsp.h" +#include "tier1/strtools.h" +#include "builddisp.h" +#include "tier0/icommandline.h" +#include "KeyValues.h" +#include "materialsub.h" +#include "fgdlib/fgdlib.h" +#include "manifest.h" + +#ifdef VSVMFIO +#include "VmfImport.h" +#endif // VSVMFIO + + +// undefine to make plane finding use linear sort +#define USE_HASHING + +#define RENDER_NORMAL_EPSILON 0.00001 +#define RENDER_DIST_EPSILON 0.01f + +#define BRUSH_CLIP_EPSILON 0.01f // this should probably be the same + // as clip epsilon, but it is 0.1f and I + // currently don't know how that number was + // come to (cab) - this is 0.01 of an inch + // for clipping brush solids +struct LoadSide_t +{ + mapbrush_t *pBrush; + side_t *pSide; + int nSideIndex; + int nBaseFlags; + int nBaseContents; + Vector planepts[3]; + brush_texture_t td; +}; + + +extern qboolean onlyents; + + +CUtlVector< CMapFile * > g_Maps; +CMapFile *g_MainMap = NULL; +CMapFile *g_LoadingMap = NULL; + +char CMapFile::m_InstancePath[ MAX_PATH ] = ""; +int CMapFile::m_InstanceCount = 0; +int CMapFile::c_areaportals = 0; + +void CMapFile::Init( void ) +{ + entity_num = 0; + num_entities = 0; + + nummapplanes = 0; + memset( mapplanes, 0, sizeof( mapplanes ) ); + + nummapbrushes = 0; + memset( mapbrushes, 0, sizeof( mapbrushes ) ); + + nummapbrushsides = 0; + memset( brushsides, 0, sizeof( brushsides ) ); + + memset( side_brushtextures, 0, sizeof( side_brushtextures ) ); + + memset( planehash, 0, sizeof( planehash ) ); + + m_ConnectionPairs = NULL; + + m_StartMapOverlays = g_aMapOverlays.Count(); + m_StartMapWaterOverlays = g_aMapWaterOverlays.Count(); + + c_boxbevels = 0; + c_edgebevels = 0; + c_clipbrushes = 0; + g_ClipTexinfo = -1; +} + + +// All the brush sides referenced by info_no_dynamic_shadow entities. +CUtlVector g_NoDynamicShadowSides; + + +void TestExpandBrushes (void); + +ChunkFileResult_t LoadDispDistancesCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispDistancesKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispInfoCallback(CChunkFile *pFile, mapdispinfo_t **ppMapDispInfo ); +ChunkFileResult_t LoadDispInfoKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispOffsetsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispOffsetsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispAlphasCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispAlphasKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispTriangleTagsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispTriangleTagsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); + +#ifdef VSVMFIO +ChunkFileResult_t LoadDispOffsetNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispOffsetNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +#endif // VSVMFIO + +ChunkFileResult_t LoadEntityCallback(CChunkFile *pFile, int nParam); +ChunkFileResult_t LoadEntityKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity); + +ChunkFileResult_t LoadConnectionsCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); +ChunkFileResult_t LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity); + +ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); +ChunkFileResult_t LoadSolidKeyCallback(const char *szKey, const char *szValue, mapbrush_t *pLoadBrush); + +ChunkFileResult_t LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo); +ChunkFileResult_t LoadSideKeyCallback(const char *szKey, const char *szValue, LoadSide_t *pSideInfo); + + + +/* +============================================================================= + +PLANE FINDING + +============================================================================= +*/ + + +/* +================= +PlaneTypeForNormal +================= +*/ +int PlaneTypeForNormal (Vector& normal) +{ + vec_t ax, ay, az; + +// NOTE: should these have an epsilon around 1.0? + if (normal[0] == 1.0 || normal[0] == -1.0) + return PLANE_X; + if (normal[1] == 1.0 || normal[1] == -1.0) + return PLANE_Y; + if (normal[2] == 1.0 || normal[2] == -1.0) + return PLANE_Z; + + ax = fabs(normal[0]); + ay = fabs(normal[1]); + az = fabs(normal[2]); + + if (ax >= ay && ax >= az) + return PLANE_ANYX; + if (ay >= ax && ay >= az) + return PLANE_ANYY; + return PLANE_ANYZ; +} + +/* +================ +PlaneEqual +================ +*/ +qboolean PlaneEqual (plane_t *p, Vector& normal, vec_t dist, float normalEpsilon, float distEpsilon) +{ +#if 1 + if ( + fabs(p->normal[0] - normal[0]) < normalEpsilon + && fabs(p->normal[1] - normal[1]) < normalEpsilon + && fabs(p->normal[2] - normal[2]) < normalEpsilon + && fabs(p->dist - dist) < distEpsilon ) + return true; +#else + if (p->normal[0] == normal[0] + && p->normal[1] == normal[1] + && p->normal[2] == normal[2] + && p->dist == dist) + return true; +#endif + return false; +} + +/* +================ +AddPlaneToHash +================ +*/ +void CMapFile::AddPlaneToHash (plane_t *p) +{ + int hash; + + hash = (int)fabs(p->dist) / 8; + hash &= (PLANE_HASHES-1); + + p->hash_chain = planehash[hash]; + planehash[hash] = p; +} + +/* +================ +CreateNewFloatPlane +================ +*/ +int CMapFile::CreateNewFloatPlane (Vector& normal, vec_t dist) +{ + plane_t *p, temp; + + if (VectorLength(normal) < 0.5) + g_MapError.ReportError ("FloatPlane: bad normal"); + // create a new plane + if (nummapplanes+2 > MAX_MAP_PLANES) + g_MapError.ReportError ("MAX_MAP_PLANES"); + + p = &mapplanes[nummapplanes]; + VectorCopy (normal, p->normal); + p->dist = dist; + p->type = (p+1)->type = PlaneTypeForNormal (p->normal); + + VectorSubtract (vec3_origin, normal, (p+1)->normal); + (p+1)->dist = -dist; + + nummapplanes += 2; + + // allways put axial planes facing positive first + if (p->type < 3) + { + if (p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0) + { + // flip order + temp = *p; + *p = *(p+1); + *(p+1) = temp; + + AddPlaneToHash (p); + AddPlaneToHash (p+1); + return nummapplanes - 1; + } + } + + AddPlaneToHash (p); + AddPlaneToHash (p+1); + return nummapplanes - 2; +} + + +/* +============== +SnapVector +============== +*/ +bool SnapVector (Vector& normal) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if ( fabs(normal[i] - 1) < RENDER_NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = 1; + return true; + } + + if ( fabs(normal[i] - -1) < RENDER_NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = -1; + return true; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Snaps normal to axis-aligned if it is within an epsilon of axial. +// Rounds dist to integer if it is within an epsilon of integer. +// Input : normal - Plane normal vector (assumed to be unit length). +// dist - Plane constant. +//----------------------------------------------------------------------------- +void SnapPlane(Vector &normal, vec_t &dist) +{ + SnapVector(normal); + + if (fabs(dist - RoundInt(dist)) < RENDER_DIST_EPSILON) + { + dist = RoundInt(dist); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Snaps normal to axis-aligned if it is within an epsilon of axial. +// Recalculates dist if the normal was snapped. Rounds dist to integer +// if it is within an epsilon of integer. +// Input : normal - Plane normal vector (assumed to be unit length). +// dist - Plane constant. +// p0, p1, p2 - Three points on the plane. +//----------------------------------------------------------------------------- +void SnapPlane(Vector &normal, vec_t &dist, const Vector &p0, const Vector &p1, const Vector &p2) +{ + if (SnapVector(normal)) + { + // + // Calculate a new plane constant using the snapped normal. Use the + // centroid of the three plane points to minimize error. This is like + // rotating the plane around the centroid. + // + Vector p3 = (p0 + p1 + p2) / 3.0f; + dist = normal.Dot(p3); + if ( g_snapAxialPlanes ) + { + dist = RoundInt(dist); + } + } + + if (fabs(dist - RoundInt(dist)) < RENDER_DIST_EPSILON) + { + dist = RoundInt(dist); + } +} + + +/* +============= +FindFloatPlane + +============= +*/ +#ifndef USE_HASHING +int CMapFile::FindFloatPlane (Vector& normal, vec_t dist) +{ + int i; + plane_t *p; + + SnapPlane(normal, dist); + for (i=0, p=mapplanes ; ihash_chain) + { + if (PlaneEqual (p, normal, dist, RENDER_NORMAL_EPSILON, RENDER_DIST_EPSILON)) + return p-mapplanes; + } + } + + return CreateNewFloatPlane (normal, dist); +} +#endif + + +//----------------------------------------------------------------------------- +// Purpose: Builds a plane normal and distance from three points on the plane. +// If the normal is nearly axial, it will be snapped to be axial. Looks +// up the plane in the unique planes. +// Input : p0, p1, p2 - Three points on the plane. +// Output : Returns the index of the plane in the planes list. +//----------------------------------------------------------------------------- +int CMapFile::PlaneFromPoints(const Vector &p0, const Vector &p1, const Vector &p2) +{ + Vector t1, t2, normal; + vec_t dist; + + VectorSubtract (p0, p1, t1); + VectorSubtract (p2, p1, t2); + CrossProduct (t1, t2, normal); + VectorNormalize (normal); + + dist = DotProduct (p0, normal); + + SnapPlane(normal, dist, p0, p1, p2); + + return FindFloatPlane (normal, dist); +} + + +/* +=========== +BrushContents +=========== +*/ +int BrushContents (mapbrush_t *b) +{ + int contents; + int unionContents = 0; + side_t *s; + int i; + + s = &b->original_sides[0]; + contents = s->contents; + unionContents = contents; + for (i=1 ; inumsides ; i++, s++) + { + s = &b->original_sides[i]; + + unionContents |= s->contents; +#if 0 + if (s->contents != contents) + { + Msg("Brush %i: mixed face contents\n", b->id); + break; + } +#endif + } + + // NOTE: we're making slime translucent so that it doesn't block lighting on things floating on its surface + int transparentContents = unionContents & (CONTENTS_WINDOW|CONTENTS_GRATE|CONTENTS_WATER|CONTENTS_SLIME); + if ( transparentContents ) + { + contents |= transparentContents | CONTENTS_TRANSLUCENT; + contents &= ~CONTENTS_SOLID; + } + + return contents; +} + + +//============================================================================ + +bool IsAreaPortal( char const *pClassName ) +{ + // If the class name starts with "func_areaportal", then it's considered an area portal. + char const *pBaseName = "func_areaportal"; + char const *pCur = pBaseName; + while( *pCur && *pClassName ) + { + if( *pCur != *pClassName ) + break; + + ++pCur; + ++pClassName; + } + + return *pCur == 0; +} + + +/* +================= +AddBrushBevels + +Adds any additional planes necessary to allow the brush to be expanded +against axial bounding boxes +================= +*/ +void CMapFile::AddBrushBevels (mapbrush_t *b) +{ + int axis, dir; + int i, j, k, l, order; + side_t sidetemp; + brush_texture_t tdtemp; + side_t *s, *s2; + Vector normal; + float dist; + winding_t *w, *w2; + Vector vec, vec2; + float d; + + // + // add the axial planes + // + order = 0; + for (axis=0 ; axis <3 ; axis++) + { + for (dir=-1 ; dir <= 1 ; dir+=2, order++) + { + // see if the plane is allready present + for (i=0, s=b->original_sides ; inumsides ; i++,s++) + { + if (mapplanes[s->planenum].normal[axis] == dir) + break; + } + + if (i == b->numsides) + { // add a new side + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); + nummapbrushsides++; + b->numsides++; + VectorClear (normal); + normal[axis] = dir; + if (dir == 1) + dist = b->maxs[axis]; + else + dist = -b->mins[axis]; + s->planenum = FindFloatPlane (normal, dist); + s->texinfo = b->original_sides[0].texinfo; + s->contents = b->original_sides[0].contents; + s->bevel = true; + c_boxbevels++; + } + + // if the plane is not in it canonical order, swap it + if (i != order) + { + sidetemp = b->original_sides[order]; + b->original_sides[order] = b->original_sides[i]; + b->original_sides[i] = sidetemp; + + j = b->original_sides - brushsides; + tdtemp = side_brushtextures[j+order]; + side_brushtextures[j+order] = side_brushtextures[j+i]; + side_brushtextures[j+i] = tdtemp; + } + } + } + + // + // add the edge bevels + // + if (b->numsides == 6) + return; // pure axial + + // test the non-axial plane edges + for (i=6 ; inumsides ; i++) + { + s = b->original_sides + i; + w = s->winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + k = (j+1)%w->numpoints; + VectorSubtract (w->p[j], w->p[k], vec); + if (VectorNormalize (vec) < 0.5) + continue; + SnapVector (vec); + for (k=0 ; k<3 ; k++) + if ( vec[k] == -1 || vec[k] == 1) + break; // axial + if (k != 3) + continue; // only test non-axial edges + + // try the six possible slanted axials from this edge + for (axis=0 ; axis <3 ; axis++) + { + for (dir=-1 ; dir <= 1 ; dir+=2) + { + // construct a plane + VectorClear (vec2); + vec2[axis] = dir; + CrossProduct (vec, vec2, normal); + if (VectorNormalize (normal) < 0.5) + continue; + dist = DotProduct (w->p[j], normal); + + // if all the points on all the sides are + // behind this plane, it is a proper edge bevel + for (k=0 ; knumsides ; k++) + { + // if this plane has allready been used, skip it + // NOTE: Use a larger tolerance for collision planes than for rendering planes + if ( PlaneEqual(&mapplanes[b->original_sides[k].planenum], normal, dist, 0.01f, 0.01f ) ) + break; + + w2 = b->original_sides[k].winding; + if (!w2) + continue; + for (l=0 ; lnumpoints ; l++) + { + d = DotProduct (w2->p[l], normal) - dist; + if (d > 0.1) + break; // point in front + } + if (l != w2->numpoints) + break; + } + + if (k != b->numsides) + continue; // wasn't part of the outer hull + // add this plane + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); + nummapbrushsides++; + s2 = &b->original_sides[b->numsides]; + s2->planenum = FindFloatPlane (normal, dist); + s2->texinfo = b->original_sides[0].texinfo; + s2->contents = b->original_sides[0].contents; + s2->bevel = true; + c_edgebevels++; + b->numsides++; + } + } + } + } +} + +/* +================ +MakeBrushWindings + +makes basewindigs for sides and mins / maxs for the brush +================ +*/ +qboolean CMapFile::MakeBrushWindings (mapbrush_t *ob) +{ + int i, j; + winding_t *w; + side_t *side; + plane_t *plane; + + ClearBounds (ob->mins, ob->maxs); + + for (i=0 ; inumsides ; i++) + { + plane = &mapplanes[ob->original_sides[i].planenum]; + w = BaseWindingForPlane (plane->normal, plane->dist); + for (j=0 ; jnumsides && w; j++) + { + if (i == j) + continue; + if (ob->original_sides[j].bevel) + continue; + plane = &mapplanes[ob->original_sides[j].planenum^1]; +// ChopWindingInPlace (&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); + // adding an epsilon here, due to precision issues creating complex + // displacement surfaces (cab) + ChopWindingInPlace( &w, plane->normal, plane->dist, BRUSH_CLIP_EPSILON ); + } + + side = &ob->original_sides[i]; + side->winding = w; + if (w) + { + side->visible = true; + for (j=0 ; jnumpoints ; j++) + AddPointToBounds (w->p[j], ob->mins, ob->maxs); + } + } + + for (i=0 ; i<3 ; i++) + { + if (ob->mins[i] < MIN_COORD_INTEGER || ob->maxs[i] > MAX_COORD_INTEGER) + Msg("Brush %i: bounds out of range\n", ob->id); + if (ob->mins[i] > MAX_COORD_INTEGER || ob->maxs[i] < MIN_COORD_INTEGER) + Msg("Brush %i: no visible sides on brush\n", ob->id); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Takes all of the brushes from the current entity and adds them to the +// world's brush list. Used by func_detail and func_areaportal. +// THIS ROUTINE MAY ONLY BE USED DURING ENTITY LOADING. +// Input : mapent - Entity whose brushes are to be moved to the world. +//----------------------------------------------------------------------------- +void CMapFile::MoveBrushesToWorld( entity_t *mapent ) +{ + int newbrushes; + int worldbrushes; + mapbrush_t *temp; + int i; + + // this is pretty gross, because the brushes are expected to be + // in linear order for each entity + + newbrushes = mapent->numbrushes; + worldbrushes = entities[0].numbrushes; + + temp = (mapbrush_t *)malloc(newbrushes*sizeof(mapbrush_t)); + memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); + +#if 0 // let them keep their original brush numbers + for (i=0 ; inumbrushes = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Takes all of the brushes from the current entity and adds them to the +// world's brush list. Used by func_detail and func_areaportal. +// Input : mapent - Entity whose brushes are to be moved to the world. +//----------------------------------------------------------------------------- +void CMapFile::MoveBrushesToWorldGeneral( entity_t *mapent ) +{ + int newbrushes; + int worldbrushes; + mapbrush_t *temp; + int i; + + for( i = 0; i < nummapdispinfo; i++ ) + { + if ( mapdispinfo[ i ].entitynum == ( mapent - entities ) ) + { + mapdispinfo[ i ].entitynum = 0; + } + } + + // this is pretty gross, because the brushes are expected to be + // in linear order for each entity + newbrushes = mapent->numbrushes; + worldbrushes = entities[0].numbrushes; + + temp = (mapbrush_t *)malloc(newbrushes*sizeof(mapbrush_t)); + memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); + +#if 0 // let them keep their original brush numbers + for (i=0 ; ifirstbrush - worldbrushes) ); + + + // wwwxxxmmyyy + + // copy the new brushes down + memcpy (mapbrushes + worldbrushes, temp, sizeof(mapbrush_t) * newbrushes); + + // fix up indexes + entities[0].numbrushes += newbrushes; + for (i=1 ; ifirstbrush ) // if we use <=, then we'll remap the passed in ent, which we don't want to + { + entities[ i ].firstbrush += newbrushes; + } + } + free (temp); + + mapent->numbrushes = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Iterates the sides of brush and removed CONTENTS_DETAIL from each side +// Input : *brush - +//----------------------------------------------------------------------------- +void RemoveContentsDetailFromBrush( mapbrush_t *brush ) +{ + // Only valid on non-world brushes + Assert( brush->entitynum != 0 ); + + side_t *s; + int i; + + s = &brush->original_sides[0]; + for ( i=0 ; inumsides ; i++, s++ ) + { + if ( s->contents & CONTENTS_DETAIL ) + { + s->contents &= ~CONTENTS_DETAIL; + } + } + +} + +//----------------------------------------------------------------------------- +// Purpose: Iterates all brushes in an entity and removes CONTENTS_DETAIL from all brushes +// Input : *mapent - +//----------------------------------------------------------------------------- +void CMapFile::RemoveContentsDetailFromEntity( entity_t *mapent ) +{ + int i; + for ( i = 0; i < mapent->numbrushes; i++ ) + { + int brushnum = mapent->firstbrush + i; + + mapbrush_t *brush = &mapbrushes[ brushnum ]; + RemoveContentsDetailFromBrush( brush ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFile - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispDistancesCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispDistancesKeyCallback, pMapDispInfo)); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : szKey - +// szValue - +// pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispDistancesKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!strnicmp(szKey, "row", 3)) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy(szBuf, szValue); + + int nCols = (1 << pMapDispInfo->power) + 1; + int nRow = atoi(&szKey[3]); + + char *pszNext = strtok(szBuf, " "); + int nIndex = nRow * nCols; + + while (pszNext != NULL) + { + pMapDispInfo->dispDists[nIndex] = (float)atof(pszNext); + pszNext = strtok(NULL, " "); + nIndex++; + } + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: load in the displacement info "chunk" from the .map file into the +// vbsp map displacement info data structure +// Output : return the index of the map displacement info +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispInfoCallback(CChunkFile *pFile, mapdispinfo_t **ppMapDispInfo ) +{ + // + // check to see if we exceeded the maximum displacement info list size + // + if (nummapdispinfo > MAX_MAP_DISPINFO) + { + g_MapError.ReportError( "ParseDispInfoChunk: nummapdispinfo > MAX_MAP_DISPINFO" ); + } + + // get a pointer to the next available displacement info slot + mapdispinfo_t *pMapDispInfo = &mapdispinfo[nummapdispinfo]; + nummapdispinfo++; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler("normals", (ChunkHandler_t)LoadDispNormalsCallback, pMapDispInfo); + Handlers.AddHandler("distances", (ChunkHandler_t)LoadDispDistancesCallback, pMapDispInfo); + Handlers.AddHandler("offsets", (ChunkHandler_t)LoadDispOffsetsCallback, pMapDispInfo); + Handlers.AddHandler("alphas", (ChunkHandler_t)LoadDispAlphasCallback, pMapDispInfo); + Handlers.AddHandler("triangle_tags", (ChunkHandler_t)LoadDispTriangleTagsCallback, pMapDispInfo); + +#ifdef VSVMFIO + Handlers.AddHandler("offset_normals", (ChunkHandler_t)LoadDispOffsetNormalsCallback, pMapDispInfo); +#endif // VSVMFIO + + // + // Read the displacement chunk. + // + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadDispInfoKeyCallback, pMapDispInfo); + pFile->PopHandlers(); + + if (eResult == ChunkFile_Ok) + { + // return a pointer to the displacement info + *ppMapDispInfo = pMapDispInfo; + } + + return(eResult); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *mapent - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispInfoKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!stricmp(szKey, "power")) + { + CChunkFile::ReadKeyValueInt(szValue, pMapDispInfo->power); + } +#ifdef VSVMFIO + else if (!stricmp(szKey, "elevation")) + { + CChunkFile::ReadKeyValueFloat(szValue, pMapDispInfo->m_elevation); + } +#endif // VSVMFIO + else if (!stricmp(szKey, "uaxis")) + { + CChunkFile::ReadKeyValueVector3(szValue, pMapDispInfo->uAxis); + } + else if (!stricmp(szKey, "vaxis")) + { + CChunkFile::ReadKeyValueVector3(szValue, pMapDispInfo->vAxis); + } + else if( !stricmp( szKey, "startposition" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pMapDispInfo->startPosition ); + } + else if( !stricmp( szKey, "flags" ) ) + { + CChunkFile::ReadKeyValueInt( szValue, pMapDispInfo->flags ); + } +#if 0 // old data + else if (!stricmp( szKey, "alpha" ) ) + { + CChunkFile::ReadKeyValueVector4( szValue, pMapDispInfo->alphaValues ); + } +#endif + else if (!stricmp(szKey, "mintess")) + { + CChunkFile::ReadKeyValueInt(szValue, pMapDispInfo->minTess); + } + else if (!stricmp(szKey, "smooth")) + { + CChunkFile::ReadKeyValueFloat(szValue, pMapDispInfo->smoothingAngle); + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFile - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispNormalsKeyCallback, pMapDispInfo)); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!strnicmp(szKey, "row", 3)) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy(szBuf, szValue); + + int nCols = (1 << pMapDispInfo->power) + 1; + int nRow = atoi(&szKey[3]); + + char *pszNext0 = strtok(szBuf, " "); + char *pszNext1 = strtok(NULL, " "); + char *pszNext2 = strtok(NULL, " "); + + int nIndex = nRow * nCols; + + while ((pszNext0 != NULL) && (pszNext1 != NULL) && (pszNext2 != NULL)) + { + pMapDispInfo->vectorDisps[nIndex][0] = (float)atof(pszNext0); + pMapDispInfo->vectorDisps[nIndex][1] = (float)atof(pszNext1); + pMapDispInfo->vectorDisps[nIndex][2] = (float)atof(pszNext2); + + pszNext0 = strtok(NULL, " "); + pszNext1 = strtok(NULL, " "); + pszNext2 = strtok(NULL, " "); + + nIndex++; + } + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispOffsetsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispOffsetsKeyCallback, pMapDispInfo)); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispOffsetsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!strnicmp(szKey, "row", 3)) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy(szBuf, szValue); + + int nCols = (1 << pMapDispInfo->power) + 1; + int nRow = atoi(&szKey[3]); + + char *pszNext0 = strtok(szBuf, " "); + char *pszNext1 = strtok(NULL, " "); + char *pszNext2 = strtok(NULL, " "); + + int nIndex = nRow * nCols; + + while ((pszNext0 != NULL) && (pszNext1 != NULL) && (pszNext2 != NULL)) + { + pMapDispInfo->vectorOffsets[nIndex][0] = (float)atof(pszNext0); + pMapDispInfo->vectorOffsets[nIndex][1] = (float)atof(pszNext1); + pMapDispInfo->vectorOffsets[nIndex][2] = (float)atof(pszNext2); + + pszNext0 = strtok(NULL, " "); + pszNext1 = strtok(NULL, " "); + pszNext2 = strtok(NULL, " "); + + nIndex++; + } + } + + return(ChunkFile_Ok); +} + + +#ifdef VSVMFIO +ChunkFileResult_t LoadDispOffsetNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispOffsetNormalsKeyCallback, pMapDispInfo)); +} + + +ChunkFileResult_t LoadDispOffsetNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!strnicmp(szKey, "row", 3)) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy(szBuf, szValue); + + int nCols = (1 << pMapDispInfo->power) + 1; + int nRow = atoi(&szKey[3]); + + char *pszNext0 = strtok(szBuf, " "); + char *pszNext1 = strtok(NULL, " "); + char *pszNext2 = strtok(NULL, " "); + + int nIndex = nRow * nCols; + + while ((pszNext0 != NULL) && (pszNext1 != NULL) && (pszNext2 != NULL)) + { + pMapDispInfo->m_offsetNormals[nIndex][0] = (float)atof(pszNext0); + pMapDispInfo->m_offsetNormals[nIndex][1] = (float)atof(pszNext1); + pMapDispInfo->m_offsetNormals[nIndex][2] = (float)atof(pszNext2); + + pszNext0 = strtok(NULL, " "); + pszNext1 = strtok(NULL, " "); + pszNext2 = strtok(NULL, " "); + + nIndex++; + } + } + + return(ChunkFile_Ok); +} +#endif // VSVMFIO + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispAlphasCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispAlphasKeyCallback, pMapDispInfo)); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispAlphasKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!strnicmp(szKey, "row", 3)) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy(szBuf, szValue); + + int nCols = (1 << pMapDispInfo->power) + 1; + int nRow = atoi(&szKey[3]); + + char *pszNext0 = strtok(szBuf, " "); + + int nIndex = nRow * nCols; + + while (pszNext0 != NULL) + { + pMapDispInfo->alphaValues[nIndex] = (float)atof(pszNext0); + pszNext0 = strtok(NULL, " "); + nIndex++; + } + } + + return(ChunkFile_Ok); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispTriangleTagsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispTriangleTagsKeyCallback, pMapDispInfo)); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispTriangleTagsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if ( !strnicmp( szKey, "row", 3 ) ) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy( szBuf, szValue ); + + int nCols = ( 1 << pMapDispInfo->power ); + int nRow = atoi( &szKey[3] ); + + char *pszNext = strtok( szBuf, " " ); + + int nIndex = nRow * nCols; + int iTri = nIndex * 2; + + while ( pszNext != NULL ) + { + // Collapse the tags here! + unsigned short nTriTags = ( unsigned short )atoi( pszNext ); + + // Walkable + bool bWalkable = ( ( nTriTags & COREDISPTRI_TAG_WALKABLE ) != 0 ); + if ( ( ( nTriTags & COREDISPTRI_TAG_FORCE_WALKABLE_BIT ) != 0 ) ) + { + bWalkable = ( ( nTriTags & COREDISPTRI_TAG_FORCE_WALKABLE_VAL ) != 0 ); + } + + // Buildable + bool bBuildable = ( ( nTriTags & COREDISPTRI_TAG_BUILDABLE ) != 0 ); + if ( ( ( nTriTags & COREDISPTRI_TAG_FORCE_BUILDABLE_BIT ) != 0 ) ) + { + bBuildable = ( ( nTriTags & COREDISPTRI_TAG_FORCE_BUILDABLE_VAL ) != 0 ); + } + + nTriTags = 0; + if ( bWalkable ) + { + nTriTags |= DISPTRI_TAG_WALKABLE; + } + + if ( bBuildable ) + { + nTriTags |= DISPTRI_TAG_BUILDABLE; + } + + pMapDispInfo->triTags[iTri] = nTriTags; + pszNext = strtok( NULL, " " ); + iTri++; + } + } + + return( ChunkFile_Ok ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : brushSideID - +// Output : int +//----------------------------------------------------------------------------- +int CMapFile::SideIDToIndex( int brushSideID ) +{ + int i; + for ( i = 0; i < nummapbrushsides; i++ ) + { + if ( brushsides[i].id == brushSideID ) + { + return i; + } + } + Assert( 0 ); + return -1; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mapent - +// *key - +//----------------------------------------------------------------------------- +void ConvertSideList( entity_t *mapent, char *key ) +{ + char *pszSideList = ValueForKey( mapent, key ); + + if (pszSideList) + { + char *pszTmpList = ( char* )_alloca( strlen( pszSideList ) + 1 ); + strcpy( pszTmpList, pszSideList ); + + bool bFirst = true; + char szNewValue[1024]; + szNewValue[0] = '\0'; + + const char *pszScan = strtok( pszTmpList, " " ); + if ( !pszScan ) + return; + do + { + int nSideID; + + if ( sscanf( pszScan, "%d", &nSideID ) == 1 ) + { + int nIndex = g_LoadingMap->SideIDToIndex(nSideID); + if (nIndex != -1) + { + if (!bFirst) + { + strcat( szNewValue, " " ); + } + else + { + bFirst = false; + } + + char szIndex[15]; + itoa( nIndex, szIndex, 10 ); + strcat( szNewValue, szIndex ); + } + } + } while ( ( pszScan = strtok( NULL, " " ) ) ); + + SetKeyValue( mapent, key, szNewValue ); + } +} + + +// Add all the sides referenced by info_no_dynamic_shadows entities to g_NoDynamicShadowSides. +ChunkFileResult_t HandleNoDynamicShadowsEnt( entity_t *pMapEnt ) +{ + // Get the list of the sides. + char *pSideList = ValueForKey( pMapEnt, "sides" ); + + // Parse the side list. + char *pScan = strtok( pSideList, " " ); + if( pScan ) + { + do + { + int brushSideID; + if( sscanf( pScan, "%d", &brushSideID ) == 1 ) + { + if ( g_NoDynamicShadowSides.Find( brushSideID ) == -1 ) + g_NoDynamicShadowSides.AddToTail( brushSideID ); + } + } while( ( pScan = strtok( NULL, " " ) ) ); + } + + // Clear out this entity. + pMapEnt->epairs = NULL; + return ( ChunkFile_Ok ); +} + + +static ChunkFileResult_t LoadOverlayDataTransitionKeyCallback( const char *szKey, const char *szValue, mapoverlay_t *pOverlay ) +{ + if ( !stricmp( szKey, "material" ) ) + { + // Get the material name. + const char *pMaterialName = szValue; + if( g_ReplaceMaterials ) + { + pMaterialName = ReplaceMaterialName( szValue ); + } + + Assert( strlen( pMaterialName ) < OVERLAY_MAP_STRLEN ); + if ( strlen( pMaterialName ) >= OVERLAY_MAP_STRLEN ) + { + Error( "Overlay Material Name (%s) > OVERLAY_MAP_STRLEN (%d)", pMaterialName, OVERLAY_MAP_STRLEN ); + return ChunkFile_Fail; + } + strcpy( pOverlay->szMaterialName, pMaterialName ); + } + else if ( !stricmp( szKey, "StartU") ) + { + CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flU[0] ); + } + else if ( !stricmp( szKey, "EndU" ) ) + { + CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flU[1] ); + } + else if ( !stricmp( szKey, "StartV" ) ) + { + CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flV[0] ); + } + else if ( !stricmp( szKey, "EndV" ) ) + { + CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flV[1] ); + } + else if ( !stricmp( szKey, "BasisOrigin" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecOrigin ); + } + else if ( !stricmp( szKey, "BasisU" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecBasis[0] ); + } + else if ( !stricmp( szKey, "BasisV" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecBasis[1] ); + } + else if ( !stricmp( szKey, "BasisNormal" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecBasis[2] ); + } + else if ( !stricmp( szKey, "uv0" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[0] ); + } + else if ( !stricmp( szKey, "uv1" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[1] ); + } + else if ( !stricmp( szKey, "uv2" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[2] ); + } + else if ( !stricmp( szKey, "uv3" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[3] ); + } + else if ( !stricmp( szKey, "sides" ) ) + { + const char *pSideList = szValue; + char *pTmpList = ( char* )_alloca( strlen( pSideList ) + 1 ); + strcpy( pTmpList, pSideList ); + const char *pScan = strtok( pTmpList, " " ); + if ( !pScan ) + return ChunkFile_Fail; + + pOverlay->aSideList.Purge(); + pOverlay->aFaceList.Purge(); + + do + { + int nSideId; + if ( sscanf( pScan, "%d", &nSideId ) == 1 ) + { + pOverlay->aSideList.AddToTail( nSideId ); + } + } while ( ( pScan = strtok( NULL, " " ) ) ); + } + + return ChunkFile_Ok; +} + +static ChunkFileResult_t LoadOverlayDataTransitionCallback( CChunkFile *pFile, int nParam ) +{ + int iOverlay = g_aMapWaterOverlays.AddToTail(); + mapoverlay_t *pOverlay = &g_aMapWaterOverlays[iOverlay]; + if ( !pOverlay ) + return ChunkFile_Fail; + + pOverlay->nId = ( MAX_MAP_OVERLAYS + 1 ) + g_aMapWaterOverlays.Count() - 1; + pOverlay->m_nRenderOrder = 0; + + ChunkFileResult_t eResult = pFile->ReadChunk( ( KeyHandler_t )LoadOverlayDataTransitionKeyCallback, pOverlay ); + return eResult; +} + +static ChunkFileResult_t LoadOverlayTransitionCallback( CChunkFile *pFile, int nParam ) +{ + CChunkHandlerMap Handlers; + Handlers.AddHandler( "overlaydata", ( ChunkHandler_t )LoadOverlayDataTransitionCallback, 0 ); + pFile->PushHandlers( &Handlers ); + + ChunkFileResult_t eResult = pFile->ReadChunk( NULL, NULL ); + + pFile->PopHandlers(); + + return eResult; +} + +//----------------------------------------------------------------------------- +// Purpose: Iterates all brushes in a ladder entity, generates its mins and maxs. +// These are stored in the object, since the brushes are going to go away. +// Input : *mapent - +//----------------------------------------------------------------------------- +void CMapFile::AddLadderKeys( entity_t *mapent ) +{ + Vector mins, maxs; + ClearBounds( mins, maxs ); + + int i; + for ( i = 0; i < mapent->numbrushes; i++ ) + { + int brushnum = mapent->firstbrush + i; + mapbrush_t *brush = &mapbrushes[ brushnum ]; + + AddPointToBounds( brush->mins, mins, maxs ); + AddPointToBounds( brush->maxs, mins, maxs ); + } + + char buf[16]; + + Q_snprintf( buf, sizeof(buf), "%2.2f", mins.x ); + SetKeyValue( mapent, "mins.x", buf ); + + Q_snprintf( buf, sizeof(buf), "%2.2f", mins.y ); + SetKeyValue( mapent, "mins.y", buf ); + + Q_snprintf( buf, sizeof(buf), "%2.2f", mins.z ); + SetKeyValue( mapent, "mins.z", buf ); + + Q_snprintf( buf, sizeof(buf), "%2.2f", maxs.x ); + SetKeyValue( mapent, "maxs.x", buf ); + + Q_snprintf( buf, sizeof(buf), "%2.2f", maxs.y ); + SetKeyValue( mapent, "maxs.y", buf ); + + Q_snprintf( buf, sizeof(buf), "%2.2f", maxs.z ); + SetKeyValue( mapent, "maxs.z", buf ); +} + +ChunkFileResult_t LoadEntityCallback(CChunkFile *pFile, int nParam) +{ + return g_LoadingMap->LoadEntityCallback( pFile, nParam ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFile - +// ulParam - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapFile::LoadEntityCallback(CChunkFile *pFile, int nParam) +{ + if (num_entities == MAX_MAP_ENTITIES) + { + // Exits. + g_MapError.ReportError ("num_entities == MAX_MAP_ENTITIES"); + } + + entity_t *mapent = &entities[num_entities]; + num_entities++; + memset(mapent, 0, sizeof(*mapent)); + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; + //mapent->portalareas[0] = -1; + //mapent->portalareas[1] = -1; + + LoadEntity_t LoadEntity; + LoadEntity.pEntity = mapent; + + // No default flags/contents + LoadEntity.nBaseFlags = 0; + LoadEntity.nBaseContents = 0; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler("solid", (ChunkHandler_t)::LoadSolidCallback, &LoadEntity); + Handlers.AddHandler("connections", (ChunkHandler_t)LoadConnectionsCallback, &LoadEntity); + Handlers.AddHandler( "overlaytransition", ( ChunkHandler_t )LoadOverlayTransitionCallback, 0 ); + + // + // Read the entity chunk. + // + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadEntityKeyCallback, &LoadEntity); + pFile->PopHandlers(); + + if (eResult == ChunkFile_Ok) + { + GetVectorForKey (mapent, "origin", mapent->origin); + + const char *pMinDXLevelStr = ValueForKey( mapent, "mindxlevel" ); + const char *pMaxDXLevelStr = ValueForKey( mapent, "maxdxlevel" ); + if( *pMinDXLevelStr != '\0' || *pMaxDXLevelStr != '\0' ) + { + int min = 0; + int max = 0; + if( *pMinDXLevelStr ) + { + min = atoi( pMinDXLevelStr ); + } + if( *pMaxDXLevelStr ) + { + max = atoi( pMaxDXLevelStr ); + } + + // Set min and max to default values. + if( min == 0 ) + { + min = g_nDXLevel; + } + if( max == 0 ) + { + max = g_nDXLevel; + } + if( ( g_nDXLevel != 0 ) && ( g_nDXLevel < min || g_nDXLevel > max ) ) + { + mapent->numbrushes = 0; + mapent->epairs = NULL; + return(ChunkFile_Ok); + } + } + + // offset all of the planes and texinfo + if ( mapent->origin[0] || mapent->origin[1] || mapent->origin[2] ) + { + for (int i=0 ; inumbrushes ; i++) + { + mapbrush_t *b = &mapbrushes[mapent->firstbrush + i]; + for (int j=0 ; jnumsides ; j++) + { + side_t *s = &b->original_sides[j]; + vec_t newdist = mapplanes[s->planenum].dist - DotProduct (mapplanes[s->planenum].normal, mapent->origin); + s->planenum = FindFloatPlane (mapplanes[s->planenum].normal, newdist); + if ( !onlyents ) + { + s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], &side_brushtextures[s-brushsides], mapent->origin); + } + } + MakeBrushWindings (b); + } + } + + // + // func_detail brushes are moved into the world entity. The CONTENTS_DETAIL flag was set by the loader. + // + const char *pClassName = ValueForKey( mapent, "classname" ); + + if ( !strcmp( "func_detail", pClassName ) ) + { + MoveBrushesToWorld (mapent); + mapent->numbrushes = 0; + + // clear out this entity + mapent->epairs = NULL; + return(ChunkFile_Ok); + } + + // these get added to a list for processing the portal file + // but aren't necessary to emit to the BSP + if ( !strcmp( "func_viscluster", pClassName ) ) + { + AddVisCluster(mapent); + return(ChunkFile_Ok); + } + + // + // func_ladder brushes are moved into the world entity. We convert the func_ladder to an info_ladder + // that holds the ladder's mins and maxs, and leave the entity. This helps the bots figure out ladders. + // + if ( !strcmp( "func_ladder", pClassName ) ) + { + AddLadderKeys( mapent ); + + MoveBrushesToWorld (mapent); + + // Convert to info_ladder entity + SetKeyValue( mapent, "classname", "info_ladder" ); + + return(ChunkFile_Ok); + } + + if( !strcmp( "env_cubemap", pClassName ) ) + { + if( ( g_nDXLevel == 0 ) || ( g_nDXLevel >= 70 ) ) + { + const char *pSideListStr = ValueForKey( mapent, "sides" ); + int size; + size = IntForKey( mapent, "cubemapsize" ); + Cubemap_InsertSample( mapent->origin, size ); + Cubemap_SaveBrushSides( pSideListStr ); + } + // clear out this entity + mapent->epairs = NULL; + return(ChunkFile_Ok); + } + + if ( !strcmp( "test_sidelist", pClassName ) ) + { + ConvertSideList(mapent, "sides"); + return ChunkFile_Ok; + } + + if ( !strcmp( "info_overlay", pClassName ) ) + { + int iAccessorID = Overlay_GetFromEntity( mapent ); + + if ( iAccessorID < 0 ) + { + // Clear out this entity. + mapent->epairs = NULL; + } + else + { + // Convert to info_overlay_accessor entity + SetKeyValue( mapent, "classname", "info_overlay_accessor" ); + + // Remember the id for accessing the overlay + char buf[16]; + Q_snprintf( buf, sizeof(buf), "%i", iAccessorID ); + SetKeyValue( mapent, "OverlayID", buf ); + } + + return ( ChunkFile_Ok ); + } + + if ( !strcmp( "info_overlay_transition", pClassName ) ) + { + // Clear out this entity. + mapent->epairs = NULL; + return ( ChunkFile_Ok ); + } + + if ( Q_stricmp( pClassName, "info_no_dynamic_shadow" ) == 0 ) + { + return HandleNoDynamicShadowsEnt( mapent ); + } + + if ( Q_stricmp( pClassName, "func_instance_parms" ) == 0 ) + { + // Clear out this entity. + mapent->epairs = NULL; + return ( ChunkFile_Ok ); + } + + // areaportal entities move their brushes, but don't eliminate + // the entity + if( IsAreaPortal( pClassName ) ) + { + char str[128]; + + if (mapent->numbrushes != 1) + { + Error ("Entity %i: func_areaportal can only be a single brush", num_entities-1); + } + + mapbrush_t *b = &mapbrushes[nummapbrushes-1]; + b->contents = CONTENTS_AREAPORTAL; + c_areaportals++; + mapent->areaportalnum = c_areaportals; + + // set the portal number as "portalnumber" + sprintf (str, "%i", c_areaportals); + SetKeyValue (mapent, "portalnumber", str); + + MoveBrushesToWorld (mapent); + return(ChunkFile_Ok); + } + +#ifdef VSVMFIO + if ( !Q_stricmp( pClassName, "light" ) ) + { + CVmfImport::GetVmfImporter()->ImportLightCallback( + ValueForKey( mapent, "hammerid" ), + ValueForKey( mapent, "origin" ), + ValueForKey( mapent, "_light" ), + ValueForKey( mapent, "_lightHDR" ), + ValueForKey( mapent, "_lightscaleHDR" ), + ValueForKey( mapent, "_quadratic_attn" ) ); + } + + if ( !Q_stricmp( pClassName, "light_spot" ) ) + { + CVmfImport::GetVmfImporter()->ImportLightSpotCallback( + ValueForKey( mapent, "hammerid" ), + ValueForKey( mapent, "origin" ), + ValueForKey( mapent, "angles" ), + ValueForKey( mapent, "pitch" ), + ValueForKey( mapent, "_light" ), + ValueForKey( mapent, "_lightHDR" ), + ValueForKey( mapent, "_lightscaleHDR" ), + ValueForKey( mapent, "_quadratic_attn" ), + ValueForKey( mapent, "_inner_cone" ), + ValueForKey( mapent, "_cone" ), + ValueForKey( mapent, "_exponent" ) ); + } + + if ( !Q_stricmp( pClassName, "light_dynamic" ) ) + { + CVmfImport::GetVmfImporter()->ImportLightDynamicCallback( + ValueForKey( mapent, "hammerid" ), + ValueForKey( mapent, "origin" ), + ValueForKey( mapent, "angles" ), + ValueForKey( mapent, "pitch" ), + ValueForKey( mapent, "_light" ), + ValueForKey( mapent, "_quadratic_attn" ), + ValueForKey( mapent, "_inner_cone" ), + ValueForKey( mapent, "_cone" ), + ValueForKey( mapent, "brightness" ), + ValueForKey( mapent, "distance" ), + ValueForKey( mapent, "spotlight_radius" ) ); + } + + if ( !Q_stricmp( pClassName, "light_environment" ) ) + { + CVmfImport::GetVmfImporter()->ImportLightEnvironmentCallback( + ValueForKey( mapent, "hammerid" ), + ValueForKey( mapent, "origin" ), + ValueForKey( mapent, "angles" ), + ValueForKey( mapent, "pitch" ), + ValueForKey( mapent, "_light" ), + ValueForKey( mapent, "_lightHDR" ), + ValueForKey( mapent, "_lightscaleHDR" ), + ValueForKey( mapent, "_ambient" ), + ValueForKey( mapent, "_ambientHDR" ), + ValueForKey( mapent, "_AmbientScaleHDR" ), + ValueForKey( mapent, "SunSpreadAngle" ) ); + } + + const char *pModel = ValueForKey( mapent, "model" ); + if ( pModel && Q_strlen( pModel ) ) + { + CVmfImport::GetVmfImporter()->ImportModelCallback( + pModel, + ValueForKey( mapent, "hammerid" ), + ValueForKey( mapent, "angles" ), + ValueForKey( mapent, "origin" ), + MDagPath() ); + } +#endif // VSVMFIO + + // If it's not in the world at this point, unmark CONTENTS_DETAIL from all sides... + if ( mapent != &entities[ 0 ] ) + { + RemoveContentsDetailFromEntity( mapent ); + } + + return(ChunkFile_Ok); + } + + return(eResult); +} + + +entity_t* EntityByName( char const *pTestName ) +{ + if( !pTestName ) + return 0; + + for( int i=0; i < g_MainMap->num_entities; i++ ) + { + entity_t *e = &g_MainMap->entities[i]; + + const char *pName = ValueForKey( e, "targetname" ); + if( stricmp( pName, pTestName ) == 0 ) + return e; + } + + return 0; +} + + +void CMapFile::ForceFuncAreaPortalWindowContents() +{ + // Now go through all areaportal entities and force CONTENTS_WINDOW + // on the brushes of the bmodels they point at. + char *targets[] = {"target", "BackgroundBModel"}; + int nTargets = sizeof(targets) / sizeof(targets[0]); + + for( int i=0; i < num_entities; i++ ) + { + entity_t *e = &entities[i]; + + const char *pClassName = ValueForKey( e, "classname" ); + + // Don't do this on "normal" func_areaportal entities. Those are tied to doors + // and should be opaque when closed. But areaportal windows (and any other + // distance-based areaportals) should be windows because they are normally open/transparent + if( !IsAreaPortal( pClassName ) || !Q_stricmp( pClassName, "func_areaportal" ) ) + continue; + +// const char *pTestEntName = ValueForKey( e, "targetname" ); + + for( int iTarget=0; iTarget < nTargets; iTarget++ ) + { + char const *pEntName = ValueForKey( e, targets[iTarget] ); + if( !pEntName[0] ) + continue; + + entity_t *pBrushEnt = EntityByName( pEntName ); + if( !pBrushEnt ) + continue; + + for( int iBrush=0; iBrush < pBrushEnt->numbrushes; iBrush++ ) + { + mapbrushes[pBrushEnt->firstbrush + iBrush].contents &= ~CONTENTS_SOLID; + mapbrushes[pBrushEnt->firstbrush + iBrush].contents |= CONTENTS_TRANSLUCENT | CONTENTS_WINDOW; + } + } + } +} + + +// ============ Instancing ============ + +// #define MERGE_INSTANCE_DEBUG_INFO 1 + +#define INSTANCE_VARIABLE_KEY "replace" + +static GameData GD; + +//----------------------------------------------------------------------------- +// Purpose: this function will read in a standard key / value file +// Input : pFilename - the absolute name of the file to read +// Output : returns the KeyValues of the file, NULL if the file could not be read. +//----------------------------------------------------------------------------- +static KeyValues *ReadKeyValuesFile( const char *pFilename ) +{ + // Read in the gameinfo.txt file and null-terminate it. + FILE *fp = fopen( pFilename, "rb" ); + if ( !fp ) + return NULL; + CUtlVector buf; + fseek( fp, 0, SEEK_END ); + buf.SetSize( ftell( fp ) + 1 ); + fseek( fp, 0, SEEK_SET ); + fread( buf.Base(), 1, buf.Count()-1, fp ); + fclose( fp ); + buf[buf.Count()-1] = 0; + + KeyValues *kv = new KeyValues( "" ); + if ( !kv->LoadFromBuffer( pFilename, buf.Base() ) ) + { + kv->deleteThis(); + return NULL; + } + + return kv; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will set a secondary lookup path for instances. +// Input : pszInstancePath - the secondary lookup path +//----------------------------------------------------------------------------- +void CMapFile::SetInstancePath( const char *pszInstancePath ) +{ + strcpy( m_InstancePath, pszInstancePath ); + V_strlower( m_InstancePath ); + V_FixSlashes( m_InstancePath ); +} + + +//----------------------------------------------------------------------------- +// Purpose: This function will attempt to find a full path given the base and relative names. +// Input : pszBaseFileName - the base file that referenced this instance +// pszInstanceFileName - the relative file name of this instance +// Output : Returns true if it was able to locate the file +// pszOutFileName - the full path to the file name if located +//----------------------------------------------------------------------------- +bool CMapFile::DeterminePath( const char *pszBaseFileName, const char *pszInstanceFileName, char *pszOutFileName ) +{ + char szInstanceFileNameFixed[ MAX_PATH ]; + const char *pszMapPath = "\\maps\\"; + + strcpy( szInstanceFileNameFixed, pszInstanceFileName ); + V_SetExtension( szInstanceFileNameFixed, ".vmf", sizeof( szInstanceFileNameFixed ) ); + V_FixSlashes( szInstanceFileNameFixed ); + + // first, try to find a relative location based upon the Base file name + strcpy( pszOutFileName, pszBaseFileName ); + V_StripFilename( pszOutFileName ); + + strcat( pszOutFileName, "\\" ); + strcat( pszOutFileName, szInstanceFileNameFixed ); + + if ( g_pFullFileSystem->FileExists( pszOutFileName ) ) + { + return true; + } + + // second, try to find the master 'maps' directory and make it relative from that + strcpy( pszOutFileName, pszBaseFileName ); + V_StripFilename( pszOutFileName ); + V_RemoveDotSlashes( pszOutFileName ); + V_FixDoubleSlashes( pszOutFileName ); + V_strlower( pszOutFileName ); + strcat( pszOutFileName, "\\" ); + + char *pos = strstr( pszOutFileName, pszMapPath ); + if ( pos ) + { + pos += strlen( pszMapPath ); + *pos = 0; + strcat( pszOutFileName, szInstanceFileNameFixed ); + + if ( g_pFullFileSystem->FileExists( pszOutFileName ) ) + { + return true; + } + } + + if ( m_InstancePath[ 0 ] != 0 ) + { + sprintf( szInstanceFileNameFixed, "%s%s", m_InstancePath, pszInstanceFileName ); + + if ( g_pFullFileSystem->FileExists( szInstanceFileNameFixed, "GAME" ) ) + { + char FullPath[ MAX_PATH ]; + g_pFullFileSystem->RelativePathToFullPath( szInstanceFileNameFixed, "GAME", FullPath, sizeof( FullPath ) ); + strcpy( pszOutFileName, FullPath ); + + return true; + } + } + + pszOutFileName[ 0 ] = 0; + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will check the main map for any func_instances. It will +// also attempt to load in the gamedata file for instancing remapping help. +// Input : none +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::CheckForInstances( const char *pszFileName ) +{ + if ( this != g_MainMap ) + { // all sub-instances will be appended to the main map master list as they are read in + // so the main loop below will naturally get to the appended ones. + return; + } + + char GameInfoPath[ MAX_PATH ]; + + g_pFullFileSystem->RelativePathToFullPath( "gameinfo.txt", "MOD", GameInfoPath, sizeof( GameInfoPath ) ); + KeyValues *GameInfoKV = ReadKeyValuesFile( GameInfoPath ); + if ( !GameInfoKV ) + { + Msg( "Could not locate gameinfo.txt for Instance Remapping at %s\n", GameInfoPath ); + return; + } + + const char *InstancePath = GameInfoKV->GetString( "InstancePath", NULL ); + if ( InstancePath ) + { + CMapFile::SetInstancePath( InstancePath ); + } + + const char *GameDataFile = GameInfoKV->GetString( "GameData", NULL ); + if ( !GameDataFile ) + { + Msg( "Could not locate 'GameData' key in %s\n", GameInfoPath ); + return; + } + + char FDGPath[ MAX_PATH ]; + if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, "EXECUTABLE_PATH", FDGPath, sizeof( FDGPath ) ) ) + { + if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, "", FDGPath, sizeof( FDGPath ) ) ) + { + Msg( "Could not locate GameData file %s\n", GameDataFile ); + } + } + + GD.Load( FDGPath ); + + // this list will grow as instances are merged onto it. sub-instances are merged and + // automatically done in this processing. + for ( int i = 0; i < num_entities; i++ ) + { + char *pEntity = ValueForKey( &entities[ i ], "classname" ); + if ( !strcmp( pEntity, "func_instance" ) ) + { + char *pInstanceFile = ValueForKey( &entities[ i ], "file" ); + if ( pInstanceFile[ 0 ] ) + { + char InstancePath[ MAX_PATH ]; + bool bLoaded = false; + + if ( DeterminePath( pszFileName, pInstanceFile, InstancePath ) ) + { + if ( LoadMapFile( InstancePath ) ) + { + MergeInstance( &entities[ i ], g_LoadingMap ); + delete g_LoadingMap; + bLoaded = true; + } + } + + if ( bLoaded == false ) + { + Color red( 255, 0, 0, 255 ); + + ColorSpewMessage( SPEW_ERROR, &red, "Could not open instance file %s\n", pInstanceFile ); + } + } + + entities[ i ].numbrushes = 0; + entities[ i ].epairs = NULL; + } + } + + g_LoadingMap = this; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will do all of the necessary work to merge the instance +// into the main map. +// Input : pInstanceEntity - the entity of the func_instance +// Instance - the map file of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergeInstance( entity_t *pInstanceEntity, CMapFile *Instance ) +{ + matrix3x4_t mat; + QAngle angles; + Vector OriginOffset = pInstanceEntity->origin; + + m_InstanceCount++; + + GetAnglesForKey( pInstanceEntity, "angles", angles ); + AngleMatrix( angles, OriginOffset, mat ); + +#ifdef MERGE_INSTANCE_DEBUG_INFO + Msg( "Instance Remapping: O:( %g, %g, %g ) A:( %g, %g, %g )\n", OriginOffset.x, OriginOffset.y, OriginOffset.z, angles.x, angles.y, angles.z ); +#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO + MergePlanes( pInstanceEntity, Instance, OriginOffset, angles, mat ); + MergeBrushes( pInstanceEntity, Instance, OriginOffset, angles, mat ); + MergeBrushSides( pInstanceEntity, Instance, OriginOffset, angles, mat ); + MergeEntities( pInstanceEntity, Instance, OriginOffset, angles, mat ); + MergeOverlays( pInstanceEntity, Instance, OriginOffset, angles, mat ); +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will merge in the map planes from the instance into +// the main map. +// Input : pInstanceEntity - the entity of the func_instance +// Instance - the map file of the instance +// InstanceOrigin - the translation of the instance +// InstanceAngle - the rotation of the instance +// InstanceMatrix - the translation / rotation matrix of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergePlanes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) +{ + // Each pair of planes needs to be added to the main map + for ( int i = 0; i < Instance->nummapplanes; i += 2 ) + { + FindFloatPlane( Instance->mapplanes[i].normal, Instance->mapplanes[i].dist ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will merge in the map brushes from the instance into +// the main map. +// Input : pInstanceEntity - the entity of the func_instance +// Instance - the map file of the instance +// InstanceOrigin - the translation of the instance +// InstanceAngle - the rotation of the instance +// InstanceMatrix - the translation / rotation matrix of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergeBrushes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) +{ + int max_brush_id = 0; + + for( int i = 0; i < nummapbrushes; i++ ) + { + if ( mapbrushes[ i ].id > max_brush_id ) + { + max_brush_id = mapbrushes[ i ].id; + } + } + + for( int i = 0; i < Instance->nummapbrushes; i++ ) + { + mapbrushes[ nummapbrushes + i ] = Instance->mapbrushes[ i ]; + + mapbrush_t *brush = &mapbrushes[ nummapbrushes + i ]; + brush->entitynum += num_entities; + brush->brushnum += nummapbrushes; + + if ( i < Instance->entities[ 0 ].numbrushes || ( brush->contents & CONTENTS_LADDER ) != 0 ) + { // world spawn brushes as well as ladders we physically move + Vector minsIn = brush->mins; + Vector maxsIn = brush->maxs; + + TransformAABB( InstanceMatrix, minsIn, maxsIn, brush->mins, brush->maxs ); + } + else + { + } + brush->id += max_brush_id; + + int index = brush->original_sides - Instance->brushsides; + brush->original_sides = &brushsides[ nummapbrushsides + index ]; + } + + nummapbrushes += Instance->nummapbrushes; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will merge in the map sides from the instance into +// the main map. +// Input : pInstanceEntity - the entity of the func_instance +// Instance - the map file of the instance +// InstanceOrigin - the translation of the instance +// InstanceAngle - the rotation of the instance +// InstanceMatrix - the translation / rotation matrix of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergeBrushSides( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) +{ + int max_side_id = 0; + + for( int i = 0; i < nummapbrushsides; i++ ) + { + if ( brushsides[ i ].id > max_side_id ) + { + max_side_id = brushsides[ i ].id; + } + } + + for( int i = 0; i < Instance->nummapbrushsides; i++ ) + { + brushsides[ nummapbrushsides + i ] = Instance->brushsides[ i ]; + + side_t *side = &brushsides[ nummapbrushsides + i ]; + // The planes got merged & remapped. So you need to search for the output plane index on each side + // NOTE: You could optimize this by saving off an index map in MergePlanes + side->planenum = FindFloatPlane( Instance->mapplanes[side->planenum].normal, Instance->mapplanes[side->planenum].dist ); + side->id += max_side_id; + + // this could be pre-processed into a list for quicker checking + bool bNeedsTranslation = ( side->pMapDisp && side->pMapDisp->entitynum == 0 ); + if ( !bNeedsTranslation ) + { // check for sides that are part of the world spawn - those need translating + for( int j = 0; j < Instance->entities[ 0 ].numbrushes; j++ ) + { + int loc = Instance->mapbrushes[ j ].original_sides - Instance->brushsides; + + if ( i >= loc && i < ( loc + Instance->mapbrushes[ j ].numsides ) ) + { + bNeedsTranslation = true; + break; + } + } + } + if ( !bNeedsTranslation ) + { // sides for ladders are outside of the world spawn, but also need translating + for( int j = Instance->entities[ 0 ].numbrushes; j < Instance->nummapbrushes; j++ ) + { + int loc = Instance->mapbrushes[ j ].original_sides - Instance->brushsides; + + if ( i >= loc && i < ( loc + Instance->mapbrushes[ j ].numsides ) && ( Instance->mapbrushes[ j ].contents & CONTENTS_LADDER ) != 0 ) + { + bNeedsTranslation = true; + break; + } + } + } + if ( bNeedsTranslation ) + { // we only want to do the adjustment on world spawn brushes, not entity brushes + if ( side->winding ) + { + for( int point = 0; point < side->winding->numpoints; point++ ) + { + Vector inPoint = side->winding->p[ point ]; + VectorTransform( inPoint, InstanceMatrix, side->winding->p[ point ] ); + } + } + + int planenum = side->planenum; + cplane_t inPlane, outPlane; + inPlane.normal = mapplanes[ planenum ].normal; + inPlane.dist = mapplanes[ planenum ].dist; + + MatrixTransformPlane( InstanceMatrix, inPlane, outPlane ); + planenum = FindFloatPlane( outPlane.normal, outPlane.dist ); + side->planenum = planenum; + + brush_texture_t bt = Instance->side_brushtextures[ i ]; + + VectorRotate( Instance->side_brushtextures[ i ].UAxis, InstanceMatrix, bt.UAxis ); + VectorRotate( Instance->side_brushtextures[ i ].VAxis, InstanceMatrix, bt.VAxis ); + bt.shift[ 0 ] -= InstanceOrigin.Dot( bt.UAxis ) / bt.textureWorldUnitsPerTexel[ 0 ]; + bt.shift[ 1 ] -= InstanceOrigin.Dot( bt.VAxis ) / bt.textureWorldUnitsPerTexel[ 1 ]; + + if ( !onlyents ) + { + side->texinfo = TexinfoForBrushTexture ( &mapplanes[ side->planenum ], &bt, vec3_origin ); + } + } + + if ( side->pMapDisp ) + { + mapdispinfo_t *disp = side->pMapDisp; + + disp->brushSideID = side->id; + Vector inPoint = disp->startPosition; + VectorTransform( inPoint, InstanceMatrix, disp->startPosition ); + + disp->face.originalface = side; + disp->face.texinfo = side->texinfo; + disp->face.planenum = side->planenum; + disp->entitynum += num_entities; + + for( int point = 0; point < disp->face.w->numpoints; point++ ) + { + Vector inPoint = disp->face.w->p[ point ]; + VectorTransform( inPoint, InstanceMatrix, disp->face.w->p[ point ] ); + } + + } + } + + nummapbrushsides += Instance->nummapbrushsides; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will look for replace parameters in the function instance +// to see if there is anything in the epair that should be replaced. +// Input : pPair - the epair with the value +// pInstanceEntity - the func_instance that may ahve replace keywords +// Output : pPair - the value field may be updated +//----------------------------------------------------------------------------- +void CMapFile::ReplaceInstancePair( epair_t *pPair, entity_t *pInstanceEntity ) +{ + char Value[ MAX_KEYVALUE_LEN ], NewValue[ MAX_KEYVALUE_LEN ]; + bool Overwritten = false; + + strcpy( NewValue, pPair->value ); + for ( epair_t *epInstance = pInstanceEntity->epairs; epInstance != NULL; epInstance = epInstance->next ) + { + if ( strnicmp( epInstance->key, INSTANCE_VARIABLE_KEY, strlen( INSTANCE_VARIABLE_KEY ) ) == 0 ) + { + char InstanceVariable[ MAX_KEYVALUE_LEN ]; + + strcpy( InstanceVariable, epInstance->value ); + + char *ValuePos = strchr( InstanceVariable, ' ' ); + if ( !ValuePos ) + { + continue; + } + *ValuePos = 0; + ValuePos++; + + strcpy( Value, NewValue ); + if ( !V_StrSubst( Value, InstanceVariable, ValuePos, NewValue, sizeof( NewValue ), false ) ) + { + Overwritten = true; + break; + } + } + } + + if ( !Overwritten && strcmp( pPair->value, NewValue ) != 0 ) + { + free( pPair->value ); + pPair->value = copystring( NewValue ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will merge in the entities from the instance into +// the main map. +// Input : pInstanceEntity - the entity of the func_instance +// Instance - the map file of the instance +// InstanceOrigin - the translation of the instance +// InstanceAngle - the rotation of the instance +// InstanceMatrix - the translation / rotation matrix of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) +{ + int max_entity_id = 0; + char temp[ 2048 ]; + char NameFixup[ 128 ]; + entity_t *WorldspawnEnt = NULL; + GameData::TNameFixup FixupStyle; + + char *pTargetName = ValueForKey( pInstanceEntity, "targetname" ); + char *pName = ValueForKey( pInstanceEntity, "name" ); + if ( pTargetName[ 0 ] ) + { + sprintf( NameFixup, "%s", pTargetName ); + } + else if ( pName[ 0 ] ) + { + sprintf( NameFixup, "%s", pName ); + } + else + { + sprintf( NameFixup, "InstanceAuto%d", m_InstanceCount ); + } + + for( int i = 0; i < num_entities; i++ ) + { + char *pID = ValueForKey( &entities[ i ], "hammerid" ); + if ( pID[ 0 ] ) + { + int value = atoi( pID ); + if ( value > max_entity_id ) + { + max_entity_id = value; + } + } + } + + FixupStyle = ( GameData::TNameFixup )( IntForKey( pInstanceEntity, "fixup_style" ) ); + + for( int i = 0; i < Instance->num_entities; i++ ) + { + entities[ num_entities + i ] = Instance->entities[ i ]; + + entity_t *entity = &entities[ num_entities + i ]; + entity->firstbrush += ( nummapbrushes - Instance->nummapbrushes ); + + char *pID = ValueForKey( entity, "hammerid" ); + if ( pID[ 0 ] ) + { + int value = atoi( pID ); + value += max_entity_id; + sprintf( temp, "%d", value ); + + SetKeyValue( entity, "hammerid", temp ); + } + + char *pEntity = ValueForKey( entity, "classname" ); + if ( strcmpi( pEntity, "worldspawn" ) == 0 ) + { + WorldspawnEnt = entity; + } + else + { + Vector inOrigin = entity->origin; + VectorTransform( inOrigin, InstanceMatrix, entity->origin ); + + // search for variables coming from the func_instance to replace inside of the instance + // this is done before entity fixup, so fixup may occur on the replaced value. Not sure if this is a desired order of operation yet. + for ( epair_t *ep = entity->epairs; ep != NULL; ep = ep->next ) + { + ReplaceInstancePair( ep, pInstanceEntity ); + } + +#ifdef MERGE_INSTANCE_DEBUG_INFO + Msg( "Remapping class %s\n", pEntity ); +#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO + GDclass *EntClass = GD.BeginInstanceRemap( pEntity, NameFixup, InstanceOrigin, InstanceAngle ); + if ( EntClass ) + { + for( int i = 0; i < EntClass->GetVariableCount(); i++ ) + { + GDinputvariable *EntVar = EntClass->GetVariableAt( i ); + char *pValue = ValueForKey( entity, ( char * )EntVar->GetName() ); + if ( GD.RemapKeyValue( EntVar->GetName(), pValue, temp, FixupStyle ) ) + { +#ifdef MERGE_INSTANCE_DEBUG_INFO + Msg( " %d. Remapped %s: from %s to %s\n", i, EntVar->GetName(), pValue, temp ); +#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO + SetKeyValue( entity, EntVar->GetName(), temp ); + } + else + { +#ifdef MERGE_INSTANCE_DEBUG_INFO + Msg( " %d. Ignored %s: %s\n", i, EntVar->GetName(), pValue ); +#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO + } + } + } + + if ( strcmpi( pEntity, "func_simpleladder" ) == 0 ) + { // hate having to do this, but the key values are so screwed up + AddLadderKeys( entity ); +/* Vector vInNormal, vOutNormal; + + vInNormal.x = FloatForKey( entity, "normal.x" ); + vInNormal.y = FloatForKey( entity, "normal.y" ); + vInNormal.z = FloatForKey( entity, "normal.z" ); + VectorRotate( vInNormal, InstanceMatrix, vOutNormal ); + + Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.x ); + SetKeyValue( entity, "normal.x", temp ); + + Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.y ); + SetKeyValue( entity, "normal.y", temp ); + + Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.z ); + SetKeyValue( entity, "normal.z", temp );*/ + } + } + +#ifdef MERGE_INSTANCE_DEBUG_INFO + Msg( "Instance Entity %d remapped to %d\n", i, num_entities + i ); + Msg( " FirstBrush: from %d to %d\n", Instance->entities[ i ].firstbrush, entity->firstbrush ); + Msg( " KV Pairs:\n" ); + for ( epair_t *ep = entity->epairs; ep->next != NULL; ep = ep->next ) + { + Msg( " %s %s\n", ep->key, ep->value ); + } +#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO + } + + // search for variables coming from the func_instance to replace inside of the instance + // this is done before connection fix up, so fix up may occur on the replaced value. Not sure if this is a desired order of operation yet. + for( CConnectionPairs *Connection = Instance->m_ConnectionPairs; Connection; Connection = Connection->m_Next ) + { + ReplaceInstancePair( Connection->m_Pair, pInstanceEntity ); + } + + for( CConnectionPairs *Connection = Instance->m_ConnectionPairs; Connection; Connection = Connection->m_Next ) + { + char *newValue, *oldValue; + char origValue[ 4096 ]; + int extraLen = 0; + + oldValue = Connection->m_Pair->value; + strcpy( origValue, oldValue ); + char *pos = strchr( origValue, ',' ); + if ( pos ) + { // null terminate the first field + *pos = NULL; + extraLen = strlen( pos + 1) + 1; // for the comma we just null'd + } + + if ( GD.RemapNameField( origValue, temp, FixupStyle ) ) + { + newValue = new char [ strlen( temp ) + extraLen + 1 ]; + strcpy( newValue, temp ); + if ( pos ) + { + strcat( newValue, "," ); + strcat( newValue, pos + 1 ); + } + + Connection->m_Pair->value = newValue; + delete oldValue; + } + } + + num_entities += Instance->num_entities; + + MoveBrushesToWorldGeneral( WorldspawnEnt ); + WorldspawnEnt->numbrushes = 0; + WorldspawnEnt->epairs = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will translate overlays from the instance into +// the main map. +// Input : InstanceEntityNum - the entity number of the func_instance +// Instance - the map file of the instance +// InstanceOrigin - the translation of the instance +// InstanceAngle - the rotation of the instance +// InstanceMatrix - the translation / rotation matrix of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergeOverlays( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) +{ + for( int i = Instance->m_StartMapOverlays; i < g_aMapOverlays.Count(); i++ ) + { + Overlay_Translate( &g_aMapOverlays[ i ], InstanceOrigin, InstanceAngle, InstanceMatrix ); + } + for( int i = Instance->m_StartMapWaterOverlays; i < g_aMapWaterOverlays.Count(); i++ ) + { + Overlay_Translate( &g_aMapWaterOverlays[ i ], InstanceOrigin, InstanceAngle, InstanceMatrix ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Loads a VMF or MAP file. If the file has a .MAP extension, the MAP +// loader is used, otherwise the file is assumed to be in VMF format. +// Input : pszFileName - Full path of the map file to load. +//----------------------------------------------------------------------------- +bool LoadMapFile( const char *pszFileName ) +{ + bool bLoadingManifest = false; + CManifest *pMainManifest = NULL; + ChunkFileResult_t eResult; + + // + // Dummy this up for the texture handling. This can be removed when old .MAP file + // support is removed. + // + g_nMapFileVersion = 400; + + const char *pszExtension =V_GetFileExtension( pszFileName ); + if ( pszExtension && strcmpi( pszExtension, "vmm" ) == 0 ) + { + pMainManifest = new CManifest(); + if ( pMainManifest->LoadVMFManifest( pszFileName ) ) + { + eResult = ChunkFile_Ok; + pszFileName = pMainManifest->GetInstancePath(); + } + else + { + eResult = ChunkFile_Fail; + } + bLoadingManifest = true; + } + else + { + // + // Open the file. + // + CChunkFile File; + eResult = File.Open(pszFileName, ChunkFile_Read); + + // + // Read the file. + // + if (eResult == ChunkFile_Ok) + { + int index = g_Maps.AddToTail( new CMapFile() ); + g_LoadingMap = g_Maps[ index ]; + if ( g_MainMap == NULL ) + { + g_MainMap = g_LoadingMap; + } + + if ( g_MainMap == g_LoadingMap || verbose ) + { + Msg( "Loading %s\n", pszFileName ); + } + + + // reset the displacement info count + // nummapdispinfo = 0; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler("world", (ChunkHandler_t)LoadEntityCallback, 0); + Handlers.AddHandler("entity", (ChunkHandler_t)LoadEntityCallback, 0); + + File.PushHandlers(&Handlers); + + // + // Read the sub-chunks. We ignore keys in the root of the file. + // + while (eResult == ChunkFile_Ok) + { + eResult = File.ReadChunk(); + } + + File.PopHandlers(); + } + else + { + Error("Error opening %s: %s.\n", pszFileName, File.GetErrorText(eResult)); + } + } + + if ((eResult == ChunkFile_Ok) || (eResult == ChunkFile_EOF)) + { + // Update the overlay/side list(s). + Overlay_UpdateSideLists( g_LoadingMap->m_StartMapOverlays ); + OverlayTransition_UpdateSideLists( g_LoadingMap->m_StartMapWaterOverlays ); + + g_LoadingMap->CheckForInstances( pszFileName ); + + if ( pMainManifest ) + { + pMainManifest->CordonWorld(); + } + + ClearBounds (g_LoadingMap->map_mins, g_LoadingMap->map_maxs); + for (int i=0 ; ientities[0].numbrushes ; i++) + { + // HLTOOLS: Raise map limits + if (g_LoadingMap->mapbrushes[i].mins[0] > MAX_COORD_INTEGER) + { + continue; // no valid points + } + + AddPointToBounds (g_LoadingMap->mapbrushes[i].mins, g_LoadingMap->map_mins, g_LoadingMap->map_maxs); + AddPointToBounds (g_LoadingMap->mapbrushes[i].maxs, g_LoadingMap->map_mins, g_LoadingMap->map_maxs); + } + + qprintf ("%5i brushes\n", g_LoadingMap->nummapbrushes); + qprintf ("%5i clipbrushes\n", g_LoadingMap->c_clipbrushes); + qprintf ("%5i total sides\n", g_LoadingMap->nummapbrushsides); + qprintf ("%5i boxbevels\n", g_LoadingMap->c_boxbevels); + qprintf ("%5i edgebevels\n", g_LoadingMap->c_edgebevels); + qprintf ("%5i entities\n", g_LoadingMap->num_entities); + qprintf ("%5i planes\n", g_LoadingMap->nummapplanes); + qprintf ("%5i areaportals\n", g_LoadingMap->c_areaportals); + qprintf ("size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", g_LoadingMap->map_mins[0],g_LoadingMap->map_mins[1],g_LoadingMap->map_mins[2], + g_LoadingMap->map_maxs[0],g_LoadingMap->map_maxs[1],g_LoadingMap->map_maxs[2]); + + //TestExpandBrushes(); + + // Clear the error reporting + g_MapError.ClearState(); + } + + if ( g_MainMap == g_LoadingMap ) + { + num_entities = g_MainMap->num_entities; + memcpy( entities, g_MainMap->entities, sizeof( g_MainMap->entities ) ); + } + g_LoadingMap->ForceFuncAreaPortalWindowContents(); + + return ( ( eResult == ChunkFile_Ok ) || ( eResult == ChunkFile_EOF ) ); +} + +ChunkFileResult_t LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo) +{ + return g_LoadingMap->LoadSideCallback( pFile, pSideInfo ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pFile - +// pParent - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapFile::LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo) +{ + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + { + g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); + } + + pSideInfo->pSide = &brushsides[nummapbrushsides]; + + side_t *side = pSideInfo->pSide; + mapbrush_t *b = pSideInfo->pBrush; + g_MapError.BrushSide( pSideInfo->nSideIndex++ ); + + // initialize the displacement info + pSideInfo->pSide->pMapDisp = NULL; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler( "dispinfo", ( ChunkHandler_t )LoadDispInfoCallback, &side->pMapDisp ); + + // + // Read the side chunk. + // + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadSideKeyCallback, pSideInfo); + pFile->PopHandlers(); + + if (eResult == ChunkFile_Ok) + { + side->contents |= pSideInfo->nBaseContents; + side->surf |= pSideInfo->nBaseFlags; + pSideInfo->td.flags |= pSideInfo->nBaseFlags; + + if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + { + side->contents |= CONTENTS_DETAIL; + } + + if (fulldetail ) + { + side->contents &= ~CONTENTS_DETAIL; + } + + if (!(side->contents & (ALL_VISIBLE_CONTENTS | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) ) + { + side->contents |= CONTENTS_SOLID; + } + + // hints and skips are never detail, and have no content + if (side->surf & (SURF_HINT|SURF_SKIP) ) + { + side->contents = 0; + } + + // + // find the plane number + // + int planenum = PlaneFromPoints(pSideInfo->planepts[0], pSideInfo->planepts[1], pSideInfo->planepts[2]); + if (planenum != -1) + { + // + // See if the plane has been used already. + // + int k; + for ( k = 0; k < b->numsides; k++) + { + side_t *s2 = b->original_sides + k; + if (s2->planenum == planenum) + { + g_MapError.ReportWarning("duplicate plane"); + break; + } + if ( s2->planenum == (planenum^1) ) + { + g_MapError.ReportWarning("mirrored plane"); + break; + } + } + + // + // If the plane hasn't been used already, keep this side. + // + if (k == b->numsides) + { + side = b->original_sides + b->numsides; + side->planenum = planenum; + if ( !onlyents ) + { + side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], &pSideInfo->td, vec3_origin); + } + + // save the td off in case there is an origin brush and we + // have to recalculate the texinfo + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); + side_brushtextures[nummapbrushsides] = pSideInfo->td; + nummapbrushsides++; + b->numsides++; + +#ifdef VSVMFIO + // Tell Maya We Have Another Side + if ( CVmfImport::GetVmfImporter() ) + { + CVmfImport::GetVmfImporter()->AddSideCallback( + b, side, pSideInfo->td, + pSideInfo->planepts[ 0 ], pSideInfo->planepts[ 1 ], pSideInfo->planepts[ 2 ] ); + } +#endif // VSVMFIO + + } + } + else + { + g_MapError.ReportWarning("plane with no normal"); + } + } + + return(eResult); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : szKey - +// szValue - +// pSideInfo - +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadSideKeyCallback(const char *szKey, const char *szValue, LoadSide_t *pSideInfo) +{ + if (!stricmp(szKey, "plane")) + { + int nRead = sscanf(szValue, "(%f %f %f) (%f %f %f) (%f %f %f)", + &pSideInfo->planepts[0][0], &pSideInfo->planepts[0][1], &pSideInfo->planepts[0][2], + &pSideInfo->planepts[1][0], &pSideInfo->planepts[1][1], &pSideInfo->planepts[1][2], + &pSideInfo->planepts[2][0], &pSideInfo->planepts[2][1], &pSideInfo->planepts[2][2]); + + if (nRead != 9) + { + g_MapError.ReportError("parsing plane definition"); + } + } + else if (!stricmp(szKey, "material")) + { + // Get the material name. + if( g_ReplaceMaterials ) + { + szValue = ReplaceMaterialName( szValue ); + } + + strcpy(pSideInfo->td.name, szValue); + g_MapError.TextureState(szValue); + + // Find default flags and values for this material. + int mt = FindMiptex(pSideInfo->td.name); + pSideInfo->td.flags = textureref[mt].flags; + pSideInfo->td.lightmapWorldUnitsPerLuxel = textureref[mt].lightmapWorldUnitsPerLuxel; + + pSideInfo->pSide->contents = textureref[mt].contents; + pSideInfo->pSide->surf = pSideInfo->td.flags; + } + else if (!stricmp(szKey, "uaxis")) + { + int nRead = sscanf(szValue, "[%f %f %f %f] %f", &pSideInfo->td.UAxis[0], &pSideInfo->td.UAxis[1], &pSideInfo->td.UAxis[2], &pSideInfo->td.shift[0], &pSideInfo->td.textureWorldUnitsPerTexel[0]); + if (nRead != 5) + { + g_MapError.ReportError("parsing U axis definition"); + } + } + else if (!stricmp(szKey, "vaxis")) + { + int nRead = sscanf(szValue, "[%f %f %f %f] %f", &pSideInfo->td.VAxis[0], &pSideInfo->td.VAxis[1], &pSideInfo->td.VAxis[2], &pSideInfo->td.shift[1], &pSideInfo->td.textureWorldUnitsPerTexel[1]); + if (nRead != 5) + { + g_MapError.ReportError("parsing V axis definition"); + } + } + else if (!stricmp(szKey, "lightmapscale")) + { + pSideInfo->td.lightmapWorldUnitsPerLuxel = atoi(szValue); + if (pSideInfo->td.lightmapWorldUnitsPerLuxel == 0.0f) + { + g_MapError.ReportWarning("luxel size of 0"); + pSideInfo->td.lightmapWorldUnitsPerLuxel = g_defaultLuxelSize; + } + pSideInfo->td.lightmapWorldUnitsPerLuxel *= g_luxelScale; + if (pSideInfo->td.lightmapWorldUnitsPerLuxel < g_minLuxelScale) + { + pSideInfo->td.lightmapWorldUnitsPerLuxel = g_minLuxelScale; + } + } + else if (!stricmp(szKey, "contents")) + { + pSideInfo->pSide->contents |= atoi(szValue); + } + else if (!stricmp(szKey, "flags")) + { + pSideInfo->td.flags |= atoi(szValue); + pSideInfo->pSide->surf = pSideInfo->td.flags; + } + else if (!stricmp(szKey, "id")) + { + pSideInfo->pSide->id = atoi( szValue ); + } + else if (!stricmp(szKey, "smoothing_groups")) + { + pSideInfo->pSide->smoothingGroups = atoi( szValue ); + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: Reads the connections chunk of the entity. +// Input : pFile - Chunk file to load from. +// pLoadEntity - Structure to receive loaded entity information. +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadConnectionsCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadConnectionsKeyCallback, pLoadEntity)); +} + + +//----------------------------------------------------------------------------- +// Purpose: Parses a key/value pair from the entity connections chunk. +// Input : szKey - Key indicating the name of the entity output. +// szValue - Comma delimited fields in the following format: +// ,,,, +// pLoadEntity - Structure to receive loaded entity information. +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity) +{ + return g_LoadingMap->LoadConnectionsKeyCallback( szKey, szValue, pLoadEntity ); +} + +ChunkFileResult_t CMapFile::LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity) +{ + // + // Create new input and fill it out. + // + epair_t *pOutput = new epair_t; + + pOutput->key = new char [strlen(szKey) + 1]; + pOutput->value = new char [strlen(szValue) + 1]; + + strcpy(pOutput->key, szKey); + strcpy(pOutput->value, szValue); + + m_ConnectionPairs = new CConnectionPairs( pOutput, m_ConnectionPairs ); + + // + // Append it to the end of epairs list. + // + pOutput->next = NULL; + + if (!pLoadEntity->pEntity->epairs) + { + pLoadEntity->pEntity->epairs = pOutput; + } + else + { + epair_t *ep; + for ( ep = pLoadEntity->pEntity->epairs; ep->next != NULL; ep = ep->next ) + { + } + ep->next = pOutput; + } + + return(ChunkFile_Ok); +} + + +ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity) +{ + return g_LoadingMap->LoadSolidCallback( pFile, pLoadEntity ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pFile - +// pParent - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapFile::LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity) +{ + if (nummapbrushes == MAX_MAP_BRUSHES) + { + g_MapError.ReportError ("nummapbrushes == MAX_MAP_BRUSHES"); + } + + mapbrush_t *b = &mapbrushes[nummapbrushes]; + b->original_sides = &brushsides[nummapbrushsides]; + b->entitynum = num_entities-1; + b->brushnum = nummapbrushes - pLoadEntity->pEntity->firstbrush; + + LoadSide_t SideInfo; + SideInfo.pBrush = b; + SideInfo.nSideIndex = 0; + SideInfo.nBaseContents = pLoadEntity->nBaseContents; + SideInfo.nBaseFlags = pLoadEntity->nBaseFlags; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler("side", (ChunkHandler_t)::LoadSideCallback, &SideInfo); + + // + // Read the solid chunk. + // + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadSolidKeyCallback, b); + pFile->PopHandlers(); + + if (eResult == ChunkFile_Ok) + { + // get the content for the entire brush + b->contents = BrushContents (b); + + // allow detail brushes to be removed + if (nodetail && (b->contents & CONTENTS_DETAIL) && !HasDispInfo( b ) ) + { + b->numsides = 0; + return(ChunkFile_Ok); + } + + // allow water brushes to be removed + if (nowater && (b->contents & MASK_WATER) ) + { + b->numsides = 0; + return(ChunkFile_Ok); + } + + // create windings for sides and bounds for brush + MakeBrushWindings (b); + + // + // brushes that will not be visible at all will never be + // used as bsp splitters + // + // only do this on the world entity + // + if ( b->entitynum == 0 ) + { + if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + { + if ( g_ClipTexinfo < 0 ) + { + g_ClipTexinfo = b->original_sides[0].texinfo; + } + c_clipbrushes++; + for (int i=0 ; inumsides ; i++) + { + b->original_sides[i].texinfo = TEXINFO_NODE; + } + } + } + + // + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity. After the entire entity is parsed, + // the planenums and texinfos will be adjusted for + // the origin brush + // + if (b->contents & CONTENTS_ORIGIN) + { + char string[32]; + Vector origin; + + if (num_entities == 1) + { + Error("Brush %i: origin brushes not allowed in world", b->id); + } + + VectorAdd (b->mins, b->maxs, origin); + VectorScale (origin, 0.5, origin); + + sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); + SetKeyValue (&entities[b->entitynum], "origin", string); + + VectorCopy (origin, entities[b->entitynum].origin); + + // don't keep this brush + b->numsides = 0; + + return(ChunkFile_Ok); + } + +#ifdef VSVMFIO + if ( CVmfImport::GetVmfImporter() ) + { + CVmfImport::GetVmfImporter()->MapBrushToMayaCallback( b ); + } +#endif // VSVMFIO + + // + // find a map brushes with displacement surfaces and remove them from the "world" + // + if( HasDispInfo( b ) ) + { + // add the base face data to the displacement surface + DispGetFaceInfo( b ); + + // don't keep this brush + b->numsides = 0; + + return( ChunkFile_Ok ); + } + + AddBrushBevels (b); + + nummapbrushes++; + pLoadEntity->pEntity->numbrushes++; + } + else + { + return eResult; + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pFile - +// parent - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadSolidKeyCallback(const char *szKey, const char *szValue, mapbrush_t *pLoadBrush) +{ + if (!stricmp(szKey, "id")) + { + pLoadBrush->id = atoi(szValue); + g_MapError.BrushState(pLoadBrush->id); + } + + return ChunkFile_Ok; +} + + +/* +================ +TestExpandBrushes + +Expands all the brush planes and saves a new map out +================ +*/ +void CMapFile::TestExpandBrushes (void) +{ + FILE *f; + side_t *s; + int i, j, bn; + winding_t *w; + char *name = "expanded.map"; + mapbrush_t *brush; + vec_t dist; + + Msg ("writing %s\n", name); + f = fopen (name, "wb"); + if (!f) + Error ("Can't write %s\b", name); + + fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); + fprintf( f, "\"mapversion\" \"220\"\n\"sounds\" \"1\"\n\"MaxRange\" \"4096\"\n\"mapversion\" \"220\"\n\"wad\" \"vert.wad;dev.wad;generic.wad;spire.wad;urb.wad;cit.wad;water.wad\"\n" ); + + + for (bn=0 ; bnnumsides ; i++) + { + s = brush->original_sides + i; + dist = mapplanes[s->planenum].dist; + for (j=0 ; j<3 ; j++) + dist += fabs( 16 * mapplanes[s->planenum].normal[j] ); + + w = BaseWindingForPlane (mapplanes[s->planenum].normal, dist); + + fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); + + fprintf (f, "%s [ 0 0 1 -512 ] [ 0 -1 0 -256 ] 0 1 1 \n", + TexDataStringTable_GetString( GetTexData( texinfo[s->texinfo].texdata )->nameStringTableID ) ); + + FreeWinding (w); + } + fprintf (f, "}\n"); + } + fprintf (f, "}\n"); + + fclose (f); + + Error ("can't proceed after expanding brushes"); +} + + +//----------------------------------------------------------------------------- +// Purpose: load in the displacement info "chunk" from the .map file into the +// vbsp map displacement info data structure +// Output: return the pointer to the displacement map +//----------------------------------------------------------------------------- +mapdispinfo_t *ParseDispInfoChunk( void ) +{ + int i, j; + int vertCount; + mapdispinfo_t *pMapDispInfo; + + // + // check to see if we exceeded the maximum displacement info list size + // + if( nummapdispinfo > MAX_MAP_DISPINFO ) + g_MapError.ReportError( "ParseDispInfoChunk: nummapdispinfo > MAX_MAP_DISPINFO"); + + // get a pointer to the next available displacement info slot + pMapDispInfo = &mapdispinfo[nummapdispinfo]; + nummapdispinfo++; + + // + // get the chunk opener - "{" + // + GetToken( false ); + if( strcmp( token, "{" ) ) + g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - {" ); + + // + // + // get the displacement info attribs + // + // + + // power + GetToken( true ); + pMapDispInfo->power = atoi( token ); + + // u and v mapping axes + for( i = 0; i < 2; i++ ) + { + GetToken( false ); + if( strcmp( token, "[" ) ) + g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - [" ); + + for( j = 0; j < 3; j++ ) + { + GetToken( false ); + + if( i == 0 ) + { + pMapDispInfo->uAxis[j] = atof( token ); + } + else + { + pMapDispInfo->vAxis[j] = atof( token ); + } + } + + GetToken( false ); + if( strcmp( token, "]" ) ) + g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - ]" ); + } + + // max displacement value + if( g_nMapFileVersion < 350 ) + { + GetToken( false ); + pMapDispInfo->maxDispDist = atof( token ); + } + + // minimum tesselation value + GetToken( false ); + pMapDispInfo->minTess = atoi( token ); + + // light smoothing angle + GetToken( false ); + pMapDispInfo->smoothingAngle = atof( token ); + + // + // get the displacement info displacement normals + // + GetToken( true ); + pMapDispInfo->vectorDisps[0][0] = atof( token ); + GetToken( false ); + pMapDispInfo->vectorDisps[0][1] = atof( token ); + GetToken( false ); + pMapDispInfo->vectorDisps[0][2] = atof( token ); + + vertCount = ( ( ( 1 << pMapDispInfo->power ) + 1 ) * ( ( 1 << pMapDispInfo->power ) + 1 ) ); + for( i = 1; i < vertCount; i++ ) + { + GetToken( false ); + pMapDispInfo->vectorDisps[i][0] = atof( token ); + GetToken( false ); + pMapDispInfo->vectorDisps[i][1] = atof( token ); + GetToken( false ); + pMapDispInfo->vectorDisps[i][2] = atof( token ); + } + + // + // get the displacement info displacement values + // + GetToken( true ); + pMapDispInfo->dispDists[0] = atof( token ); + + for( i = 1; i < vertCount; i++ ) + { + GetToken( false ); + pMapDispInfo->dispDists[i] = atof( token ); + } + + // + // get the chunk closer - "}" + // + GetToken( true ); + if( strcmp( token, "}" ) ) + g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - }" ); + + // return the index of the displacement info slot + return pMapDispInfo; +} + + diff --git a/mp/src/utils/vbsp/map.h b/mp/src/utils/vbsp/map.h index 44b1d934..c7b0e5b8 100644 --- a/mp/src/utils/vbsp/map.h +++ b/mp/src/utils/vbsp/map.h @@ -1,18 +1,18 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef MAP_H -#define MAP_H -#ifdef _WIN32 -#pragma once -#endif - - -// All the brush sides referenced by info_no_dynamic_shadow entities. -extern CUtlVector g_NoDynamicShadowSides; - - -#endif // MAP_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MAP_H +#define MAP_H +#ifdef _WIN32 +#pragma once +#endif + + +// All the brush sides referenced by info_no_dynamic_shadow entities. +extern CUtlVector g_NoDynamicShadowSides; + + +#endif // MAP_H diff --git a/mp/src/utils/vbsp/materialpatch.cpp b/mp/src/utils/vbsp/materialpatch.cpp index e05b979d..96a30edb 100644 --- a/mp/src/utils/vbsp/materialpatch.cpp +++ b/mp/src/utils/vbsp/materialpatch.cpp @@ -1,440 +1,440 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#include "vbsp.h" -#include "UtlBuffer.h" -#include "utlsymbol.h" -#include "utlrbtree.h" -#include "KeyValues.h" -#include "bsplib.h" -#include "materialpatch.h" -#include "tier1/strtools.h" - -// case insensitive -static CUtlSymbolTable s_SymbolTable( 0, 32, true ); - -struct NameTranslationLookup_t -{ - CUtlSymbol m_OriginalFileName; - CUtlSymbol m_PatchFileName; -}; - -static bool NameTranslationLessFunc( NameTranslationLookup_t const& src1, - NameTranslationLookup_t const& src2 ) -{ - return src1.m_PatchFileName < src2.m_PatchFileName; -} - -CUtlRBTree s_MapPatchedMatToOriginalMat( 0, 256, NameTranslationLessFunc ); - -void AddNewTranslation( const char *pOriginalMaterialName, const char *pNewMaterialName ) -{ - NameTranslationLookup_t newEntry; - - newEntry.m_OriginalFileName = s_SymbolTable.AddString( pOriginalMaterialName ); - newEntry.m_PatchFileName = s_SymbolTable.AddString( pNewMaterialName ); - - s_MapPatchedMatToOriginalMat.Insert( newEntry ); -} - -const char *GetOriginalMaterialNameForPatchedMaterial( const char *pPatchMaterialName ) -{ - const char *pRetName = NULL; - int id; - NameTranslationLookup_t lookup; - lookup.m_PatchFileName = s_SymbolTable.AddString( pPatchMaterialName ); - do - { - id = s_MapPatchedMatToOriginalMat.Find( lookup ); - if( id >= 0 ) - { - NameTranslationLookup_t &found = s_MapPatchedMatToOriginalMat[id]; - lookup.m_PatchFileName = found.m_OriginalFileName; - pRetName = s_SymbolTable.String( found.m_OriginalFileName ); - } - } while( id >= 0 ); - if( !pRetName ) - { - // This isn't a patched material, so just return the original name. - return pPatchMaterialName; - } - return pRetName; -} - - -void CreateMaterialPatchRecursive( KeyValues *pOriginalKeyValues, KeyValues *pPatchKeyValues, int nKeys, const MaterialPatchInfo_t *pInfo ) -{ - int i; - for( i = 0; i < nKeys; i++ ) - { - const char *pVal = pOriginalKeyValues->GetString( pInfo[i].m_pKey, NULL ); - if( !pVal ) - continue; - if( pInfo[i].m_pRequiredOriginalValue && Q_stricmp( pVal, pInfo[i].m_pRequiredOriginalValue ) != 0 ) - continue; - pPatchKeyValues->SetString( pInfo[i].m_pKey, pInfo[i].m_pValue ); - } - KeyValues *pScan; - for( pScan = pOriginalKeyValues->GetFirstTrueSubKey(); pScan; pScan = pScan->GetNextTrueSubKey() ) - { - CreateMaterialPatchRecursive( pScan, pPatchKeyValues->FindKey( pScan->GetName(), true ), nKeys, pInfo ); - } -} - -//----------------------------------------------------------------------------- -// A version which allows you to patch multiple key values -//----------------------------------------------------------------------------- -void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, - int nKeys, const MaterialPatchInfo_t *pInfo, MaterialPatchType_t nPatchType ) -{ - char pOldVMTFile[ 512 ]; - char pNewVMTFile[ 512 ]; - - AddNewTranslation( pOriginalMaterialName, pNewMaterialName ); - - Q_snprintf( pOldVMTFile, 512, "materials/%s.vmt", pOriginalMaterialName ); - Q_snprintf( pNewVMTFile, 512, "materials/%s.vmt", pNewMaterialName ); - -// printf( "Creating material patch file %s which points at %s\n", newVMTFile, oldVMTFile ); - - KeyValues *kv = new KeyValues( "patch" ); - if ( !kv ) - { - Error( "Couldn't allocate KeyValues for %s!!!", pNewMaterialName ); - } - - kv->SetString( "include", pOldVMTFile ); - - const char *pSectionName = (nPatchType == PATCH_INSERT) ? "insert" : "replace"; - KeyValues *section = kv->FindKey( pSectionName, true ); - - if( nPatchType == PATCH_REPLACE ) - { - char name[512]; - Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pOriginalMaterialName ) ); - KeyValues *origkv = new KeyValues( "blah" ); - - if ( !origkv->LoadFromFile( g_pFileSystem, name ) ) - { - origkv->deleteThis(); - Assert( 0 ); - return; - } - - CreateMaterialPatchRecursive( origkv, section, nKeys, pInfo ); - origkv->deleteThis(); - } - else - { - for ( int i = 0; i < nKeys; ++i ) - { - section->SetString( pInfo[i].m_pKey, pInfo[i].m_pValue ); - } - } - - // Write patched .vmt into a memory buffer - CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); - kv->RecursiveSaveToFile( buf, 0 ); - - // Add to pak file for this .bsp - AddBufferToPak( GetPakFile(), pNewVMTFile, (void*)buf.Base(), buf.TellPut(), true ); - - // Cleanup - kv->deleteThis(); -} - - -//----------------------------------------------------------------------------- -// Patches a single keyvalue in a material -//----------------------------------------------------------------------------- -void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, - const char *pNewKey, const char *pNewValue, MaterialPatchType_t nPatchType ) -{ - MaterialPatchInfo_t info; - info.m_pKey = pNewKey; - info.m_pValue = pNewValue; - CreateMaterialPatch( pOriginalMaterialName, pNewMaterialName, 1, &info, nPatchType ); -} - - -//----------------------------------------------------------------------------- -// Scan material + all subsections for key -//----------------------------------------------------------------------------- -static bool DoesMaterialHaveKey( KeyValues *pKeyValues, const char *pKeyName ) -{ - const char *pVal; - pVal = pKeyValues->GetString( pKeyName, NULL ); - if ( pVal != NULL ) - return true; - - for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() ) - { - if ( DoesMaterialHaveKey( pSubKey, pKeyName) ) - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -// Scan material + all subsections for key/value pair -//----------------------------------------------------------------------------- -static bool DoesMaterialHaveKeyValuePair( KeyValues *pKeyValues, const char *pKeyName, const char *pSearchValue ) -{ - const char *pVal; - pVal = pKeyValues->GetString( pKeyName, NULL ); - if ( pVal != NULL && ( Q_stricmp( pSearchValue, pVal ) == 0 ) ) - return true; - - for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() ) - { - if ( DoesMaterialHaveKeyValuePair( pSubKey, pKeyName, pSearchValue ) ) - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -// Scan material + all subsections for key -//----------------------------------------------------------------------------- -bool DoesMaterialHaveKey( const char *pMaterialName, const char *pKeyName ) -{ - char name[512]; - Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) ); - KeyValues *kv = new KeyValues( "blah" ); - - if ( !kv->LoadFromFile( g_pFileSystem, name ) ) - { - kv->deleteThis(); - return NULL; - } - - bool retVal = DoesMaterialHaveKey( kv, pKeyName ); - - kv->deleteThis(); - return retVal; -} - -//----------------------------------------------------------------------------- -// Scan material + all subsections for key/value pair -//----------------------------------------------------------------------------- -bool DoesMaterialHaveKeyValuePair( const char *pMaterialName, const char *pKeyName, const char *pSearchValue ) -{ - char name[512]; - Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) ); - KeyValues *kv = new KeyValues( "blah" ); - - if ( !kv->LoadFromFile( g_pFileSystem, name ) ) - { - kv->deleteThis(); - return NULL; - } - - bool retVal = DoesMaterialHaveKeyValuePair( kv, pKeyName, pSearchValue ); - - kv->deleteThis(); - return retVal; -} - -//----------------------------------------------------------------------------- -// Gets a material value from a material. Ignores all patches -//----------------------------------------------------------------------------- -bool GetValueFromMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ) -{ - char name[512]; - Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) ); - KeyValues *kv = new KeyValues( "blah" ); - - if ( !kv->LoadFromFile( g_pFileSystem, name ) ) - { -// Assert( 0 ); - kv->deleteThis(); - return NULL; - } - - const char *pTmpValue = kv->GetString( pKey, NULL ); - if( pTmpValue ) - { - Q_strncpy( pValue, pTmpValue, len ); - } - - kv->deleteThis(); - return ( pTmpValue != NULL ); -} - - -//----------------------------------------------------------------------------- -// Finds the original material associated with a patched material -//----------------------------------------------------------------------------- -MaterialSystemMaterial_t FindOriginalMaterial( const char *materialName, bool *pFound, bool bComplain ) -{ - MaterialSystemMaterial_t matID; - matID = FindMaterial( GetOriginalMaterialNameForPatchedMaterial( materialName ), pFound, bComplain ); - return matID; -} - - -//----------------------------------------------------------------------------- -// Load keyvalues from the local pack file, or from a file -//----------------------------------------------------------------------------- -bool LoadKeyValuesFromPackOrFile( const char *pFileName, KeyValues *pKeyValues ) -{ - CUtlBuffer buf; - if ( ReadFileFromPak( GetPakFile(), pFileName, true, buf ) ) - { - return pKeyValues->LoadFromBuffer( pFileName, buf ); - } - - return pKeyValues->LoadFromFile( g_pFileSystem, pFileName ); -} - - -//----------------------------------------------------------------------------- -// VMT parser -//----------------------------------------------------------------------------- -static void InsertKeyValues( KeyValues &dst, KeyValues& src, bool bCheckForExistence ) -{ - KeyValues *pSrcVar = src.GetFirstSubKey(); - while( pSrcVar ) - { - if ( !bCheckForExistence || dst.FindKey( pSrcVar->GetName() ) ) - { - switch( pSrcVar->GetDataType() ) - { - case KeyValues::TYPE_STRING: - dst.SetString( pSrcVar->GetName(), pSrcVar->GetString() ); - break; - case KeyValues::TYPE_INT: - dst.SetInt( pSrcVar->GetName(), pSrcVar->GetInt() ); - break; - case KeyValues::TYPE_FLOAT: - dst.SetFloat( pSrcVar->GetName(), pSrcVar->GetFloat() ); - break; - case KeyValues::TYPE_PTR: - dst.SetPtr( pSrcVar->GetName(), pSrcVar->GetPtr() ); - break; - } - } - pSrcVar = pSrcVar->GetNextKey(); - } -} - -static void ExpandPatchFile( KeyValues &keyValues ) -{ - int nCount = 0; - while( nCount < 10 && stricmp( keyValues.GetName(), "patch" ) == 0 ) - { -// WriteKeyValuesToFile( "patch.txt", keyValues ); - const char *pIncludeFileName = keyValues.GetString( "include" ); - if( !pIncludeFileName ) - return; - - KeyValues * includeKeyValues = new KeyValues( "vmt" ); - int nBufLen = Q_strlen( pIncludeFileName ) + Q_strlen( "materials/.vmt" ) + 1; - char *pFileName = ( char * )stackalloc( nBufLen ); - Q_strncpy( pFileName, pIncludeFileName, nBufLen ); - bool bSuccess = LoadKeyValuesFromPackOrFile( pFileName, includeKeyValues ); - if ( !bSuccess ) - { - includeKeyValues->deleteThis(); - return; - } - - KeyValues *pInsertSection = keyValues.FindKey( "insert" ); - if( pInsertSection ) - { - InsertKeyValues( *includeKeyValues, *pInsertSection, false ); - keyValues = *includeKeyValues; - } - - KeyValues *pReplaceSection = keyValues.FindKey( "replace" ); - if( pReplaceSection ) - { - InsertKeyValues( *includeKeyValues, *pReplaceSection, true ); - keyValues = *includeKeyValues; - } - - // Could add other commands here, like "delete", "rename", etc. - - includeKeyValues->deleteThis(); - nCount++; - } - - if( nCount >= 10 ) - { - Warning( "Infinite recursion in patch file?\n" ); - } -} - -KeyValues *LoadMaterialKeyValues( const char *pMaterialName, unsigned int nFlags ) -{ - // Load the underlying file - KeyValues *kv = new KeyValues( "blah" ); - - char pFullMaterialName[512]; - Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName ); - if ( !LoadKeyValuesFromPackOrFile( pFullMaterialName, kv ) ) - { - // Assert( 0 ); - kv->deleteThis(); - return NULL; - } - - if( nFlags & LOAD_MATERIAL_KEY_VALUES_FLAGS_EXPAND_PATCH ) - { - ExpandPatchFile( *kv ); - } - - return kv; -} - -void WriteMaterialKeyValuesToPak( const char *pMaterialName, KeyValues *kv ) -{ - char pFullMaterialName[512]; - Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName ); - - // Write patched .vmt into a memory buffer - CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); - kv->RecursiveSaveToFile( buf, 0 ); - - // Add to pak file for this .bsp - AddBufferToPak( GetPakFile(), pFullMaterialName, (void*)buf.Base(), buf.TellPut(), true ); - - // Cleanup - kv->deleteThis(); -} - - -//----------------------------------------------------------------------------- -// Gets a keyvalue from a *patched* material -//----------------------------------------------------------------------------- -bool GetValueFromPatchedMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ) -{ - // Load the underlying file so that we can check if env_cubemap is in there. - KeyValues *kv = new KeyValues( "blah" ); - - char pFullMaterialName[512]; - Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName ); - if ( !LoadKeyValuesFromPackOrFile( pFullMaterialName, kv ) ) - { -// Assert( 0 ); - kv->deleteThis(); - return NULL; - } - - ExpandPatchFile( *kv ); - - const char *pTmpValue = kv->GetString( pKey, NULL ); - if( pTmpValue ) - { - Q_strncpy( pValue, pTmpValue, len ); - } - - kv->deleteThis(); - return ( pTmpValue != NULL ); -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "vbsp.h" +#include "UtlBuffer.h" +#include "utlsymbol.h" +#include "utlrbtree.h" +#include "KeyValues.h" +#include "bsplib.h" +#include "materialpatch.h" +#include "tier1/strtools.h" + +// case insensitive +static CUtlSymbolTable s_SymbolTable( 0, 32, true ); + +struct NameTranslationLookup_t +{ + CUtlSymbol m_OriginalFileName; + CUtlSymbol m_PatchFileName; +}; + +static bool NameTranslationLessFunc( NameTranslationLookup_t const& src1, + NameTranslationLookup_t const& src2 ) +{ + return src1.m_PatchFileName < src2.m_PatchFileName; +} + +CUtlRBTree s_MapPatchedMatToOriginalMat( 0, 256, NameTranslationLessFunc ); + +void AddNewTranslation( const char *pOriginalMaterialName, const char *pNewMaterialName ) +{ + NameTranslationLookup_t newEntry; + + newEntry.m_OriginalFileName = s_SymbolTable.AddString( pOriginalMaterialName ); + newEntry.m_PatchFileName = s_SymbolTable.AddString( pNewMaterialName ); + + s_MapPatchedMatToOriginalMat.Insert( newEntry ); +} + +const char *GetOriginalMaterialNameForPatchedMaterial( const char *pPatchMaterialName ) +{ + const char *pRetName = NULL; + int id; + NameTranslationLookup_t lookup; + lookup.m_PatchFileName = s_SymbolTable.AddString( pPatchMaterialName ); + do + { + id = s_MapPatchedMatToOriginalMat.Find( lookup ); + if( id >= 0 ) + { + NameTranslationLookup_t &found = s_MapPatchedMatToOriginalMat[id]; + lookup.m_PatchFileName = found.m_OriginalFileName; + pRetName = s_SymbolTable.String( found.m_OriginalFileName ); + } + } while( id >= 0 ); + if( !pRetName ) + { + // This isn't a patched material, so just return the original name. + return pPatchMaterialName; + } + return pRetName; +} + + +void CreateMaterialPatchRecursive( KeyValues *pOriginalKeyValues, KeyValues *pPatchKeyValues, int nKeys, const MaterialPatchInfo_t *pInfo ) +{ + int i; + for( i = 0; i < nKeys; i++ ) + { + const char *pVal = pOriginalKeyValues->GetString( pInfo[i].m_pKey, NULL ); + if( !pVal ) + continue; + if( pInfo[i].m_pRequiredOriginalValue && Q_stricmp( pVal, pInfo[i].m_pRequiredOriginalValue ) != 0 ) + continue; + pPatchKeyValues->SetString( pInfo[i].m_pKey, pInfo[i].m_pValue ); + } + KeyValues *pScan; + for( pScan = pOriginalKeyValues->GetFirstTrueSubKey(); pScan; pScan = pScan->GetNextTrueSubKey() ) + { + CreateMaterialPatchRecursive( pScan, pPatchKeyValues->FindKey( pScan->GetName(), true ), nKeys, pInfo ); + } +} + +//----------------------------------------------------------------------------- +// A version which allows you to patch multiple key values +//----------------------------------------------------------------------------- +void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, + int nKeys, const MaterialPatchInfo_t *pInfo, MaterialPatchType_t nPatchType ) +{ + char pOldVMTFile[ 512 ]; + char pNewVMTFile[ 512 ]; + + AddNewTranslation( pOriginalMaterialName, pNewMaterialName ); + + Q_snprintf( pOldVMTFile, 512, "materials/%s.vmt", pOriginalMaterialName ); + Q_snprintf( pNewVMTFile, 512, "materials/%s.vmt", pNewMaterialName ); + +// printf( "Creating material patch file %s which points at %s\n", newVMTFile, oldVMTFile ); + + KeyValues *kv = new KeyValues( "patch" ); + if ( !kv ) + { + Error( "Couldn't allocate KeyValues for %s!!!", pNewMaterialName ); + } + + kv->SetString( "include", pOldVMTFile ); + + const char *pSectionName = (nPatchType == PATCH_INSERT) ? "insert" : "replace"; + KeyValues *section = kv->FindKey( pSectionName, true ); + + if( nPatchType == PATCH_REPLACE ) + { + char name[512]; + Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pOriginalMaterialName ) ); + KeyValues *origkv = new KeyValues( "blah" ); + + if ( !origkv->LoadFromFile( g_pFileSystem, name ) ) + { + origkv->deleteThis(); + Assert( 0 ); + return; + } + + CreateMaterialPatchRecursive( origkv, section, nKeys, pInfo ); + origkv->deleteThis(); + } + else + { + for ( int i = 0; i < nKeys; ++i ) + { + section->SetString( pInfo[i].m_pKey, pInfo[i].m_pValue ); + } + } + + // Write patched .vmt into a memory buffer + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + kv->RecursiveSaveToFile( buf, 0 ); + + // Add to pak file for this .bsp + AddBufferToPak( GetPakFile(), pNewVMTFile, (void*)buf.Base(), buf.TellPut(), true ); + + // Cleanup + kv->deleteThis(); +} + + +//----------------------------------------------------------------------------- +// Patches a single keyvalue in a material +//----------------------------------------------------------------------------- +void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, + const char *pNewKey, const char *pNewValue, MaterialPatchType_t nPatchType ) +{ + MaterialPatchInfo_t info; + info.m_pKey = pNewKey; + info.m_pValue = pNewValue; + CreateMaterialPatch( pOriginalMaterialName, pNewMaterialName, 1, &info, nPatchType ); +} + + +//----------------------------------------------------------------------------- +// Scan material + all subsections for key +//----------------------------------------------------------------------------- +static bool DoesMaterialHaveKey( KeyValues *pKeyValues, const char *pKeyName ) +{ + const char *pVal; + pVal = pKeyValues->GetString( pKeyName, NULL ); + if ( pVal != NULL ) + return true; + + for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() ) + { + if ( DoesMaterialHaveKey( pSubKey, pKeyName) ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Scan material + all subsections for key/value pair +//----------------------------------------------------------------------------- +static bool DoesMaterialHaveKeyValuePair( KeyValues *pKeyValues, const char *pKeyName, const char *pSearchValue ) +{ + const char *pVal; + pVal = pKeyValues->GetString( pKeyName, NULL ); + if ( pVal != NULL && ( Q_stricmp( pSearchValue, pVal ) == 0 ) ) + return true; + + for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() ) + { + if ( DoesMaterialHaveKeyValuePair( pSubKey, pKeyName, pSearchValue ) ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Scan material + all subsections for key +//----------------------------------------------------------------------------- +bool DoesMaterialHaveKey( const char *pMaterialName, const char *pKeyName ) +{ + char name[512]; + Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) ); + KeyValues *kv = new KeyValues( "blah" ); + + if ( !kv->LoadFromFile( g_pFileSystem, name ) ) + { + kv->deleteThis(); + return NULL; + } + + bool retVal = DoesMaterialHaveKey( kv, pKeyName ); + + kv->deleteThis(); + return retVal; +} + +//----------------------------------------------------------------------------- +// Scan material + all subsections for key/value pair +//----------------------------------------------------------------------------- +bool DoesMaterialHaveKeyValuePair( const char *pMaterialName, const char *pKeyName, const char *pSearchValue ) +{ + char name[512]; + Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) ); + KeyValues *kv = new KeyValues( "blah" ); + + if ( !kv->LoadFromFile( g_pFileSystem, name ) ) + { + kv->deleteThis(); + return NULL; + } + + bool retVal = DoesMaterialHaveKeyValuePair( kv, pKeyName, pSearchValue ); + + kv->deleteThis(); + return retVal; +} + +//----------------------------------------------------------------------------- +// Gets a material value from a material. Ignores all patches +//----------------------------------------------------------------------------- +bool GetValueFromMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ) +{ + char name[512]; + Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) ); + KeyValues *kv = new KeyValues( "blah" ); + + if ( !kv->LoadFromFile( g_pFileSystem, name ) ) + { +// Assert( 0 ); + kv->deleteThis(); + return NULL; + } + + const char *pTmpValue = kv->GetString( pKey, NULL ); + if( pTmpValue ) + { + Q_strncpy( pValue, pTmpValue, len ); + } + + kv->deleteThis(); + return ( pTmpValue != NULL ); +} + + +//----------------------------------------------------------------------------- +// Finds the original material associated with a patched material +//----------------------------------------------------------------------------- +MaterialSystemMaterial_t FindOriginalMaterial( const char *materialName, bool *pFound, bool bComplain ) +{ + MaterialSystemMaterial_t matID; + matID = FindMaterial( GetOriginalMaterialNameForPatchedMaterial( materialName ), pFound, bComplain ); + return matID; +} + + +//----------------------------------------------------------------------------- +// Load keyvalues from the local pack file, or from a file +//----------------------------------------------------------------------------- +bool LoadKeyValuesFromPackOrFile( const char *pFileName, KeyValues *pKeyValues ) +{ + CUtlBuffer buf; + if ( ReadFileFromPak( GetPakFile(), pFileName, true, buf ) ) + { + return pKeyValues->LoadFromBuffer( pFileName, buf ); + } + + return pKeyValues->LoadFromFile( g_pFileSystem, pFileName ); +} + + +//----------------------------------------------------------------------------- +// VMT parser +//----------------------------------------------------------------------------- +static void InsertKeyValues( KeyValues &dst, KeyValues& src, bool bCheckForExistence ) +{ + KeyValues *pSrcVar = src.GetFirstSubKey(); + while( pSrcVar ) + { + if ( !bCheckForExistence || dst.FindKey( pSrcVar->GetName() ) ) + { + switch( pSrcVar->GetDataType() ) + { + case KeyValues::TYPE_STRING: + dst.SetString( pSrcVar->GetName(), pSrcVar->GetString() ); + break; + case KeyValues::TYPE_INT: + dst.SetInt( pSrcVar->GetName(), pSrcVar->GetInt() ); + break; + case KeyValues::TYPE_FLOAT: + dst.SetFloat( pSrcVar->GetName(), pSrcVar->GetFloat() ); + break; + case KeyValues::TYPE_PTR: + dst.SetPtr( pSrcVar->GetName(), pSrcVar->GetPtr() ); + break; + } + } + pSrcVar = pSrcVar->GetNextKey(); + } +} + +static void ExpandPatchFile( KeyValues &keyValues ) +{ + int nCount = 0; + while( nCount < 10 && stricmp( keyValues.GetName(), "patch" ) == 0 ) + { +// WriteKeyValuesToFile( "patch.txt", keyValues ); + const char *pIncludeFileName = keyValues.GetString( "include" ); + if( !pIncludeFileName ) + return; + + KeyValues * includeKeyValues = new KeyValues( "vmt" ); + int nBufLen = Q_strlen( pIncludeFileName ) + Q_strlen( "materials/.vmt" ) + 1; + char *pFileName = ( char * )stackalloc( nBufLen ); + Q_strncpy( pFileName, pIncludeFileName, nBufLen ); + bool bSuccess = LoadKeyValuesFromPackOrFile( pFileName, includeKeyValues ); + if ( !bSuccess ) + { + includeKeyValues->deleteThis(); + return; + } + + KeyValues *pInsertSection = keyValues.FindKey( "insert" ); + if( pInsertSection ) + { + InsertKeyValues( *includeKeyValues, *pInsertSection, false ); + keyValues = *includeKeyValues; + } + + KeyValues *pReplaceSection = keyValues.FindKey( "replace" ); + if( pReplaceSection ) + { + InsertKeyValues( *includeKeyValues, *pReplaceSection, true ); + keyValues = *includeKeyValues; + } + + // Could add other commands here, like "delete", "rename", etc. + + includeKeyValues->deleteThis(); + nCount++; + } + + if( nCount >= 10 ) + { + Warning( "Infinite recursion in patch file?\n" ); + } +} + +KeyValues *LoadMaterialKeyValues( const char *pMaterialName, unsigned int nFlags ) +{ + // Load the underlying file + KeyValues *kv = new KeyValues( "blah" ); + + char pFullMaterialName[512]; + Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName ); + if ( !LoadKeyValuesFromPackOrFile( pFullMaterialName, kv ) ) + { + // Assert( 0 ); + kv->deleteThis(); + return NULL; + } + + if( nFlags & LOAD_MATERIAL_KEY_VALUES_FLAGS_EXPAND_PATCH ) + { + ExpandPatchFile( *kv ); + } + + return kv; +} + +void WriteMaterialKeyValuesToPak( const char *pMaterialName, KeyValues *kv ) +{ + char pFullMaterialName[512]; + Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName ); + + // Write patched .vmt into a memory buffer + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + kv->RecursiveSaveToFile( buf, 0 ); + + // Add to pak file for this .bsp + AddBufferToPak( GetPakFile(), pFullMaterialName, (void*)buf.Base(), buf.TellPut(), true ); + + // Cleanup + kv->deleteThis(); +} + + +//----------------------------------------------------------------------------- +// Gets a keyvalue from a *patched* material +//----------------------------------------------------------------------------- +bool GetValueFromPatchedMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ) +{ + // Load the underlying file so that we can check if env_cubemap is in there. + KeyValues *kv = new KeyValues( "blah" ); + + char pFullMaterialName[512]; + Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName ); + if ( !LoadKeyValuesFromPackOrFile( pFullMaterialName, kv ) ) + { +// Assert( 0 ); + kv->deleteThis(); + return NULL; + } + + ExpandPatchFile( *kv ); + + const char *pTmpValue = kv->GetString( pKey, NULL ); + if( pTmpValue ) + { + Q_strncpy( pValue, pTmpValue, len ); + } + + kv->deleteThis(); + return ( pTmpValue != NULL ); +} diff --git a/mp/src/utils/vbsp/materialpatch.h b/mp/src/utils/vbsp/materialpatch.h index 334fe013..f04a95d9 100644 --- a/mp/src/utils/vbsp/materialpatch.h +++ b/mp/src/utils/vbsp/materialpatch.h @@ -1,60 +1,60 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef MATERIALPATCH_H -#define MATERIALPATCH_H -#ifdef _WIN32 -#pragma once -#endif - -#include "utilmatlib.h" - -struct MaterialPatchInfo_t -{ - const char *m_pKey; - const char *m_pRequiredOriginalValue; // NULL if you don't require one. - const char *m_pValue; - MaterialPatchInfo_t() - { - memset( this, 0, sizeof( *this ) ); - } -}; - -enum MaterialPatchType_t -{ - PATCH_INSERT = 0, // Add the key no matter what - PATCH_REPLACE, // Add the key only if it exists -}; - -void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, - const char *pNewKey, const char *pNewValue, MaterialPatchType_t nPatchType ); - -// A version which allows you to use multiple key values -void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, - int nKeys, const MaterialPatchInfo_t *pInfo, MaterialPatchType_t nPatchType ); - -// This gets a keyvalue from the *unpatched* version of the passed-in material -bool GetValueFromMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ); - -// Gets a keyvalue from a *patched* material -bool GetValueFromPatchedMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ); - -const char *GetOriginalMaterialNameForPatchedMaterial( const char *pPatchMaterialName ); - -MaterialSystemMaterial_t FindOriginalMaterial( const char *materialName, bool *pFound, bool bComplain = true ); - -bool DoesMaterialHaveKeyValuePair( const char *pMaterialName, const char *pKeyName, const char *pSearchValue ); -bool DoesMaterialHaveKey( const char *pMaterialName, const char *pKeyName ); - -enum LoadMaterialKeyValuesFlags_t -{ - LOAD_MATERIAL_KEY_VALUES_FLAGS_EXPAND_PATCH = 1, -}; - -KeyValues *LoadMaterialKeyValues( const char *pMaterialName, unsigned int nFlags ); -void WriteMaterialKeyValuesToPak( const char *pMaterialName, KeyValues *kv ); - -#endif // MATERIALPATCH_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MATERIALPATCH_H +#define MATERIALPATCH_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utilmatlib.h" + +struct MaterialPatchInfo_t +{ + const char *m_pKey; + const char *m_pRequiredOriginalValue; // NULL if you don't require one. + const char *m_pValue; + MaterialPatchInfo_t() + { + memset( this, 0, sizeof( *this ) ); + } +}; + +enum MaterialPatchType_t +{ + PATCH_INSERT = 0, // Add the key no matter what + PATCH_REPLACE, // Add the key only if it exists +}; + +void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, + const char *pNewKey, const char *pNewValue, MaterialPatchType_t nPatchType ); + +// A version which allows you to use multiple key values +void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, + int nKeys, const MaterialPatchInfo_t *pInfo, MaterialPatchType_t nPatchType ); + +// This gets a keyvalue from the *unpatched* version of the passed-in material +bool GetValueFromMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ); + +// Gets a keyvalue from a *patched* material +bool GetValueFromPatchedMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ); + +const char *GetOriginalMaterialNameForPatchedMaterial( const char *pPatchMaterialName ); + +MaterialSystemMaterial_t FindOriginalMaterial( const char *materialName, bool *pFound, bool bComplain = true ); + +bool DoesMaterialHaveKeyValuePair( const char *pMaterialName, const char *pKeyName, const char *pSearchValue ); +bool DoesMaterialHaveKey( const char *pMaterialName, const char *pKeyName ); + +enum LoadMaterialKeyValuesFlags_t +{ + LOAD_MATERIAL_KEY_VALUES_FLAGS_EXPAND_PATCH = 1, +}; + +KeyValues *LoadMaterialKeyValues( const char *pMaterialName, unsigned int nFlags ); +void WriteMaterialKeyValuesToPak( const char *pMaterialName, KeyValues *kv ); + +#endif // MATERIALPATCH_H diff --git a/mp/src/utils/vbsp/materialsub.cpp b/mp/src/utils/vbsp/materialsub.cpp index d8eb40df..b02063ef 100644 --- a/mp/src/utils/vbsp/materialsub.cpp +++ b/mp/src/utils/vbsp/materialsub.cpp @@ -1,90 +1,90 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: This file loads a KeyValues file containing material name mappings. -// When the bsp is compiled, all materials listed in the file will -// be replaced by the second material in the pair. -// -//============================================================================= - -#include "vbsp.h" -#include "materialsub.h" -#include "KeyValues.h" -#include "tier1/strtools.h" - -bool g_ReplaceMaterials = false; - -static KeyValues *kv = 0; -static KeyValues *allMapKeys = 0; -static KeyValues *curMapKeys = 0; - -//----------------------------------------------------------------------------- -// Purpose: Loads the KeyValues file for materials replacements -//----------------------------------------------------------------------------- -void LoadMaterialReplacementKeys( const char *gamedir, const char *mapname ) -{ - // Careful with static variables - if( kv ) - { - kv->deleteThis(); - kv = 0; - } - if( allMapKeys ) - allMapKeys = 0; - if( curMapKeys ) - curMapKeys = 0; - - Msg( "Loading Replacement Keys\n" ); - - // Attach the path to the keyValues file - char path[1024]; - Q_snprintf( path, sizeof( path ), "%scfg\\materialsub.cfg", gamedir ); - - // Load the keyvalues file - kv = new KeyValues( "MaterialReplacements" ); - - Msg( "File path: %s", path ); - if( !kv->LoadFromFile( g_pFileSystem, path ) ) - { - Msg( "Failed to load KeyValues file!\n" ); - g_ReplaceMaterials = false; - kv->deleteThis(); - kv = 0; - return; - } - - // Load global replace keys - allMapKeys = kv->FindKey( "AllMaps", true ); - - // Load keys for the current map - curMapKeys = kv->FindKey( mapname ); - - allMapKeys->ChainKeyValue( curMapKeys ); -} - -//----------------------------------------------------------------------------- -// Purpose: Deletes all keys -//----------------------------------------------------------------------------- -void DeleteMaterialReplacementKeys( void ) -{ - if( kv ) - { - kv->deleteThis(); - kv = 0; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Replace the passed-in material name with a replacement name, if one exists -//----------------------------------------------------------------------------- -const char* ReplaceMaterialName( const char *name ) -{ - // Look for the material name in the global and map KeyValues - // If it's not there, just return the original name - - // HACK: This stinks - KeyValues won't take a string with '/' in it. - // If they did, this could be a simple pointer swap. - char newName[1024]; - Q_strncpy( newName, name, sizeof( newName ) ); - Q_FixSlashes( newName ); - return allMapKeys->GetString( newName, name ); +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: This file loads a KeyValues file containing material name mappings. +// When the bsp is compiled, all materials listed in the file will +// be replaced by the second material in the pair. +// +//============================================================================= + +#include "vbsp.h" +#include "materialsub.h" +#include "KeyValues.h" +#include "tier1/strtools.h" + +bool g_ReplaceMaterials = false; + +static KeyValues *kv = 0; +static KeyValues *allMapKeys = 0; +static KeyValues *curMapKeys = 0; + +//----------------------------------------------------------------------------- +// Purpose: Loads the KeyValues file for materials replacements +//----------------------------------------------------------------------------- +void LoadMaterialReplacementKeys( const char *gamedir, const char *mapname ) +{ + // Careful with static variables + if( kv ) + { + kv->deleteThis(); + kv = 0; + } + if( allMapKeys ) + allMapKeys = 0; + if( curMapKeys ) + curMapKeys = 0; + + Msg( "Loading Replacement Keys\n" ); + + // Attach the path to the keyValues file + char path[1024]; + Q_snprintf( path, sizeof( path ), "%scfg\\materialsub.cfg", gamedir ); + + // Load the keyvalues file + kv = new KeyValues( "MaterialReplacements" ); + + Msg( "File path: %s", path ); + if( !kv->LoadFromFile( g_pFileSystem, path ) ) + { + Msg( "Failed to load KeyValues file!\n" ); + g_ReplaceMaterials = false; + kv->deleteThis(); + kv = 0; + return; + } + + // Load global replace keys + allMapKeys = kv->FindKey( "AllMaps", true ); + + // Load keys for the current map + curMapKeys = kv->FindKey( mapname ); + + allMapKeys->ChainKeyValue( curMapKeys ); +} + +//----------------------------------------------------------------------------- +// Purpose: Deletes all keys +//----------------------------------------------------------------------------- +void DeleteMaterialReplacementKeys( void ) +{ + if( kv ) + { + kv->deleteThis(); + kv = 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Replace the passed-in material name with a replacement name, if one exists +//----------------------------------------------------------------------------- +const char* ReplaceMaterialName( const char *name ) +{ + // Look for the material name in the global and map KeyValues + // If it's not there, just return the original name + + // HACK: This stinks - KeyValues won't take a string with '/' in it. + // If they did, this could be a simple pointer swap. + char newName[1024]; + Q_strncpy( newName, name, sizeof( newName ) ); + Q_FixSlashes( newName ); + return allMapKeys->GetString( newName, name ); } \ No newline at end of file diff --git a/mp/src/utils/vbsp/materialsub.h b/mp/src/utils/vbsp/materialsub.h index c1de0698..ce6cac42 100644 --- a/mp/src/utils/vbsp/materialsub.h +++ b/mp/src/utils/vbsp/materialsub.h @@ -1,25 +1,25 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: This file loads a KeyValues file containing material name mappings. -// When the bsp is compiled, all materials listed in the file will -// be replaced by the second material in the pair. -// -//============================================================================= - -#ifndef MATERIALSUB_H -#define MATERIALSUB_H -#ifdef _WIN32 -#pragma once -#endif - -extern bool g_ReplaceMaterials; - -// Setup / Cleanup -void LoadMaterialReplacementKeys( const char *gamedir, const char *mapname ); -void DeleteMaterialReplacementKeys( void ); - -// Takes a material name and returns it's replacement, if there is one. -// If there isn't a replacement, it returns the original. -const char* ReplaceMaterialName( const char *name ); - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: This file loads a KeyValues file containing material name mappings. +// When the bsp is compiled, all materials listed in the file will +// be replaced by the second material in the pair. +// +//============================================================================= + +#ifndef MATERIALSUB_H +#define MATERIALSUB_H +#ifdef _WIN32 +#pragma once +#endif + +extern bool g_ReplaceMaterials; + +// Setup / Cleanup +void LoadMaterialReplacementKeys( const char *gamedir, const char *mapname ); +void DeleteMaterialReplacementKeys( void ); + +// Takes a material name and returns it's replacement, if there is one. +// If there isn't a replacement, it returns the original. +const char* ReplaceMaterialName( const char *name ); + #endif // MATERIALSUB_H \ No newline at end of file diff --git a/mp/src/utils/vbsp/nodraw.cpp b/mp/src/utils/vbsp/nodraw.cpp index 4fa493ea..954ea671 100644 --- a/mp/src/utils/vbsp/nodraw.cpp +++ b/mp/src/utils/vbsp/nodraw.cpp @@ -1,32 +1,32 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "vbsp.h" - -Vector draw_mins, draw_maxs; - -void Draw_ClearWindow (void) -{ -} - -//============================================================ - -#define GLSERV_PORT 25001 - - -void GLS_BeginScene (void) -{ -} - -void GLS_Winding (winding_t *w, int code) -{ -} - -void GLS_EndScene (void) -{ -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" + +Vector draw_mins, draw_maxs; + +void Draw_ClearWindow (void) +{ +} + +//============================================================ + +#define GLSERV_PORT 25001 + + +void GLS_BeginScene (void) +{ +} + +void GLS_Winding (winding_t *w, int code) +{ +} + +void GLS_EndScene (void) +{ +} diff --git a/mp/src/utils/vbsp/normals.cpp b/mp/src/utils/vbsp/normals.cpp index 1696342c..3a910cb6 100644 --- a/mp/src/utils/vbsp/normals.cpp +++ b/mp/src/utils/vbsp/normals.cpp @@ -1,50 +1,50 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#include "bsplib.h" -#include "vbsp.h" - - -void SaveVertexNormals( void ) -{ - int i, j; - dface_t *f; - texinfo_t *tex; - - - g_numvertnormalindices = 0; - g_numvertnormals = 0; - - for( i = 0 ;itexinfo]; - - for( j = 0; j < f->numedges; j++ ) - { - if( g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES ) - { - Error( "g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES (%d)", MAX_MAP_VERTNORMALINDICES ); - } - - g_vertnormalindices[g_numvertnormalindices] = g_numvertnormals; - g_numvertnormalindices++; - } - - // Add this face plane's normal. - // Note: this doesn't do an exhaustive vertex normal match because the vrad does it. - // The result is that a little extra memory is wasted coming out of vbsp, but it - // goes away after vrad. - if( g_numvertnormals == MAX_MAP_VERTNORMALS ) - { - Error( "g_numvertnormals == MAX_MAP_VERTNORMALS (%d)", MAX_MAP_VERTNORMALS ); - } - - g_vertnormals[g_numvertnormals] = dplanes[f->planenum].normal; - g_numvertnormals++; - } -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "bsplib.h" +#include "vbsp.h" + + +void SaveVertexNormals( void ) +{ + int i, j; + dface_t *f; + texinfo_t *tex; + + + g_numvertnormalindices = 0; + g_numvertnormals = 0; + + for( i = 0 ;itexinfo]; + + for( j = 0; j < f->numedges; j++ ) + { + if( g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES ) + { + Error( "g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES (%d)", MAX_MAP_VERTNORMALINDICES ); + } + + g_vertnormalindices[g_numvertnormalindices] = g_numvertnormals; + g_numvertnormalindices++; + } + + // Add this face plane's normal. + // Note: this doesn't do an exhaustive vertex normal match because the vrad does it. + // The result is that a little extra memory is wasted coming out of vbsp, but it + // goes away after vrad. + if( g_numvertnormals == MAX_MAP_VERTNORMALS ) + { + Error( "g_numvertnormals == MAX_MAP_VERTNORMALS (%d)", MAX_MAP_VERTNORMALS ); + } + + g_vertnormals[g_numvertnormals] = dplanes[f->planenum].normal; + g_numvertnormals++; + } +} diff --git a/mp/src/utils/vbsp/overlay.cpp b/mp/src/utils/vbsp/overlay.cpp index fcd2e924..edceba9a 100644 --- a/mp/src/utils/vbsp/overlay.cpp +++ b/mp/src/utils/vbsp/overlay.cpp @@ -1,487 +1,487 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "vbsp.h" -#include "disp_vbsp.h" -#include "builddisp.h" -#include "mathlib/vmatrix.h" - -void Overlay_BuildBasisOrigin( doverlay_t *pOverlay ); - -// Overlay list. -CUtlVector g_aMapOverlays; -CUtlVector g_aMapWaterOverlays; - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int Overlay_GetFromEntity( entity_t *pMapEnt ) -{ - int iAccessorID = -1; - - // Allocate the new overlay. - int iOverlay = g_aMapOverlays.AddToTail(); - mapoverlay_t *pMapOverlay = &g_aMapOverlays[iOverlay]; - - // Get the overlay data. - pMapOverlay->nId = g_aMapOverlays.Count() - 1; - - if ( ValueForKey( pMapEnt, "targetname" )[ 0 ] != '\0' ) - { - // Overlay has a name, remember it's ID for accessing - iAccessorID = pMapOverlay->nId; - } - - pMapOverlay->flU[0] = FloatForKey( pMapEnt, "StartU" ); - pMapOverlay->flU[1] = FloatForKey( pMapEnt, "EndU" ); - pMapOverlay->flV[0] = FloatForKey( pMapEnt, "StartV" ); - pMapOverlay->flV[1] = FloatForKey( pMapEnt, "EndV" ); - - pMapOverlay->flFadeDistMinSq = FloatForKey( pMapEnt, "fademindist" ); - if ( pMapOverlay->flFadeDistMinSq > 0 ) - { - pMapOverlay->flFadeDistMinSq *= pMapOverlay->flFadeDistMinSq; - } - - pMapOverlay->flFadeDistMaxSq = FloatForKey( pMapEnt, "fademaxdist" ); - if ( pMapOverlay->flFadeDistMaxSq > 0 ) - { - pMapOverlay->flFadeDistMaxSq *= pMapOverlay->flFadeDistMaxSq; - } - - GetVectorForKey( pMapEnt, "BasisOrigin", pMapOverlay->vecOrigin ); - - pMapOverlay->m_nRenderOrder = IntForKey( pMapEnt, "RenderOrder" ); - if ( pMapOverlay->m_nRenderOrder < 0 || pMapOverlay->m_nRenderOrder >= OVERLAY_NUM_RENDER_ORDERS ) - Error( "Overlay (%s) at %f %f %f has invalid render order (%d).\n", ValueForKey( pMapEnt, "material" ), - pMapOverlay->vecOrigin.x, pMapOverlay->vecOrigin.y, pMapOverlay->vecOrigin.z, - pMapOverlay->m_nRenderOrder ); - - GetVectorForKey( pMapEnt, "uv0", pMapOverlay->vecUVPoints[0] ); - GetVectorForKey( pMapEnt, "uv1", pMapOverlay->vecUVPoints[1] ); - GetVectorForKey( pMapEnt, "uv2", pMapOverlay->vecUVPoints[2] ); - GetVectorForKey( pMapEnt, "uv3", pMapOverlay->vecUVPoints[3] ); - - GetVectorForKey( pMapEnt, "BasisU", pMapOverlay->vecBasis[0] ); - GetVectorForKey( pMapEnt, "BasisV", pMapOverlay->vecBasis[1] ); - GetVectorForKey( pMapEnt, "BasisNormal", pMapOverlay->vecBasis[2] ); - - const char *pMaterialName = ValueForKey( pMapEnt, "material" ); - Assert( strlen( pMaterialName ) < OVERLAY_MAP_STRLEN ); - if ( strlen( pMaterialName ) >= OVERLAY_MAP_STRLEN ) - { - Error( "Overlay Material Name (%s) too long! > OVERLAY_MAP_STRLEN (%d)", pMaterialName, OVERLAY_MAP_STRLEN ); - return -1; - } - strcpy( pMapOverlay->szMaterialName, pMaterialName ); - - // Convert the sidelist to side id(s). - const char *pSideList = ValueForKey( pMapEnt, "sides" ); - char *pTmpList = ( char* )_alloca( strlen( pSideList ) + 1 ); - strcpy( pTmpList, pSideList ); - const char *pScan = strtok( pTmpList, " " ); - if ( !pScan ) - return iAccessorID; - - pMapOverlay->aSideList.Purge(); - pMapOverlay->aFaceList.Purge(); - - do - { - int nSideId; - if ( sscanf( pScan, "%d", &nSideId ) == 1 ) - { - pMapOverlay->aSideList.AddToTail( nSideId ); - } - } while ( ( pScan = strtok( NULL, " " ) ) ); - - return iAccessorID; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -side_t *GetSide( int nSideId ) -{ - for( int iSide = 0; iSide < g_LoadingMap->nummapbrushsides; ++iSide ) - { - if ( g_LoadingMap->brushsides[iSide].id == nSideId ) - return &g_LoadingMap->brushsides[iSide]; - } - - return NULL; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void Overlay_UpdateSideLists( int StartIndex ) -{ - int nMapOverlayCount = g_aMapOverlays.Count(); - for( int iMapOverlay = StartIndex; iMapOverlay < nMapOverlayCount; ++iMapOverlay ) - { - mapoverlay_t *pMapOverlay = &g_aMapOverlays.Element( iMapOverlay ); - if ( pMapOverlay ) - { - int nSideCount = pMapOverlay->aSideList.Count(); - for( int iSide = 0; iSide < nSideCount; ++iSide ) - { - side_t *pSide = GetSide( pMapOverlay->aSideList[iSide] ); - if ( pSide ) - { - if ( pSide->aOverlayIds.Find( pMapOverlay->nId ) == -1 ) - { - pSide->aOverlayIds.AddToTail( pMapOverlay->nId ); - } - } - } - } - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void OverlayTransition_UpdateSideLists( int StartIndex ) -{ - int nOverlayCount = g_aMapWaterOverlays.Count(); - for( int iOverlay = StartIndex; iOverlay < nOverlayCount; ++iOverlay ) - { - mapoverlay_t *pOverlay = &g_aMapWaterOverlays.Element( iOverlay ); - if ( pOverlay ) - { - int nSideCount = pOverlay->aSideList.Count(); - for( int iSide = 0; iSide < nSideCount; ++iSide ) - { - side_t *pSide = GetSide( pOverlay->aSideList[iSide] ); - if ( pSide ) - { - if ( pSide->aWaterOverlayIds.Find( pOverlay->nId ) == -1 ) - { - pSide->aWaterOverlayIds.AddToTail( pOverlay->nId ); - } - } - } - } - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void Overlay_AddFaceToLists( int iFace, side_t *pSide ) -{ - int nOverlayIdCount = pSide->aOverlayIds.Count(); - for( int iOverlayId = 0; iOverlayId < nOverlayIdCount; ++iOverlayId ) - { - mapoverlay_t *pMapOverlay = &g_aMapOverlays.Element( pSide->aOverlayIds[iOverlayId] ); - if ( pMapOverlay ) - { - if( pMapOverlay->aFaceList.Find( iFace ) == -1 ) - { - pMapOverlay->aFaceList.AddToTail( iFace ); - } - } - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void OverlayTransition_AddFaceToLists( int iFace, side_t *pSide ) -{ - int nOverlayIdCount = pSide->aWaterOverlayIds.Count(); - for( int iOverlayId = 0; iOverlayId < nOverlayIdCount; ++iOverlayId ) - { - mapoverlay_t *pMapOverlay = &g_aMapWaterOverlays.Element( pSide->aWaterOverlayIds[iOverlayId] - ( MAX_MAP_OVERLAYS + 1 ) ); - if ( pMapOverlay ) - { - if( pMapOverlay->aFaceList.Find( iFace ) == -1 ) - { - pMapOverlay->aFaceList.AddToTail( iFace ); - } - } - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void Overlay_EmitOverlayFace( mapoverlay_t *pMapOverlay ) -{ - Assert( g_nOverlayCount < MAX_MAP_OVERLAYS ); - if ( g_nOverlayCount >= MAX_MAP_OVERLAYS ) - { - Error ( "Too Many Overlays!\nMAX_MAP_OVERLAYS = %d", MAX_MAP_OVERLAYS ); - return; - } - - doverlay_t *pOverlay = &g_Overlays[g_nOverlayCount]; - doverlayfade_t *pOverlayFade = &g_OverlayFades[g_nOverlayCount]; - - g_nOverlayCount++; - - // Conver the map overlay into a .bsp overlay (doverlay_t). - if ( pOverlay ) - { - pOverlay->nId = pMapOverlay->nId; - - pOverlay->flU[0] = pMapOverlay->flU[0]; - pOverlay->flU[1] = pMapOverlay->flU[1]; - pOverlay->flV[0] = pMapOverlay->flV[0]; - pOverlay->flV[1] = pMapOverlay->flV[1]; - - VectorCopy( pMapOverlay->vecUVPoints[0], pOverlay->vecUVPoints[0] ); - VectorCopy( pMapOverlay->vecUVPoints[1], pOverlay->vecUVPoints[1] ); - VectorCopy( pMapOverlay->vecUVPoints[2], pOverlay->vecUVPoints[2] ); - VectorCopy( pMapOverlay->vecUVPoints[3], pOverlay->vecUVPoints[3] ); - - VectorCopy( pMapOverlay->vecOrigin, pOverlay->vecOrigin ); - - VectorCopy( pMapOverlay->vecBasis[2], pOverlay->vecBasisNormal ); - - pOverlay->SetRenderOrder( pMapOverlay->m_nRenderOrder ); - - // Encode the BasisU into the unused z component of the vecUVPoints 0, 1, 2 - pOverlay->vecUVPoints[0].z = pMapOverlay->vecBasis[0].x; - pOverlay->vecUVPoints[1].z = pMapOverlay->vecBasis[0].y; - pOverlay->vecUVPoints[2].z = pMapOverlay->vecBasis[0].z; - - // Encode whether or not the v axis should be flipped. - Vector vecCross = pMapOverlay->vecBasis[2].Cross( pMapOverlay->vecBasis[0] ); - if ( vecCross.Dot( pMapOverlay->vecBasis[1] ) < 0.0f ) - { - pOverlay->vecUVPoints[3].z = 1.0f; - } - - // Texinfo. - texinfo_t texInfo; - texInfo.flags = 0; - texInfo.texdata = FindOrCreateTexData( pMapOverlay->szMaterialName ); - for( int iVec = 0; iVec < 2; ++iVec ) - { - for( int iAxis = 0; iAxis < 3; ++iAxis ) - { - texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][iAxis] = 0.0f; - texInfo.textureVecsTexelsPerWorldUnits[iVec][iAxis] = 0.0f; - } - - texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][3] = -99999.0f; - texInfo.textureVecsTexelsPerWorldUnits[iVec][3] = -99999.0f; - } - pOverlay->nTexInfo = FindOrCreateTexInfo( texInfo ); - - // Face List - int nFaceCount = pMapOverlay->aFaceList.Count(); - Assert( nFaceCount < OVERLAY_BSP_FACE_COUNT ); - if ( nFaceCount >= OVERLAY_BSP_FACE_COUNT ) - { - Error( "Overlay touching too many faces (touching %d, max %d)\nOverlay %s at %.1f %.1f %.1f", nFaceCount, OVERLAY_BSP_FACE_COUNT, pMapOverlay->szMaterialName, pMapOverlay->vecOrigin.x, pMapOverlay->vecOrigin.y, pMapOverlay->vecOrigin.z ); - return; - } - - pOverlay->SetFaceCount( nFaceCount ); - for( int iFace = 0; iFace < nFaceCount; ++iFace ) - { - pOverlay->aFaces[iFace] = pMapOverlay->aFaceList.Element( iFace ); - } - } - - // Convert the map overlay fade data into a .bsp overlay fade (doverlayfade_t). - if ( pOverlayFade ) - { - pOverlayFade->flFadeDistMinSq = pMapOverlay->flFadeDistMinSq; - pOverlayFade->flFadeDistMaxSq = pMapOverlay->flFadeDistMaxSq; - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void OverlayTransition_EmitOverlayFace( mapoverlay_t *pMapOverlay ) -{ - Assert( g_nWaterOverlayCount < MAX_MAP_WATEROVERLAYS ); - if ( g_nWaterOverlayCount >= MAX_MAP_WATEROVERLAYS ) - { - Error ( "Too many water overlays!\nMAX_MAP_WATEROVERLAYS = %d", MAX_MAP_WATEROVERLAYS ); - return; - } - - dwateroverlay_t *pOverlay = &g_WaterOverlays[g_nWaterOverlayCount]; - g_nWaterOverlayCount++; - - // Conver the map overlay into a .bsp overlay (doverlay_t). - if ( pOverlay ) - { - pOverlay->nId = pMapOverlay->nId; - - pOverlay->flU[0] = pMapOverlay->flU[0]; - pOverlay->flU[1] = pMapOverlay->flU[1]; - pOverlay->flV[0] = pMapOverlay->flV[0]; - pOverlay->flV[1] = pMapOverlay->flV[1]; - - VectorCopy( pMapOverlay->vecUVPoints[0], pOverlay->vecUVPoints[0] ); - VectorCopy( pMapOverlay->vecUVPoints[1], pOverlay->vecUVPoints[1] ); - VectorCopy( pMapOverlay->vecUVPoints[2], pOverlay->vecUVPoints[2] ); - VectorCopy( pMapOverlay->vecUVPoints[3], pOverlay->vecUVPoints[3] ); - - VectorCopy( pMapOverlay->vecOrigin, pOverlay->vecOrigin ); - - VectorCopy( pMapOverlay->vecBasis[2], pOverlay->vecBasisNormal ); - - pOverlay->SetRenderOrder( pMapOverlay->m_nRenderOrder ); - - // Encode the BasisU into the unused z component of the vecUVPoints 0, 1, 2 - pOverlay->vecUVPoints[0].z = pMapOverlay->vecBasis[0].x; - pOverlay->vecUVPoints[1].z = pMapOverlay->vecBasis[0].y; - pOverlay->vecUVPoints[2].z = pMapOverlay->vecBasis[0].z; - - // Encode whether or not the v axis should be flipped. - Vector vecCross = pMapOverlay->vecBasis[2].Cross( pMapOverlay->vecBasis[0] ); - if ( vecCross.Dot( pMapOverlay->vecBasis[1] ) < 0.0f ) - { - pOverlay->vecUVPoints[3].z = 1.0f; - } - - // Texinfo. - texinfo_t texInfo; - texInfo.flags = 0; - texInfo.texdata = FindOrCreateTexData( pMapOverlay->szMaterialName ); - for( int iVec = 0; iVec < 2; ++iVec ) - { - for( int iAxis = 0; iAxis < 3; ++iAxis ) - { - texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][iAxis] = 0.0f; - texInfo.textureVecsTexelsPerWorldUnits[iVec][iAxis] = 0.0f; - } - - texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][3] = -99999.0f; - texInfo.textureVecsTexelsPerWorldUnits[iVec][3] = -99999.0f; - } - pOverlay->nTexInfo = FindOrCreateTexInfo( texInfo ); - - // Face List - int nFaceCount = pMapOverlay->aFaceList.Count(); - Assert( nFaceCount < WATEROVERLAY_BSP_FACE_COUNT ); - if ( nFaceCount >= WATEROVERLAY_BSP_FACE_COUNT ) - { - Error( "Water Overlay touching too many faces (touching %d, max %d)\nOverlay %s at %.1f %.1f %.1f", nFaceCount, OVERLAY_BSP_FACE_COUNT, pMapOverlay->szMaterialName, pMapOverlay->vecOrigin.x, pMapOverlay->vecOrigin.y, pMapOverlay->vecOrigin.z ); - return; - } - - pOverlay->SetFaceCount( nFaceCount ); - for( int iFace = 0; iFace < nFaceCount; ++iFace ) - { - pOverlay->aFaces[iFace] = pMapOverlay->aFaceList.Element( iFace ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void Overlay_EmitOverlayFaces( void ) -{ - int nMapOverlayCount = g_aMapOverlays.Count(); - for( int iMapOverlay = 0; iMapOverlay < nMapOverlayCount; ++iMapOverlay ) - { - Overlay_EmitOverlayFace( &g_aMapOverlays.Element( iMapOverlay ) ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void OverlayTransition_EmitOverlayFaces( void ) -{ - int nMapOverlayCount = g_aMapWaterOverlays.Count(); - for( int iMapOverlay = 0; iMapOverlay < nMapOverlayCount; ++iMapOverlay ) - { - OverlayTransition_EmitOverlayFace( &g_aMapWaterOverlays.Element( iMapOverlay ) ); - } -} - - -//----------------------------------------------------------------------------- -// These routines were mostly stolen from MapOverlay.cpp in Hammer -//----------------------------------------------------------------------------- -#define OVERLAY_BASIS_U 0 -#define OVERLAY_BASIS_V 1 -#define OVERLAY_BASIS_NORMAL 2 -#define OVERLAY_HANDLES_COUNT 4 - - -inline void TransformPoint( const VMatrix& matrix, Vector &point ) -{ - Vector orgVector = point; - matrix.V3Mul( orgVector, point ); -} - - -inline bool fequal( float value, float target, float delta) { return ( (value<(target+delta))&&(value>(target-delta)) ); } - - -//----------------------------------------------------------------------------- -// Purpose: this function translate / rotate an overlay. -// Input : pOverlay - the overlay to be translated -// OriginOffset - the translation -// AngleOffset - the rotation -// Matrix - the translation / rotation matrix -// Output : none -//----------------------------------------------------------------------------- -void Overlay_Translate( mapoverlay_t *pOverlay, Vector &OriginOffset, QAngle &AngleOffset, matrix3x4_t &Matrix ) -{ - VMatrix tmpMatrix( Matrix ); - - Vector temp = pOverlay->vecOrigin; - VectorTransform( temp, Matrix, pOverlay->vecOrigin ); - - // erase move component - tmpMatrix.SetTranslation( vec3_origin ); - - // check if matrix would still change something - if ( !tmpMatrix.IsIdentity() ) - { - // make sure axes are normalized (they should be anyways) - pOverlay->vecBasis[OVERLAY_BASIS_U].NormalizeInPlace(); - pOverlay->vecBasis[OVERLAY_BASIS_V].NormalizeInPlace(); - - Vector vecU = pOverlay->vecBasis[OVERLAY_BASIS_U]; - Vector vecV = pOverlay->vecBasis[OVERLAY_BASIS_V]; - Vector vecNormal = pOverlay->vecBasis[OVERLAY_BASIS_NORMAL]; - - TransformPoint( tmpMatrix, vecU ); - TransformPoint( tmpMatrix, vecV ); - TransformPoint( tmpMatrix, vecNormal ); - - float fScaleU = vecU.Length(); - float fScaleV = vecV.Length(); - float flScaleNormal = vecNormal.Length(); - - bool bIsUnit = ( fequal( fScaleU, 1.0f, 0.0001 ) && fequal( fScaleV, 1.0f, 0.0001 ) && fequal( flScaleNormal, 1.0f, 0.0001 ) ); - bool bIsPerp = ( fequal( DotProduct( vecU, vecV ), 0.0f, 0.0025 ) && fequal( DotProduct( vecU, vecNormal ), 0.0f, 0.0025 ) && fequal( DotProduct( vecV, vecNormal ), 0.0f, 0.0025 ) ); - - // if ( fequal(fScaleU,1,0.0001) && fequal(fScaleV,1,0.0001) && fequal(DotProduct( vecU, vecV ),0,0.0025) ) - if ( bIsUnit && bIsPerp ) - { - // transformation doesnt scale or shear anything, so just update base axes - pOverlay->vecBasis[OVERLAY_BASIS_U] = vecU; - pOverlay->vecBasis[OVERLAY_BASIS_V] = vecV; - pOverlay->vecBasis[OVERLAY_BASIS_NORMAL] = vecNormal; - } - else - { - // more complex transformation, move UV coordinates, but leave base axes - for ( int iHandle=0; iHandlevecUVPoints[iHandle]; - Vector vecPos = ( vecUV.x * pOverlay->vecBasis[OVERLAY_BASIS_U] + vecUV.y * pOverlay->vecBasis[OVERLAY_BASIS_V] ); - - // to transform in world space - TransformPoint( tmpMatrix, vecPos ); - - vecUV.x = pOverlay->vecBasis[OVERLAY_BASIS_U].Dot( vecPos ); - vecUV.y = pOverlay->vecBasis[OVERLAY_BASIS_V].Dot( vecPos ); - - pOverlay->vecUVPoints[iHandle] = vecUV; - } - } - } - -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "disp_vbsp.h" +#include "builddisp.h" +#include "mathlib/vmatrix.h" + +void Overlay_BuildBasisOrigin( doverlay_t *pOverlay ); + +// Overlay list. +CUtlVector g_aMapOverlays; +CUtlVector g_aMapWaterOverlays; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int Overlay_GetFromEntity( entity_t *pMapEnt ) +{ + int iAccessorID = -1; + + // Allocate the new overlay. + int iOverlay = g_aMapOverlays.AddToTail(); + mapoverlay_t *pMapOverlay = &g_aMapOverlays[iOverlay]; + + // Get the overlay data. + pMapOverlay->nId = g_aMapOverlays.Count() - 1; + + if ( ValueForKey( pMapEnt, "targetname" )[ 0 ] != '\0' ) + { + // Overlay has a name, remember it's ID for accessing + iAccessorID = pMapOverlay->nId; + } + + pMapOverlay->flU[0] = FloatForKey( pMapEnt, "StartU" ); + pMapOverlay->flU[1] = FloatForKey( pMapEnt, "EndU" ); + pMapOverlay->flV[0] = FloatForKey( pMapEnt, "StartV" ); + pMapOverlay->flV[1] = FloatForKey( pMapEnt, "EndV" ); + + pMapOverlay->flFadeDistMinSq = FloatForKey( pMapEnt, "fademindist" ); + if ( pMapOverlay->flFadeDistMinSq > 0 ) + { + pMapOverlay->flFadeDistMinSq *= pMapOverlay->flFadeDistMinSq; + } + + pMapOverlay->flFadeDistMaxSq = FloatForKey( pMapEnt, "fademaxdist" ); + if ( pMapOverlay->flFadeDistMaxSq > 0 ) + { + pMapOverlay->flFadeDistMaxSq *= pMapOverlay->flFadeDistMaxSq; + } + + GetVectorForKey( pMapEnt, "BasisOrigin", pMapOverlay->vecOrigin ); + + pMapOverlay->m_nRenderOrder = IntForKey( pMapEnt, "RenderOrder" ); + if ( pMapOverlay->m_nRenderOrder < 0 || pMapOverlay->m_nRenderOrder >= OVERLAY_NUM_RENDER_ORDERS ) + Error( "Overlay (%s) at %f %f %f has invalid render order (%d).\n", ValueForKey( pMapEnt, "material" ), + pMapOverlay->vecOrigin.x, pMapOverlay->vecOrigin.y, pMapOverlay->vecOrigin.z, + pMapOverlay->m_nRenderOrder ); + + GetVectorForKey( pMapEnt, "uv0", pMapOverlay->vecUVPoints[0] ); + GetVectorForKey( pMapEnt, "uv1", pMapOverlay->vecUVPoints[1] ); + GetVectorForKey( pMapEnt, "uv2", pMapOverlay->vecUVPoints[2] ); + GetVectorForKey( pMapEnt, "uv3", pMapOverlay->vecUVPoints[3] ); + + GetVectorForKey( pMapEnt, "BasisU", pMapOverlay->vecBasis[0] ); + GetVectorForKey( pMapEnt, "BasisV", pMapOverlay->vecBasis[1] ); + GetVectorForKey( pMapEnt, "BasisNormal", pMapOverlay->vecBasis[2] ); + + const char *pMaterialName = ValueForKey( pMapEnt, "material" ); + Assert( strlen( pMaterialName ) < OVERLAY_MAP_STRLEN ); + if ( strlen( pMaterialName ) >= OVERLAY_MAP_STRLEN ) + { + Error( "Overlay Material Name (%s) too long! > OVERLAY_MAP_STRLEN (%d)", pMaterialName, OVERLAY_MAP_STRLEN ); + return -1; + } + strcpy( pMapOverlay->szMaterialName, pMaterialName ); + + // Convert the sidelist to side id(s). + const char *pSideList = ValueForKey( pMapEnt, "sides" ); + char *pTmpList = ( char* )_alloca( strlen( pSideList ) + 1 ); + strcpy( pTmpList, pSideList ); + const char *pScan = strtok( pTmpList, " " ); + if ( !pScan ) + return iAccessorID; + + pMapOverlay->aSideList.Purge(); + pMapOverlay->aFaceList.Purge(); + + do + { + int nSideId; + if ( sscanf( pScan, "%d", &nSideId ) == 1 ) + { + pMapOverlay->aSideList.AddToTail( nSideId ); + } + } while ( ( pScan = strtok( NULL, " " ) ) ); + + return iAccessorID; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +side_t *GetSide( int nSideId ) +{ + for( int iSide = 0; iSide < g_LoadingMap->nummapbrushsides; ++iSide ) + { + if ( g_LoadingMap->brushsides[iSide].id == nSideId ) + return &g_LoadingMap->brushsides[iSide]; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void Overlay_UpdateSideLists( int StartIndex ) +{ + int nMapOverlayCount = g_aMapOverlays.Count(); + for( int iMapOverlay = StartIndex; iMapOverlay < nMapOverlayCount; ++iMapOverlay ) + { + mapoverlay_t *pMapOverlay = &g_aMapOverlays.Element( iMapOverlay ); + if ( pMapOverlay ) + { + int nSideCount = pMapOverlay->aSideList.Count(); + for( int iSide = 0; iSide < nSideCount; ++iSide ) + { + side_t *pSide = GetSide( pMapOverlay->aSideList[iSide] ); + if ( pSide ) + { + if ( pSide->aOverlayIds.Find( pMapOverlay->nId ) == -1 ) + { + pSide->aOverlayIds.AddToTail( pMapOverlay->nId ); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void OverlayTransition_UpdateSideLists( int StartIndex ) +{ + int nOverlayCount = g_aMapWaterOverlays.Count(); + for( int iOverlay = StartIndex; iOverlay < nOverlayCount; ++iOverlay ) + { + mapoverlay_t *pOverlay = &g_aMapWaterOverlays.Element( iOverlay ); + if ( pOverlay ) + { + int nSideCount = pOverlay->aSideList.Count(); + for( int iSide = 0; iSide < nSideCount; ++iSide ) + { + side_t *pSide = GetSide( pOverlay->aSideList[iSide] ); + if ( pSide ) + { + if ( pSide->aWaterOverlayIds.Find( pOverlay->nId ) == -1 ) + { + pSide->aWaterOverlayIds.AddToTail( pOverlay->nId ); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void Overlay_AddFaceToLists( int iFace, side_t *pSide ) +{ + int nOverlayIdCount = pSide->aOverlayIds.Count(); + for( int iOverlayId = 0; iOverlayId < nOverlayIdCount; ++iOverlayId ) + { + mapoverlay_t *pMapOverlay = &g_aMapOverlays.Element( pSide->aOverlayIds[iOverlayId] ); + if ( pMapOverlay ) + { + if( pMapOverlay->aFaceList.Find( iFace ) == -1 ) + { + pMapOverlay->aFaceList.AddToTail( iFace ); + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void OverlayTransition_AddFaceToLists( int iFace, side_t *pSide ) +{ + int nOverlayIdCount = pSide->aWaterOverlayIds.Count(); + for( int iOverlayId = 0; iOverlayId < nOverlayIdCount; ++iOverlayId ) + { + mapoverlay_t *pMapOverlay = &g_aMapWaterOverlays.Element( pSide->aWaterOverlayIds[iOverlayId] - ( MAX_MAP_OVERLAYS + 1 ) ); + if ( pMapOverlay ) + { + if( pMapOverlay->aFaceList.Find( iFace ) == -1 ) + { + pMapOverlay->aFaceList.AddToTail( iFace ); + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void Overlay_EmitOverlayFace( mapoverlay_t *pMapOverlay ) +{ + Assert( g_nOverlayCount < MAX_MAP_OVERLAYS ); + if ( g_nOverlayCount >= MAX_MAP_OVERLAYS ) + { + Error ( "Too Many Overlays!\nMAX_MAP_OVERLAYS = %d", MAX_MAP_OVERLAYS ); + return; + } + + doverlay_t *pOverlay = &g_Overlays[g_nOverlayCount]; + doverlayfade_t *pOverlayFade = &g_OverlayFades[g_nOverlayCount]; + + g_nOverlayCount++; + + // Conver the map overlay into a .bsp overlay (doverlay_t). + if ( pOverlay ) + { + pOverlay->nId = pMapOverlay->nId; + + pOverlay->flU[0] = pMapOverlay->flU[0]; + pOverlay->flU[1] = pMapOverlay->flU[1]; + pOverlay->flV[0] = pMapOverlay->flV[0]; + pOverlay->flV[1] = pMapOverlay->flV[1]; + + VectorCopy( pMapOverlay->vecUVPoints[0], pOverlay->vecUVPoints[0] ); + VectorCopy( pMapOverlay->vecUVPoints[1], pOverlay->vecUVPoints[1] ); + VectorCopy( pMapOverlay->vecUVPoints[2], pOverlay->vecUVPoints[2] ); + VectorCopy( pMapOverlay->vecUVPoints[3], pOverlay->vecUVPoints[3] ); + + VectorCopy( pMapOverlay->vecOrigin, pOverlay->vecOrigin ); + + VectorCopy( pMapOverlay->vecBasis[2], pOverlay->vecBasisNormal ); + + pOverlay->SetRenderOrder( pMapOverlay->m_nRenderOrder ); + + // Encode the BasisU into the unused z component of the vecUVPoints 0, 1, 2 + pOverlay->vecUVPoints[0].z = pMapOverlay->vecBasis[0].x; + pOverlay->vecUVPoints[1].z = pMapOverlay->vecBasis[0].y; + pOverlay->vecUVPoints[2].z = pMapOverlay->vecBasis[0].z; + + // Encode whether or not the v axis should be flipped. + Vector vecCross = pMapOverlay->vecBasis[2].Cross( pMapOverlay->vecBasis[0] ); + if ( vecCross.Dot( pMapOverlay->vecBasis[1] ) < 0.0f ) + { + pOverlay->vecUVPoints[3].z = 1.0f; + } + + // Texinfo. + texinfo_t texInfo; + texInfo.flags = 0; + texInfo.texdata = FindOrCreateTexData( pMapOverlay->szMaterialName ); + for( int iVec = 0; iVec < 2; ++iVec ) + { + for( int iAxis = 0; iAxis < 3; ++iAxis ) + { + texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][iAxis] = 0.0f; + texInfo.textureVecsTexelsPerWorldUnits[iVec][iAxis] = 0.0f; + } + + texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][3] = -99999.0f; + texInfo.textureVecsTexelsPerWorldUnits[iVec][3] = -99999.0f; + } + pOverlay->nTexInfo = FindOrCreateTexInfo( texInfo ); + + // Face List + int nFaceCount = pMapOverlay->aFaceList.Count(); + Assert( nFaceCount < OVERLAY_BSP_FACE_COUNT ); + if ( nFaceCount >= OVERLAY_BSP_FACE_COUNT ) + { + Error( "Overlay touching too many faces (touching %d, max %d)\nOverlay %s at %.1f %.1f %.1f", nFaceCount, OVERLAY_BSP_FACE_COUNT, pMapOverlay->szMaterialName, pMapOverlay->vecOrigin.x, pMapOverlay->vecOrigin.y, pMapOverlay->vecOrigin.z ); + return; + } + + pOverlay->SetFaceCount( nFaceCount ); + for( int iFace = 0; iFace < nFaceCount; ++iFace ) + { + pOverlay->aFaces[iFace] = pMapOverlay->aFaceList.Element( iFace ); + } + } + + // Convert the map overlay fade data into a .bsp overlay fade (doverlayfade_t). + if ( pOverlayFade ) + { + pOverlayFade->flFadeDistMinSq = pMapOverlay->flFadeDistMinSq; + pOverlayFade->flFadeDistMaxSq = pMapOverlay->flFadeDistMaxSq; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void OverlayTransition_EmitOverlayFace( mapoverlay_t *pMapOverlay ) +{ + Assert( g_nWaterOverlayCount < MAX_MAP_WATEROVERLAYS ); + if ( g_nWaterOverlayCount >= MAX_MAP_WATEROVERLAYS ) + { + Error ( "Too many water overlays!\nMAX_MAP_WATEROVERLAYS = %d", MAX_MAP_WATEROVERLAYS ); + return; + } + + dwateroverlay_t *pOverlay = &g_WaterOverlays[g_nWaterOverlayCount]; + g_nWaterOverlayCount++; + + // Conver the map overlay into a .bsp overlay (doverlay_t). + if ( pOverlay ) + { + pOverlay->nId = pMapOverlay->nId; + + pOverlay->flU[0] = pMapOverlay->flU[0]; + pOverlay->flU[1] = pMapOverlay->flU[1]; + pOverlay->flV[0] = pMapOverlay->flV[0]; + pOverlay->flV[1] = pMapOverlay->flV[1]; + + VectorCopy( pMapOverlay->vecUVPoints[0], pOverlay->vecUVPoints[0] ); + VectorCopy( pMapOverlay->vecUVPoints[1], pOverlay->vecUVPoints[1] ); + VectorCopy( pMapOverlay->vecUVPoints[2], pOverlay->vecUVPoints[2] ); + VectorCopy( pMapOverlay->vecUVPoints[3], pOverlay->vecUVPoints[3] ); + + VectorCopy( pMapOverlay->vecOrigin, pOverlay->vecOrigin ); + + VectorCopy( pMapOverlay->vecBasis[2], pOverlay->vecBasisNormal ); + + pOverlay->SetRenderOrder( pMapOverlay->m_nRenderOrder ); + + // Encode the BasisU into the unused z component of the vecUVPoints 0, 1, 2 + pOverlay->vecUVPoints[0].z = pMapOverlay->vecBasis[0].x; + pOverlay->vecUVPoints[1].z = pMapOverlay->vecBasis[0].y; + pOverlay->vecUVPoints[2].z = pMapOverlay->vecBasis[0].z; + + // Encode whether or not the v axis should be flipped. + Vector vecCross = pMapOverlay->vecBasis[2].Cross( pMapOverlay->vecBasis[0] ); + if ( vecCross.Dot( pMapOverlay->vecBasis[1] ) < 0.0f ) + { + pOverlay->vecUVPoints[3].z = 1.0f; + } + + // Texinfo. + texinfo_t texInfo; + texInfo.flags = 0; + texInfo.texdata = FindOrCreateTexData( pMapOverlay->szMaterialName ); + for( int iVec = 0; iVec < 2; ++iVec ) + { + for( int iAxis = 0; iAxis < 3; ++iAxis ) + { + texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][iAxis] = 0.0f; + texInfo.textureVecsTexelsPerWorldUnits[iVec][iAxis] = 0.0f; + } + + texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][3] = -99999.0f; + texInfo.textureVecsTexelsPerWorldUnits[iVec][3] = -99999.0f; + } + pOverlay->nTexInfo = FindOrCreateTexInfo( texInfo ); + + // Face List + int nFaceCount = pMapOverlay->aFaceList.Count(); + Assert( nFaceCount < WATEROVERLAY_BSP_FACE_COUNT ); + if ( nFaceCount >= WATEROVERLAY_BSP_FACE_COUNT ) + { + Error( "Water Overlay touching too many faces (touching %d, max %d)\nOverlay %s at %.1f %.1f %.1f", nFaceCount, OVERLAY_BSP_FACE_COUNT, pMapOverlay->szMaterialName, pMapOverlay->vecOrigin.x, pMapOverlay->vecOrigin.y, pMapOverlay->vecOrigin.z ); + return; + } + + pOverlay->SetFaceCount( nFaceCount ); + for( int iFace = 0; iFace < nFaceCount; ++iFace ) + { + pOverlay->aFaces[iFace] = pMapOverlay->aFaceList.Element( iFace ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Overlay_EmitOverlayFaces( void ) +{ + int nMapOverlayCount = g_aMapOverlays.Count(); + for( int iMapOverlay = 0; iMapOverlay < nMapOverlayCount; ++iMapOverlay ) + { + Overlay_EmitOverlayFace( &g_aMapOverlays.Element( iMapOverlay ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void OverlayTransition_EmitOverlayFaces( void ) +{ + int nMapOverlayCount = g_aMapWaterOverlays.Count(); + for( int iMapOverlay = 0; iMapOverlay < nMapOverlayCount; ++iMapOverlay ) + { + OverlayTransition_EmitOverlayFace( &g_aMapWaterOverlays.Element( iMapOverlay ) ); + } +} + + +//----------------------------------------------------------------------------- +// These routines were mostly stolen from MapOverlay.cpp in Hammer +//----------------------------------------------------------------------------- +#define OVERLAY_BASIS_U 0 +#define OVERLAY_BASIS_V 1 +#define OVERLAY_BASIS_NORMAL 2 +#define OVERLAY_HANDLES_COUNT 4 + + +inline void TransformPoint( const VMatrix& matrix, Vector &point ) +{ + Vector orgVector = point; + matrix.V3Mul( orgVector, point ); +} + + +inline bool fequal( float value, float target, float delta) { return ( (value<(target+delta))&&(value>(target-delta)) ); } + + +//----------------------------------------------------------------------------- +// Purpose: this function translate / rotate an overlay. +// Input : pOverlay - the overlay to be translated +// OriginOffset - the translation +// AngleOffset - the rotation +// Matrix - the translation / rotation matrix +// Output : none +//----------------------------------------------------------------------------- +void Overlay_Translate( mapoverlay_t *pOverlay, Vector &OriginOffset, QAngle &AngleOffset, matrix3x4_t &Matrix ) +{ + VMatrix tmpMatrix( Matrix ); + + Vector temp = pOverlay->vecOrigin; + VectorTransform( temp, Matrix, pOverlay->vecOrigin ); + + // erase move component + tmpMatrix.SetTranslation( vec3_origin ); + + // check if matrix would still change something + if ( !tmpMatrix.IsIdentity() ) + { + // make sure axes are normalized (they should be anyways) + pOverlay->vecBasis[OVERLAY_BASIS_U].NormalizeInPlace(); + pOverlay->vecBasis[OVERLAY_BASIS_V].NormalizeInPlace(); + + Vector vecU = pOverlay->vecBasis[OVERLAY_BASIS_U]; + Vector vecV = pOverlay->vecBasis[OVERLAY_BASIS_V]; + Vector vecNormal = pOverlay->vecBasis[OVERLAY_BASIS_NORMAL]; + + TransformPoint( tmpMatrix, vecU ); + TransformPoint( tmpMatrix, vecV ); + TransformPoint( tmpMatrix, vecNormal ); + + float fScaleU = vecU.Length(); + float fScaleV = vecV.Length(); + float flScaleNormal = vecNormal.Length(); + + bool bIsUnit = ( fequal( fScaleU, 1.0f, 0.0001 ) && fequal( fScaleV, 1.0f, 0.0001 ) && fequal( flScaleNormal, 1.0f, 0.0001 ) ); + bool bIsPerp = ( fequal( DotProduct( vecU, vecV ), 0.0f, 0.0025 ) && fequal( DotProduct( vecU, vecNormal ), 0.0f, 0.0025 ) && fequal( DotProduct( vecV, vecNormal ), 0.0f, 0.0025 ) ); + + // if ( fequal(fScaleU,1,0.0001) && fequal(fScaleV,1,0.0001) && fequal(DotProduct( vecU, vecV ),0,0.0025) ) + if ( bIsUnit && bIsPerp ) + { + // transformation doesnt scale or shear anything, so just update base axes + pOverlay->vecBasis[OVERLAY_BASIS_U] = vecU; + pOverlay->vecBasis[OVERLAY_BASIS_V] = vecV; + pOverlay->vecBasis[OVERLAY_BASIS_NORMAL] = vecNormal; + } + else + { + // more complex transformation, move UV coordinates, but leave base axes + for ( int iHandle=0; iHandlevecUVPoints[iHandle]; + Vector vecPos = ( vecUV.x * pOverlay->vecBasis[OVERLAY_BASIS_U] + vecUV.y * pOverlay->vecBasis[OVERLAY_BASIS_V] ); + + // to transform in world space + TransformPoint( tmpMatrix, vecPos ); + + vecUV.x = pOverlay->vecBasis[OVERLAY_BASIS_U].Dot( vecPos ); + vecUV.y = pOverlay->vecBasis[OVERLAY_BASIS_V].Dot( vecPos ); + + pOverlay->vecUVPoints[iHandle] = vecUV; + } + } + } + +} diff --git a/mp/src/utils/vbsp/portals.cpp b/mp/src/utils/vbsp/portals.cpp index ec2a2695..6c59cffa 100644 --- a/mp/src/utils/vbsp/portals.cpp +++ b/mp/src/utils/vbsp/portals.cpp @@ -1,1684 +1,1684 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "vbsp.h" -#include "utlvector.h" -#include "mathlib/vmatrix.h" -#include "iscratchpad3d.h" -#include "csg.h" -#include "fmtstr.h" - -int c_active_portals; -int c_peak_portals; -int c_boundary; -int c_boundary_sides; - -/* -=========== -AllocPortal -=========== -*/ -portal_t *AllocPortal (void) -{ - static int s_PortalCount = 0; - - portal_t *p; - - if (numthreads == 1) - c_active_portals++; - if (c_active_portals > c_peak_portals) - c_peak_portals = c_active_portals; - - p = (portal_t*)malloc (sizeof(portal_t)); - memset (p, 0, sizeof(portal_t)); - p->id = s_PortalCount; - ++s_PortalCount; - - return p; -} - -void FreePortal (portal_t *p) -{ - if (p->winding) - FreeWinding (p->winding); - if (numthreads == 1) - c_active_portals--; - free (p); -} - -//============================================================== - -/* -============== -VisibleContents - -Returns the single content bit of the -strongest visible content present -============== -*/ -int VisibleContents (int contents) -{ - int i; - - for (i=1 ; i<=LAST_VISIBLE_CONTENTS ; i<<=1) - { - if (contents & i ) - { - return i; - } - } - - return 0; -} - - -/* -=============== -ClusterContents -=============== -*/ -int ClusterContents (node_t *node) -{ - int c1, c2, c; - - if (node->planenum == PLANENUM_LEAF) - return node->contents; - - c1 = ClusterContents(node->children[0]); - c2 = ClusterContents(node->children[1]); - c = c1|c2; - - // a cluster may include some solid detail areas, but - // still be seen into - if ( ! (c1&CONTENTS_SOLID) || ! (c2&CONTENTS_SOLID) ) - c &= ~CONTENTS_SOLID; - return c; -} - -/* -============= -Portal_VisFlood - -Returns true if the portal is empty or translucent, allowing -the PVS calculation to see through it. -The nodes on either side of the portal may actually be clusters, -not leafs, so all contents should be ored together -============= -*/ -qboolean Portal_VisFlood (portal_t *p) -{ - int c1, c2; - - if (!p->onnode) - return false; // to global outsideleaf - - c1 = ClusterContents(p->nodes[0]); - c2 = ClusterContents(p->nodes[1]); - - if (!VisibleContents (c1^c2)) - return true; - - if (c1 & (CONTENTS_TRANSLUCENT|CONTENTS_DETAIL)) - c1 = 0; - if (c2 & (CONTENTS_TRANSLUCENT|CONTENTS_DETAIL)) - c2 = 0; - - if ( (c1|c2) & CONTENTS_SOLID ) - return false; // can't see through solid - - if (! (c1 ^ c2)) - return true; // identical on both sides - - if (!VisibleContents (c1^c2)) - return true; - return false; -} - - -/* -=============== -Portal_EntityFlood - -The entity flood determines which areas are -"outside" on the map, which are then filled in. -Flowing from side s to side !s -=============== -*/ -qboolean Portal_EntityFlood (portal_t *p, int s) -{ - if (p->nodes[0]->planenum != PLANENUM_LEAF - || p->nodes[1]->planenum != PLANENUM_LEAF) - Error ("Portal_EntityFlood: not a leaf"); - - // can never cross to a solid - if ( (p->nodes[0]->contents & CONTENTS_SOLID) - || (p->nodes[1]->contents & CONTENTS_SOLID) ) - return false; - - // can flood through everything else - return true; -} - -qboolean Portal_AreaLeakFlood (portal_t *p, int s) -{ - if ( !Portal_EntityFlood( p, s ) ) - return false; - - // can never cross through areaportal - if ( (p->nodes[0]->contents & CONTENTS_AREAPORTAL) - || (p->nodes[1]->contents & CONTENTS_AREAPORTAL) ) - return false; - - // can flood through everything else - return true; -} - - -//============================================================================= - -int c_tinyportals; - -/* -============= -AddPortalToNodes -============= -*/ -void AddPortalToNodes (portal_t *p, node_t *front, node_t *back) -{ - if (p->nodes[0] || p->nodes[1]) - Error ("AddPortalToNode: allready included"); - - p->nodes[0] = front; - p->next[0] = front->portals; - front->portals = p; - - p->nodes[1] = back; - p->next[1] = back->portals; - back->portals = p; -} - - -/* -============= -RemovePortalFromNode -============= -*/ -void RemovePortalFromNode (portal_t *portal, node_t *l) -{ - portal_t **pp, *t; - -// remove reference to the current portal - pp = &l->portals; - while (1) - { - t = *pp; - if (!t) - Error ("RemovePortalFromNode: portal not in leaf"); - - if ( t == portal ) - break; - - if (t->nodes[0] == l) - pp = &t->next[0]; - else if (t->nodes[1] == l) - pp = &t->next[1]; - else - Error ("RemovePortalFromNode: portal not bounding leaf"); - } - - if (portal->nodes[0] == l) - { - *pp = portal->next[0]; - portal->nodes[0] = NULL; - } - else if (portal->nodes[1] == l) - { - *pp = portal->next[1]; - portal->nodes[1] = NULL; - } -} - -//============================================================================ - -void PrintPortal (portal_t *p) -{ - int i; - winding_t *w; - - w = p->winding; - for (i=0 ; inumpoints ; i++) - Msg ("(%5.0f,%5.0f,%5.0f)\n",w->p[i][0] - , w->p[i][1], w->p[i][2]); -} - -// because of water areaportals support, the areaportal may not be the only brush on this node -bspbrush_t *AreaportalBrushForNode( node_t *node ) -{ - bspbrush_t *b = node->brushlist; - while ( b && !(b->original->contents & CONTENTS_AREAPORTAL) ) - { - b = b->next; - } - Assert( b->original->entitynum != 0 ); - return b; -} - -/* -================ -MakeHeadnodePortals - -The created portals will face the global outside_node -================ -*/ -// buffer space around sides of nodes -#define SIDESPACE 8 -void MakeHeadnodePortals (tree_t *tree) -{ - Vector bounds[2]; - int i, j, n; - portal_t *p, *portals[6]; - plane_t bplanes[6], *pl; - node_t *node; - - node = tree->headnode; - -// pad with some space so there will never be null volume leafs - for (i=0 ; i<3 ; i++) - { - bounds[0][i] = tree->mins[i] - SIDESPACE; - bounds[1][i] = tree->maxs[i] + SIDESPACE; - } - - tree->outside_node.planenum = PLANENUM_LEAF; - tree->outside_node.brushlist = NULL; - tree->outside_node.portals = NULL; - tree->outside_node.contents = 0; - - for (i=0 ; i<3 ; i++) - for (j=0 ; j<2 ; j++) - { - n = j*3 + i; - - p = AllocPortal (); - portals[n] = p; - - pl = &bplanes[n]; - memset (pl, 0, sizeof(*pl)); - if (j) - { - pl->normal[i] = -1; - pl->dist = -bounds[j][i]; - } - else - { - pl->normal[i] = 1; - pl->dist = bounds[j][i]; - } - p->plane = *pl; - p->winding = BaseWindingForPlane (pl->normal, pl->dist); - AddPortalToNodes (p, node, &tree->outside_node); - } - -// clip the basewindings by all the other planes - for (i=0 ; i<6 ; i++) - { - for (j=0 ; j<6 ; j++) - { - if (j == i) - continue; - ChopWindingInPlace (&portals[i]->winding, bplanes[j].normal, bplanes[j].dist, ON_EPSILON); - } - } -} - -//=================================================== - - -/* -================ -BaseWindingForNode -================ -*/ -#define BASE_WINDING_EPSILON 0.001 -#define SPLIT_WINDING_EPSILON 0.001 - -winding_t *BaseWindingForNode (node_t *node) -{ - winding_t *w; - node_t *n; - plane_t *plane; - Vector normal; - vec_t dist; - - w = BaseWindingForPlane (g_MainMap->mapplanes[node->planenum].normal, g_MainMap->mapplanes[node->planenum].dist); - - // clip by all the parents - for (n=node->parent ; n && w ; ) - { - plane = &g_MainMap->mapplanes[n->planenum]; - - if (n->children[0] == node) - { // take front - ChopWindingInPlace (&w, plane->normal, plane->dist, BASE_WINDING_EPSILON); - } - else - { // take back - VectorSubtract (vec3_origin, plane->normal, normal); - dist = -plane->dist; - ChopWindingInPlace (&w, normal, dist, BASE_WINDING_EPSILON); - } - node = n; - n = n->parent; - } - - return w; -} - -//============================================================ - -/* -================== -MakeNodePortal - -create the new portal by taking the full plane winding for the cutting plane -and clipping it by all of parents of this node -================== -*/ -void MakeNodePortal (node_t *node) -{ - portal_t *new_portal, *p; - winding_t *w; - Vector normal; - float dist = 0.0f; - int side = 0; - - w = BaseWindingForNode (node); - - // clip the portal by all the other portals in the node - for (p = node->portals ; p && w; p = p->next[side]) - { - if (p->nodes[0] == node) - { - side = 0; - VectorCopy (p->plane.normal, normal); - dist = p->plane.dist; - } - else if (p->nodes[1] == node) - { - side = 1; - VectorSubtract (vec3_origin, p->plane.normal, normal); - dist = -p->plane.dist; - } - else - { - Error ("CutNodePortals_r: mislinked portal"); - } - - ChopWindingInPlace (&w, normal, dist, 0.1); - } - - if (!w) - { - return; - } - - if (WindingIsTiny (w)) - { - c_tinyportals++; - FreeWinding (w); - return; - } - - - new_portal = AllocPortal (); - new_portal->plane = g_MainMap->mapplanes[node->planenum]; - new_portal->onnode = node; - new_portal->winding = w; - - AddPortalToNodes (new_portal, node->children[0], node->children[1]); -} - - -/* -============== -SplitNodePortals - -Move or split the portals that bound node so that the node's -children have portals instead of node. -============== -*/ -void SplitNodePortals (node_t *node) -{ - portal_t *p, *next_portal, *new_portal; - node_t *f, *b, *other_node; - int side = 0; - plane_t *plane; - winding_t *frontwinding, *backwinding; - - plane = &g_MainMap->mapplanes[node->planenum]; - f = node->children[0]; - b = node->children[1]; - - for (p = node->portals ; p ; p = next_portal) - { - if (p->nodes[0] == node) - side = 0; - else if (p->nodes[1] == node) - side = 1; - else - Error ("CutNodePortals_r: mislinked portal"); - next_portal = p->next[side]; - - other_node = p->nodes[!side]; - RemovePortalFromNode (p, p->nodes[0]); - RemovePortalFromNode (p, p->nodes[1]); - -// -// cut the portal into two portals, one on each side of the cut plane -// - ClipWindingEpsilon (p->winding, plane->normal, plane->dist, - SPLIT_WINDING_EPSILON, &frontwinding, &backwinding); - - if (frontwinding && WindingIsTiny(frontwinding)) - { - FreeWinding (frontwinding); - frontwinding = NULL; - c_tinyportals++; - } - - if (backwinding && WindingIsTiny(backwinding)) - { - FreeWinding (backwinding); - backwinding = NULL; - c_tinyportals++; - } - - if (!frontwinding && !backwinding) - { // tiny windings on both sides - continue; - } - - if (!frontwinding) - { - FreeWinding (backwinding); - if (side == 0) - AddPortalToNodes (p, b, other_node); - else - AddPortalToNodes (p, other_node, b); - continue; - } - if (!backwinding) - { - FreeWinding (frontwinding); - if (side == 0) - AddPortalToNodes (p, f, other_node); - else - AddPortalToNodes (p, other_node, f); - continue; - } - - // the winding is split - new_portal = AllocPortal (); - *new_portal = *p; - new_portal->winding = backwinding; - FreeWinding (p->winding); - p->winding = frontwinding; - - if (side == 0) - { - AddPortalToNodes (p, f, other_node); - AddPortalToNodes (new_portal, b, other_node); - } - else - { - AddPortalToNodes (p, other_node, f); - AddPortalToNodes (new_portal, other_node, b); - } - } - - node->portals = NULL; -} - - -/* -================ -CalcNodeBounds -================ -*/ -void CalcNodeBounds (node_t *node) -{ - portal_t *p; - int s; - int i; - - // calc mins/maxs for both leafs and nodes - ClearBounds (node->mins, node->maxs); - for (p = node->portals ; p ; p = p->next[s]) - { - s = (p->nodes[1] == node); - for (i=0 ; iwinding->numpoints ; i++) - AddPointToBounds (p->winding->p[i], node->mins, node->maxs); - } -} - - -/* -================== -MakeTreePortals_r -================== -*/ -void MakeTreePortals_r (node_t *node) -{ - int i; - - CalcNodeBounds (node); - if (node->mins[0] >= node->maxs[0]) - { - Warning("WARNING: node without a volume\n"); - } - - for (i=0 ; i<3 ; i++) - { - if (node->mins[i] < (MIN_COORD_INTEGER-SIDESPACE) || node->maxs[i] > (MAX_COORD_INTEGER+SIDESPACE)) - { - const char *pMatName = ""; - // split by brush side - if ( node->side ) - { - texinfo_t *pTexInfo = &texinfo[node->side->texinfo]; - dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); - pMatName = TexDataStringTable_GetString( pTexData->nameStringTableID ); - } - Vector point = node->portals->winding->p[0]; - Warning("WARNING: BSP node with unbounded volume (material: %s, near %s)\n", pMatName, VecToString(point) ); - break; - } - } - if (node->planenum == PLANENUM_LEAF) - return; - - MakeNodePortal (node); - SplitNodePortals (node); - - MakeTreePortals_r (node->children[0]); - MakeTreePortals_r (node->children[1]); -} - -/* -================== -MakeTreePortals -================== -*/ -void MakeTreePortals (tree_t *tree) -{ - MakeHeadnodePortals (tree); - MakeTreePortals_r (tree->headnode); -} - -/* -========================================================= - -FLOOD ENTITIES - -========================================================= -*/ - -//----------------------------------------------------------------------------- -// Purpose: Floods outward from the given node, marking visited nodes with -// the number of hops from a node with an entity. If we ever mark -// the outside_node for this tree, we've leaked. -// Input : node - -// dist - -//----------------------------------------------------------------------------- -void FloodPortals_r (node_t *node, int dist) -{ - portal_t *p; - int s; - - node->occupied = dist; - - for (p=node->portals ; p ; p = p->next[s]) - { - s = (p->nodes[1] == node); - - // Skip nodes that have already been marked. - if (p->nodes[!s]->occupied) - continue; - - // Skip portals that lead to or from nodes with solid contents. - if (!Portal_EntityFlood (p, s)) - continue; - - FloodPortals_r (p->nodes[!s], dist+1); - } -} - -void FloodAreaLeak_r( node_t *node, int dist ) -{ - portal_t *p; - int s; - - node->occupied = dist; - - for (p=node->portals ; p ; p = p->next[s]) - { - s = (p->nodes[1] == node); - - if (p->nodes[!s]->occupied) - continue; - - if (!Portal_AreaLeakFlood (p, s)) - continue; - - FloodAreaLeak_r( p->nodes[!s], dist+1 ); - } -} - -void ClearOccupied_r( node_t *headnode ) -{ - if ( !headnode ) - return; - - headnode->occupied = 0; - ClearOccupied_r( headnode->children[0] ); - ClearOccupied_r( headnode->children[1] ); -} - -void FloodAreaLeak( node_t *headnode, node_t *pFirstSide ) -{ - ClearOccupied_r( headnode ); - FloodAreaLeak_r( pFirstSide, 2 ); -} - -//----------------------------------------------------------------------------- -// Purpose: For the given entity at the given origin, finds the leaf node in the -// BSP tree that the entity occupies. -// -// We then flood outward from that leaf to see if the entity leaks. -// Input : headnode - -// origin - -// occupant - -// Output : Returns false if the entity is in solid, true if it is not. -//----------------------------------------------------------------------------- -qboolean PlaceOccupant (node_t *headnode, Vector& origin, entity_t *occupant) -{ - node_t *node; - vec_t d; - plane_t *plane; - - // find the leaf to start in - node = headnode; - while (node->planenum != PLANENUM_LEAF) - { - plane = &g_MainMap->mapplanes[node->planenum]; - d = DotProduct (origin, plane->normal) - plane->dist; - if (d >= 0) - node = node->children[0]; - else - node = node->children[1]; - } - - if (node->contents == CONTENTS_SOLID) - return false; - - node->occupant = occupant; - - // Flood outward from here to see if this entity leaks. - FloodPortals_r (node, 1); - - return true; -} - -/* -============= -FloodEntities - -Marks all nodes that can be reached by entites -============= -*/ -qboolean FloodEntities (tree_t *tree) -{ - int i; - Vector origin; - char *cl; - qboolean inside; - node_t *headnode; - - headnode = tree->headnode; - qprintf ("--- FloodEntities ---\n"); - inside = false; - tree->outside_node.occupied = 0; - - for (i=1 ; ioutside_node.occupied) - { - qprintf ("entity reached from outside -- no filling\n" ); - } - - return (qboolean)(inside && !tree->outside_node.occupied); -} - - -/* -========================================================= - -FLOOD AREAS - -========================================================= -*/ - -int c_areas; - -bool IsAreaportalNode( node_t *node ) -{ - return ( node->contents & CONTENTS_AREAPORTAL ) ? true : false; -} -/* -============= -FloodAreas_r -============= -*/ - -void FloodAreas_r (node_t *node, portal_t *pSeeThrough) -{ - portal_t *p; - int s; - bspbrush_t *b; - entity_t *e; - - if ( IsAreaportalNode(node) ) - { - // this node is part of an area portal - b = AreaportalBrushForNode( node ); - e = &entities[b->original->entitynum]; - - // if the current area has allready touched this - // portal, we are done - if (e->portalareas[0] == c_areas || e->portalareas[1] == c_areas) - return; - - // note the current area as bounding the portal - if (e->portalareas[1]) - { - Warning("WARNING: areaportal entity %i (brush %i) touches > 2 areas\n", b->original->entitynum, b->original->id ); - return; - } - - if (e->portalareas[0]) - { - e->portalareas[1] = c_areas; - e->m_pPortalsLeadingIntoAreas[1] = pSeeThrough; - } - else - { - e->portalareas[0] = c_areas; - e->m_pPortalsLeadingIntoAreas[0] = pSeeThrough; - } - - return; - } - - if (node->area) - return; // allready got it - node->area = c_areas; - - for (p=node->portals ; p ; p = p->next[s]) - { - s = (p->nodes[1] == node); -#if 0 - if (p->nodes[!s]->occupied) - continue; -#endif - if (!Portal_EntityFlood (p, s)) - continue; - - FloodAreas_r (p->nodes[!s], p); - } -} - -/* -============= -FindAreas_r - -Just decend the tree, and for each node that hasn't had an -area set, flood fill out from there -============= -*/ -void FindAreas_r (node_t *node) -{ - if (node->planenum != PLANENUM_LEAF) - { - FindAreas_r (node->children[0]); - FindAreas_r (node->children[1]); - return; - } - - if (node->area) - return; // allready got it - - if (node->contents & CONTENTS_SOLID) - return; - - if (!node->occupied) - return; // not reachable by entities - - // area portals are allways only flooded into, never - // out of - if (IsAreaportalNode(node)) - return; - - c_areas++; - FloodAreas_r (node, NULL); -} - - -void ReportAreaportalLeak( tree_t *tree, node_t *node ) -{ - portal_t *p, *pStart = NULL; - int s; - - // Find a portal out of this areaportal into empty space - for (p=node->portals ; p ; p = p->next[s]) - { - s = (p->nodes[1] == node); - if ( !Portal_EntityFlood( p, !s ) ) - continue; - if ( p->nodes[!s]->contents & CONTENTS_AREAPORTAL ) - continue; - - pStart = p; - break; - } - - if ( pStart ) - { - s = pStart->nodes[0] == node; - Assert(!(pStart->nodes[s]->contents & CONTENTS_AREAPORTAL) ); - // flood fill the area outside this areaportal brush - FloodAreaLeak( tree->headnode, pStart->nodes[s] ); - - // find the portal into the longest path around the portal - portal_t *pBest = NULL; - int bestDist = 0; - for (p=node->portals ; p ; p = p->next[s]) - { - if ( p == pStart ) - continue; - - s = (p->nodes[1] == node); - if ( p->nodes[!s]->occupied > bestDist ) - { - pBest = p; - bestDist = p->nodes[!s]->occupied; - } - } - if ( pBest ) - { - s = (pBest->nodes[0] == node); - // write the linefile that goes from pBest to pStart - AreaportalLeakFile( tree, pStart, pBest, pBest->nodes[s] ); - } - } -} - - -/* -============= -SetAreaPortalAreas_r - -Just decend the tree, and for each node that hasn't had an -area set, flood fill out from there -============= -*/ -void SetAreaPortalAreas_r (tree_t *tree, node_t *node) -{ - bspbrush_t *b; - entity_t *e; - - if (node->planenum != PLANENUM_LEAF) - { - SetAreaPortalAreas_r (tree, node->children[0]); - SetAreaPortalAreas_r (tree, node->children[1]); - return; - } - - if (IsAreaportalNode(node)) - { - if (node->area) - return; // already set - - b = AreaportalBrushForNode( node ); - e = &entities[b->original->entitynum]; - node->area = e->portalareas[0]; - if (!e->portalareas[1]) - { - ReportAreaportalLeak( tree, node ); - Warning("\nBrush %i: areaportal brush doesn't touch two areas\n", b->original->id); - return; - } - } -} - - -// Return a positive value between 0 and 2*PI telling the angle distance -// from flBaseAngle to flTestAngle. -float AngleOffset( float flBaseAngle, float flTestAngle ) -{ - while( flTestAngle > flBaseAngle ) - flTestAngle -= 2 * M_PI; - - return fmod( flBaseAngle - flTestAngle, (float) (2 * M_PI) ); -} - - -int FindUniquePoints( const Vector2D *pPoints, int nPoints, int *indexMap, int nMaxIndexMapPoints, float flTolerance ) -{ - float flToleranceSqr = flTolerance * flTolerance; - - // This could be slightly more efficient. - int nUniquePoints = 0; - for ( int i=0; i < nPoints; i++ ) - { - int j; - for ( j=0; j < nUniquePoints; j++ ) - { - if ( pPoints[i].DistToSqr( pPoints[indexMap[j]] ) < flToleranceSqr ) - break; - } - if ( j == nUniquePoints ) - { - if ( nUniquePoints >= nMaxIndexMapPoints ) - Error( "FindUniquePoints: overflowed unique point list (size %d).", nMaxIndexMapPoints ); - - indexMap[nUniquePoints++] = i; - } - } - - return nUniquePoints; -} - -// Build a 2D convex hull of the set of points. -// This essentially giftwraps the points as it walks around the perimeter. -int Convex2D( Vector2D const *pPoints, int nPoints, int *indices, int nMaxIndices ) -{ - int nIndices = 0; - bool touched[512]; - int indexMap[512]; - - if( nPoints == 0 ) - return 0; - - - // If we don't collapse the points into a unique set, we can loop around forever - // and max out nMaxIndices. - nPoints = FindUniquePoints( pPoints, nPoints, indexMap, ARRAYSIZE( indexMap ), 0.1f ); - memset( touched, 0, nPoints*sizeof(touched[0]) ); - - // Find the (lower) left side. - int i; - int iBest = 0; - for( i=1; i < nPoints; i++ ) - { - if( pPoints[indexMap[i]].x < pPoints[indexMap[iBest]].x || - (pPoints[indexMap[i]].x == pPoints[indexMap[iBest]].x && pPoints[indexMap[i]].y < pPoints[indexMap[iBest]].y) ) - { - iBest = i; - } - } - - touched[iBest] = true; - indices[0] = indexMap[iBest]; - nIndices = 1; - - Vector2D curEdge( 0, 1 ); - - // Wind around clockwise. - while( 1 ) - { - Vector2D const *pStartPoint = &pPoints[ indices[nIndices-1] ]; - - float flEdgeAngle = atan2( curEdge.y, curEdge.x ); - - int iMinAngle = -1; - float flMinAngle = 5000; - - for( i=0; i < nPoints; i++ ) - { - Vector2D vTo = pPoints[indexMap[i]] - *pStartPoint; - float flDistToSqr = vTo.LengthSqr(); - if ( flDistToSqr <= 0.1f ) - continue; - - // Get the angle from the edge to this point. - float flAngle = atan2( vTo.y, vTo.x ); - flAngle = AngleOffset( flEdgeAngle, flAngle ); - - if( fabs( flAngle - flMinAngle ) < 0.00001f ) - { - float flDistToTestSqr = pStartPoint->DistToSqr( pPoints[iMinAngle] ); - - // If the angle is the same, pick the point farthest away. - // unless the current one is closing the face loop - if ( iMinAngle != indices[0] && flDistToSqr > flDistToTestSqr ) - { - flMinAngle = flAngle; - iMinAngle = indexMap[i]; - } - } - else if( flAngle < flMinAngle ) - { - flMinAngle = flAngle; - iMinAngle = indexMap[i]; - } - } - - if( iMinAngle == -1 ) - { - // Couldn't find a point? - Assert( false ); - break; - } - else if( iMinAngle == indices[0] ) - { - // Finished. - break; - } - else - { - // Add this point. - if( nIndices >= nMaxIndices ) - break; - - for ( int jj = 0; jj < nIndices; jj++ ) - { - // if this assert hits, this routine is broken and is generating a spiral - // rather than a closed polygon - basically an edge overlap of some kind - Assert(indices[jj] != iMinAngle ); - } - - indices[nIndices] = iMinAngle; - ++nIndices; - } - - curEdge = pPoints[indices[nIndices-1]] - pPoints[indices[nIndices-2]]; - } - - return nIndices; -} - -void FindPortalsLeadingToArea_R( - node_t *pHeadNode, - int iSrcArea, - int iDestArea, - plane_t *pPlane, - CUtlVector &portals ) -{ - if (pHeadNode->planenum != PLANENUM_LEAF) - { - FindPortalsLeadingToArea_R( pHeadNode->children[0], iSrcArea, iDestArea, pPlane, portals ); - FindPortalsLeadingToArea_R( pHeadNode->children[1], iSrcArea, iDestArea, pPlane, portals ); - return; - } - - // Ok.. this is a leaf, check its portals. - int s; - for( portal_t *p = pHeadNode->portals; p ;p = p->next[!s] ) - { - s = (p->nodes[0] == pHeadNode); - - if( !p->nodes[0]->occupied || !p->nodes[1]->occupied ) - continue; - - if( p->nodes[1]->area == iDestArea && p->nodes[0]->area == iSrcArea || - p->nodes[0]->area == iDestArea && p->nodes[1]->area == iSrcArea ) - { - // Make sure the plane normals point the same way. - plane_t *pMapPlane = &g_MainMap->mapplanes[p->onnode->planenum]; - float flDot = fabs( pMapPlane->normal.Dot( pPlane->normal ) ); - if( fabs( 1 - flDot ) < 0.01f ) - { - Vector vPlanePt1 = pPlane->normal * pPlane->dist; - Vector vPlanePt2 = pMapPlane->normal * pMapPlane->dist; - - if( vPlanePt1.DistToSqr( vPlanePt2 ) < 0.01f ) - { - portals.AddToTail( p ); - } - } - } - } -} - - -void EmitClipPortalGeometry( node_t *pHeadNode, portal_t *pPortal, int iSrcArea, dareaportal_t *dp ) -{ - // Build a list of all the points in portals from the same original face. - CUtlVector portals; - FindPortalsLeadingToArea_R( - pHeadNode, - iSrcArea, - dp->otherarea, - &pPortal->plane, - portals ); - - CUtlVector points; - for( int iPortal=0; iPortal < portals.Size(); iPortal++ ) - { - portal_t *pPointPortal = portals[iPortal]; - winding_t *pWinding = pPointPortal->winding; - for( int i=0; i < pWinding->numpoints; i++ ) - { - points.AddToTail( pWinding->p[i] ); - } - } - - // Get the 2D convex hull. - - //// First transform them into a plane. - QAngle vAngles; - Vector vecs[3]; - - VectorAngles( pPortal->plane.normal, vAngles ); - AngleVectors( vAngles, &vecs[0], &vecs[1], &vecs[2] ); - VMatrix mTransform; - mTransform.Identity(); - mTransform.SetBasisVectors( vecs[0], vecs[1], vecs[2] ); - VMatrix mInvTransform = mTransform.Transpose(); - - int i; - CUtlVector points2D; - for( i=0; i < points.Size(); i++ ) - { - Vector vTest = mTransform * points[i]; - points2D.AddToTail( Vector2D( vTest.y, vTest.z ) ); - } - - // Build the hull. - int indices[512]; - int nIndices = Convex2D( points2D.Base(), points2D.Size(), indices, 512 ); - - // Output the hull. - dp->m_FirstClipPortalVert = g_nClipPortalVerts; - dp->m_nClipPortalVerts = nIndices; - - if ( nIndices >= 32 ) - { - Warning( "Warning: area portal has %d verts. Could be a vbsp bug.\n", nIndices ); - } - - if( dp->m_FirstClipPortalVert + dp->m_nClipPortalVerts >= MAX_MAP_PORTALVERTS ) - { - Vector *p = pPortal->winding->p; - Error( "MAX_MAP_PORTALVERTS (probably a broken areaportal near %.1f %.1f %.1f ", p->x, p->y, p->z ); - } - - for( i=0; i < nIndices; i++ ) - { - g_ClipPortalVerts[g_nClipPortalVerts] = points[ indices[i] ]; - ++g_nClipPortalVerts; - } -} - - -// Sets node_t::area for non-leaf nodes (this allows an optimization in the renderer). -void SetNodeAreaIndices_R( node_t *node ) -{ - // All leaf area indices should already be set. - if( node->planenum == PLANENUM_LEAF ) - return; - - // Have the children set their area indices. - SetNodeAreaIndices_R( node->children[0] ); - SetNodeAreaIndices_R( node->children[1] ); - - // If all children (leaves or nodes) are in the same area, then set our area - // to this area as well. Otherwise, set it to -1. - if( node->children[0]->area == node->children[1]->area ) - node->area = node->children[0]->area; - else - node->area = -1; -} - - -/* -============= -EmitAreaPortals - -============= -*/ -void EmitAreaPortals (node_t *headnode) -{ - entity_t *e; - dareaportal_t *dp; - - if (c_areas > MAX_MAP_AREAS) - Error ("Map is split into too many unique areas (max = %d)\nProbably too many areaportals", MAX_MAP_AREAS); - numareas = c_areas+1; - numareaportals = 1; // leave 0 as an error - - // Reset the clip portal vert info. - g_nClipPortalVerts = 0; - - for (int iSrcArea=1 ; iSrcArea<=c_areas ; iSrcArea++) - { - dareas[iSrcArea].firstareaportal = numareaportals; - for (int j=0 ; jareaportalnum) - continue; - - if (e->portalareas[0] == iSrcArea || e->portalareas[1] == iSrcArea) - { - int iSide = (e->portalareas[0] == iSrcArea); - - // We're only interested in the portal that divides the two areas. - // One of the portals that leads into the CONTENTS_AREAPORTAL just bounds - // the same two areas but the other bounds two different ones. - portal_t *pLeadingPortal = e->m_pPortalsLeadingIntoAreas[0]; - if( pLeadingPortal->nodes[0]->area == pLeadingPortal->nodes[1]->area ) - pLeadingPortal = e->m_pPortalsLeadingIntoAreas[1]; - - if( pLeadingPortal ) - { - Assert( pLeadingPortal->nodes[0]->area != pLeadingPortal->nodes[1]->area ); - - dp = &dareaportals[numareaportals]; - numareaportals++; - - dp->m_PortalKey = e->areaportalnum; - dp->otherarea = e->portalareas[iSide]; - dp->planenum = pLeadingPortal->onnode->planenum; - - Assert( pLeadingPortal->nodes[0]->planenum == PLANENUM_LEAF ); - Assert( pLeadingPortal->nodes[1]->planenum == PLANENUM_LEAF ); - - if( pLeadingPortal->nodes[0]->area == dp->otherarea ) - { - // Use the flipped version of the plane. - dp->planenum = (dp->planenum & ~1) | (~dp->planenum & 1); - } - - EmitClipPortalGeometry( headnode, pLeadingPortal, iSrcArea, dp ); - } - } - } - - dareas[iSrcArea].numareaportals = numareaportals - dareas[iSrcArea].firstareaportal; - } - - SetNodeAreaIndices_R( headnode ); - - qprintf ("%5i numareas\n", numareas); - qprintf ("%5i numareaportals\n", numareaportals); -} - -/* -============= -FloodAreas - -Mark each leaf with an area, bounded by CONTENTS_AREAPORTAL -============= -*/ -void FloodAreas (tree_t *tree) -{ - int start = Plat_FloatTime(); - qprintf ("--- FloodAreas ---\n"); - Msg("Processing areas..."); - FindAreas_r (tree->headnode); - SetAreaPortalAreas_r (tree, tree->headnode); - qprintf ("%5i areas\n", c_areas); - Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); -} - -//====================================================== - -int c_outside; -int c_inside; -int c_solid; - -void FillOutside_r (node_t *node) -{ - if (node->planenum != PLANENUM_LEAF) - { - FillOutside_r (node->children[0]); - FillOutside_r (node->children[1]); - return; - } - - // anything not reachable by an entity - // can be filled away - if (!node->occupied) - { - if (node->contents != CONTENTS_SOLID) - { - c_outside++; - node->contents = CONTENTS_SOLID; - } - else - c_solid++; - } - else - c_inside++; - -} - -/* -============= -FillOutside - -Fill all nodes that can't be reached by entities -============= -*/ -void FillOutside (node_t *headnode) -{ - c_outside = 0; - c_inside = 0; - c_solid = 0; - qprintf ("--- FillOutside ---\n"); - FillOutside_r (headnode); - qprintf ("%5i solid leafs\n", c_solid); - qprintf ("%5i leafs filled\n", c_outside); - qprintf ("%5i inside leafs\n", c_inside); -} - - -static float ComputeDistFromPlane( winding_t *pWinding, plane_t *pPlane, float maxdist ) -{ - float totaldist = 0.0f; - for (int i = 0; i < pWinding->numpoints; ++i) - { - totaldist += fabs(DotProduct( pPlane->normal, pWinding->p[i] ) - pPlane->dist); - if (totaldist > maxdist) - return totaldist; - } - return totaldist; -} - - -//----------------------------------------------------------------------------- -// Display portal error -//----------------------------------------------------------------------------- -static void DisplayPortalError( portal_t *p, int viscontents ) -{ - char contents[3][1024]; - PrintBrushContentsToString( p->nodes[0]->contents, contents[0], sizeof( contents[0] ) ); - PrintBrushContentsToString( p->nodes[1]->contents, contents[1], sizeof( contents[1] ) ); - PrintBrushContentsToString( viscontents, contents[2], sizeof( contents[2] ) ); - - Vector center; - WindingCenter( p->winding, center ); - Warning( "\nFindPortalSide: Couldn't find a good match for which brush to assign to a portal near (%.1f %.1f %.1f)\n", center.x, center.y, center.z); - Warning( "Leaf 0 contents: %s\n", contents[0] ); - Warning( "Leaf 1 contents: %s\n", contents[1] ); - Warning( "viscontents (node 0 contents ^ node 1 contents): %s\n", contents[2] ); - Warning( "This means that none of the brushes in leaf 0 or 1 that touches the portal has %s\n", contents[2] ); - Warning( "Check for a huge brush enclosing the coordinates above that has contents %s\n", contents[2] ); - Warning( "Candidate brush IDs: " ); - - CUtlVector listed; - for (int j=0 ; j<2 ; j++) - { - node_t *n = p->nodes[j]; - for (bspbrush_t *bb=n->brushlist ; bb ; bb=bb->next) - { - mapbrush_t *brush = bb->original; - if ( brush->contents & viscontents ) - { - if ( listed.Find( brush->brushnum ) == -1 ) - { - listed.AddToTail( brush->brushnum ); - Warning( "Brush %d: ", brush->id ); - } - } - } - } - Warning( "\n\n" ); -} - - -//============================================================== - -/* -============ -FindPortalSide - -Finds a brush side to use for texturing the given portal -============ -*/ -void FindPortalSide (portal_t *p) -{ - int viscontents; - bspbrush_t *bb; - mapbrush_t *brush; - node_t *n; - int i,j; - int planenum; - side_t *side, *bestside; - float bestdist; - plane_t *p1, *p2; - - // decide which content change is strongest - // solid > lava > water, etc - viscontents = VisibleContents (p->nodes[0]->contents ^ p->nodes[1]->contents); - if (!viscontents) - { - return; - } - - planenum = p->onnode->planenum; - bestside = NULL; - bestdist = 1000000; - - for (j=0 ; j<2 ; j++) - { - n = p->nodes[j]; - p1 = &g_MainMap->mapplanes[p->onnode->planenum]; - - for (bb=n->brushlist ; bb ; bb=bb->next) - { - brush = bb->original; - if ( !(brush->contents & viscontents) ) - continue; - - for (i=0 ; inumsides ; i++) - { - side = &brush->original_sides[i]; - if (side->bevel) - continue; - if (side->texinfo == TEXINFO_NODE) - continue; // non-visible - - if ((side->planenum&~1) == planenum) - { // exact match - bestside = &brush->original_sides[i]; - bestdist = 0.0f; - goto gotit; - } - - p2 = &g_MainMap->mapplanes[side->planenum&~1]; - - float dist = ComputeDistFromPlane( p->winding, p2, bestdist ); - if (dist < bestdist) - { - bestside = side; - bestdist = dist; - } - } - } - } - -gotit: - if (!bestside) - qprintf ("WARNING: side not found for portal\n"); - - // Compute average dist, check for problems... - if ((bestdist / p->winding->numpoints) > 2) - { - static int nWarnCount = 0; - if ( nWarnCount < 8 ) - { - DisplayPortalError( p, viscontents ); - if ( ++nWarnCount == 8 ) - { - Warning("*** Suppressing further FindPortalSide errors.... ***\n" ); - } - } - } - - p->sidefound = true; - p->side = bestside; -} - - -/* -=============== -MarkVisibleSides_r - -=============== -*/ -void MarkVisibleSides_r (node_t *node) -{ - portal_t *p; - int s; - - if (node->planenum != PLANENUM_LEAF) - { - MarkVisibleSides_r (node->children[0]); - MarkVisibleSides_r (node->children[1]); - return; - } - - // empty leafs are never boundary leafs - if (!node->contents) - return; - - // see if there is a visible face - for (p=node->portals ; p ; p = p->next[!s]) - { - s = (p->nodes[0] == node); - if (!p->onnode) - continue; // edge of world - if (!p->sidefound) - FindPortalSide (p); - if (p->side) - p->side->visible = true; - } - -} - -/* -============= -MarkVisibleSides - -============= -*/ -// UNDONE: Put detail brushes in a separate list (not mapbrushes) ? -void MarkVisibleSides (tree_t *tree, int startbrush, int endbrush, int detailScreen) -{ - int i, j; - mapbrush_t *mb; - int numsides; - qboolean detail; - - qprintf ("--- MarkVisibleSides ---\n"); - - // clear all the visible flags - for (i=startbrush ; imapbrushes[i]; - - if ( detailScreen != FULL_DETAIL ) - { - qboolean onlyDetail = (detailScreen==ONLY_DETAIL)?true:false; - // true for detail brushes - detail = (mb->contents & CONTENTS_DETAIL) ? true : false; - if ( onlyDetail ^ detail ) - { - // both of these must have the same value or we're not interested in this brush - continue; - } - } - - numsides = mb->numsides; - for (j=0 ; joriginal_sides[j].visible = false; - } - - // set visible flags on the sides that are used by portals - MarkVisibleSides_r (tree->headnode); -} - - -//----------------------------------------------------------------------------- -// Used to determine which sides are visible -//----------------------------------------------------------------------------- -void MarkVisibleSides (tree_t *tree, mapbrush_t **ppBrushes, int nCount ) -{ - qprintf ("--- MarkVisibleSides ---\n"); - - // clear all the visible flags - int i, j; - for ( i=0; i < nCount; ++i ) - { - mapbrush_t *mb = ppBrushes[i]; - int numsides = mb->numsides; - for (j=0 ; joriginal_sides[j].visible = false; - } - } - - // set visible flags on the sides that are used by portals - MarkVisibleSides_r( tree->headnode ); -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" +#include "utlvector.h" +#include "mathlib/vmatrix.h" +#include "iscratchpad3d.h" +#include "csg.h" +#include "fmtstr.h" + +int c_active_portals; +int c_peak_portals; +int c_boundary; +int c_boundary_sides; + +/* +=========== +AllocPortal +=========== +*/ +portal_t *AllocPortal (void) +{ + static int s_PortalCount = 0; + + portal_t *p; + + if (numthreads == 1) + c_active_portals++; + if (c_active_portals > c_peak_portals) + c_peak_portals = c_active_portals; + + p = (portal_t*)malloc (sizeof(portal_t)); + memset (p, 0, sizeof(portal_t)); + p->id = s_PortalCount; + ++s_PortalCount; + + return p; +} + +void FreePortal (portal_t *p) +{ + if (p->winding) + FreeWinding (p->winding); + if (numthreads == 1) + c_active_portals--; + free (p); +} + +//============================================================== + +/* +============== +VisibleContents + +Returns the single content bit of the +strongest visible content present +============== +*/ +int VisibleContents (int contents) +{ + int i; + + for (i=1 ; i<=LAST_VISIBLE_CONTENTS ; i<<=1) + { + if (contents & i ) + { + return i; + } + } + + return 0; +} + + +/* +=============== +ClusterContents +=============== +*/ +int ClusterContents (node_t *node) +{ + int c1, c2, c; + + if (node->planenum == PLANENUM_LEAF) + return node->contents; + + c1 = ClusterContents(node->children[0]); + c2 = ClusterContents(node->children[1]); + c = c1|c2; + + // a cluster may include some solid detail areas, but + // still be seen into + if ( ! (c1&CONTENTS_SOLID) || ! (c2&CONTENTS_SOLID) ) + c &= ~CONTENTS_SOLID; + return c; +} + +/* +============= +Portal_VisFlood + +Returns true if the portal is empty or translucent, allowing +the PVS calculation to see through it. +The nodes on either side of the portal may actually be clusters, +not leafs, so all contents should be ored together +============= +*/ +qboolean Portal_VisFlood (portal_t *p) +{ + int c1, c2; + + if (!p->onnode) + return false; // to global outsideleaf + + c1 = ClusterContents(p->nodes[0]); + c2 = ClusterContents(p->nodes[1]); + + if (!VisibleContents (c1^c2)) + return true; + + if (c1 & (CONTENTS_TRANSLUCENT|CONTENTS_DETAIL)) + c1 = 0; + if (c2 & (CONTENTS_TRANSLUCENT|CONTENTS_DETAIL)) + c2 = 0; + + if ( (c1|c2) & CONTENTS_SOLID ) + return false; // can't see through solid + + if (! (c1 ^ c2)) + return true; // identical on both sides + + if (!VisibleContents (c1^c2)) + return true; + return false; +} + + +/* +=============== +Portal_EntityFlood + +The entity flood determines which areas are +"outside" on the map, which are then filled in. +Flowing from side s to side !s +=============== +*/ +qboolean Portal_EntityFlood (portal_t *p, int s) +{ + if (p->nodes[0]->planenum != PLANENUM_LEAF + || p->nodes[1]->planenum != PLANENUM_LEAF) + Error ("Portal_EntityFlood: not a leaf"); + + // can never cross to a solid + if ( (p->nodes[0]->contents & CONTENTS_SOLID) + || (p->nodes[1]->contents & CONTENTS_SOLID) ) + return false; + + // can flood through everything else + return true; +} + +qboolean Portal_AreaLeakFlood (portal_t *p, int s) +{ + if ( !Portal_EntityFlood( p, s ) ) + return false; + + // can never cross through areaportal + if ( (p->nodes[0]->contents & CONTENTS_AREAPORTAL) + || (p->nodes[1]->contents & CONTENTS_AREAPORTAL) ) + return false; + + // can flood through everything else + return true; +} + + +//============================================================================= + +int c_tinyportals; + +/* +============= +AddPortalToNodes +============= +*/ +void AddPortalToNodes (portal_t *p, node_t *front, node_t *back) +{ + if (p->nodes[0] || p->nodes[1]) + Error ("AddPortalToNode: allready included"); + + p->nodes[0] = front; + p->next[0] = front->portals; + front->portals = p; + + p->nodes[1] = back; + p->next[1] = back->portals; + back->portals = p; +} + + +/* +============= +RemovePortalFromNode +============= +*/ +void RemovePortalFromNode (portal_t *portal, node_t *l) +{ + portal_t **pp, *t; + +// remove reference to the current portal + pp = &l->portals; + while (1) + { + t = *pp; + if (!t) + Error ("RemovePortalFromNode: portal not in leaf"); + + if ( t == portal ) + break; + + if (t->nodes[0] == l) + pp = &t->next[0]; + else if (t->nodes[1] == l) + pp = &t->next[1]; + else + Error ("RemovePortalFromNode: portal not bounding leaf"); + } + + if (portal->nodes[0] == l) + { + *pp = portal->next[0]; + portal->nodes[0] = NULL; + } + else if (portal->nodes[1] == l) + { + *pp = portal->next[1]; + portal->nodes[1] = NULL; + } +} + +//============================================================================ + +void PrintPortal (portal_t *p) +{ + int i; + winding_t *w; + + w = p->winding; + for (i=0 ; inumpoints ; i++) + Msg ("(%5.0f,%5.0f,%5.0f)\n",w->p[i][0] + , w->p[i][1], w->p[i][2]); +} + +// because of water areaportals support, the areaportal may not be the only brush on this node +bspbrush_t *AreaportalBrushForNode( node_t *node ) +{ + bspbrush_t *b = node->brushlist; + while ( b && !(b->original->contents & CONTENTS_AREAPORTAL) ) + { + b = b->next; + } + Assert( b->original->entitynum != 0 ); + return b; +} + +/* +================ +MakeHeadnodePortals + +The created portals will face the global outside_node +================ +*/ +// buffer space around sides of nodes +#define SIDESPACE 8 +void MakeHeadnodePortals (tree_t *tree) +{ + Vector bounds[2]; + int i, j, n; + portal_t *p, *portals[6]; + plane_t bplanes[6], *pl; + node_t *node; + + node = tree->headnode; + +// pad with some space so there will never be null volume leafs + for (i=0 ; i<3 ; i++) + { + bounds[0][i] = tree->mins[i] - SIDESPACE; + bounds[1][i] = tree->maxs[i] + SIDESPACE; + } + + tree->outside_node.planenum = PLANENUM_LEAF; + tree->outside_node.brushlist = NULL; + tree->outside_node.portals = NULL; + tree->outside_node.contents = 0; + + for (i=0 ; i<3 ; i++) + for (j=0 ; j<2 ; j++) + { + n = j*3 + i; + + p = AllocPortal (); + portals[n] = p; + + pl = &bplanes[n]; + memset (pl, 0, sizeof(*pl)); + if (j) + { + pl->normal[i] = -1; + pl->dist = -bounds[j][i]; + } + else + { + pl->normal[i] = 1; + pl->dist = bounds[j][i]; + } + p->plane = *pl; + p->winding = BaseWindingForPlane (pl->normal, pl->dist); + AddPortalToNodes (p, node, &tree->outside_node); + } + +// clip the basewindings by all the other planes + for (i=0 ; i<6 ; i++) + { + for (j=0 ; j<6 ; j++) + { + if (j == i) + continue; + ChopWindingInPlace (&portals[i]->winding, bplanes[j].normal, bplanes[j].dist, ON_EPSILON); + } + } +} + +//=================================================== + + +/* +================ +BaseWindingForNode +================ +*/ +#define BASE_WINDING_EPSILON 0.001 +#define SPLIT_WINDING_EPSILON 0.001 + +winding_t *BaseWindingForNode (node_t *node) +{ + winding_t *w; + node_t *n; + plane_t *plane; + Vector normal; + vec_t dist; + + w = BaseWindingForPlane (g_MainMap->mapplanes[node->planenum].normal, g_MainMap->mapplanes[node->planenum].dist); + + // clip by all the parents + for (n=node->parent ; n && w ; ) + { + plane = &g_MainMap->mapplanes[n->planenum]; + + if (n->children[0] == node) + { // take front + ChopWindingInPlace (&w, plane->normal, plane->dist, BASE_WINDING_EPSILON); + } + else + { // take back + VectorSubtract (vec3_origin, plane->normal, normal); + dist = -plane->dist; + ChopWindingInPlace (&w, normal, dist, BASE_WINDING_EPSILON); + } + node = n; + n = n->parent; + } + + return w; +} + +//============================================================ + +/* +================== +MakeNodePortal + +create the new portal by taking the full plane winding for the cutting plane +and clipping it by all of parents of this node +================== +*/ +void MakeNodePortal (node_t *node) +{ + portal_t *new_portal, *p; + winding_t *w; + Vector normal; + float dist = 0.0f; + int side = 0; + + w = BaseWindingForNode (node); + + // clip the portal by all the other portals in the node + for (p = node->portals ; p && w; p = p->next[side]) + { + if (p->nodes[0] == node) + { + side = 0; + VectorCopy (p->plane.normal, normal); + dist = p->plane.dist; + } + else if (p->nodes[1] == node) + { + side = 1; + VectorSubtract (vec3_origin, p->plane.normal, normal); + dist = -p->plane.dist; + } + else + { + Error ("CutNodePortals_r: mislinked portal"); + } + + ChopWindingInPlace (&w, normal, dist, 0.1); + } + + if (!w) + { + return; + } + + if (WindingIsTiny (w)) + { + c_tinyportals++; + FreeWinding (w); + return; + } + + + new_portal = AllocPortal (); + new_portal->plane = g_MainMap->mapplanes[node->planenum]; + new_portal->onnode = node; + new_portal->winding = w; + + AddPortalToNodes (new_portal, node->children[0], node->children[1]); +} + + +/* +============== +SplitNodePortals + +Move or split the portals that bound node so that the node's +children have portals instead of node. +============== +*/ +void SplitNodePortals (node_t *node) +{ + portal_t *p, *next_portal, *new_portal; + node_t *f, *b, *other_node; + int side = 0; + plane_t *plane; + winding_t *frontwinding, *backwinding; + + plane = &g_MainMap->mapplanes[node->planenum]; + f = node->children[0]; + b = node->children[1]; + + for (p = node->portals ; p ; p = next_portal) + { + if (p->nodes[0] == node) + side = 0; + else if (p->nodes[1] == node) + side = 1; + else + Error ("CutNodePortals_r: mislinked portal"); + next_portal = p->next[side]; + + other_node = p->nodes[!side]; + RemovePortalFromNode (p, p->nodes[0]); + RemovePortalFromNode (p, p->nodes[1]); + +// +// cut the portal into two portals, one on each side of the cut plane +// + ClipWindingEpsilon (p->winding, plane->normal, plane->dist, + SPLIT_WINDING_EPSILON, &frontwinding, &backwinding); + + if (frontwinding && WindingIsTiny(frontwinding)) + { + FreeWinding (frontwinding); + frontwinding = NULL; + c_tinyportals++; + } + + if (backwinding && WindingIsTiny(backwinding)) + { + FreeWinding (backwinding); + backwinding = NULL; + c_tinyportals++; + } + + if (!frontwinding && !backwinding) + { // tiny windings on both sides + continue; + } + + if (!frontwinding) + { + FreeWinding (backwinding); + if (side == 0) + AddPortalToNodes (p, b, other_node); + else + AddPortalToNodes (p, other_node, b); + continue; + } + if (!backwinding) + { + FreeWinding (frontwinding); + if (side == 0) + AddPortalToNodes (p, f, other_node); + else + AddPortalToNodes (p, other_node, f); + continue; + } + + // the winding is split + new_portal = AllocPortal (); + *new_portal = *p; + new_portal->winding = backwinding; + FreeWinding (p->winding); + p->winding = frontwinding; + + if (side == 0) + { + AddPortalToNodes (p, f, other_node); + AddPortalToNodes (new_portal, b, other_node); + } + else + { + AddPortalToNodes (p, other_node, f); + AddPortalToNodes (new_portal, other_node, b); + } + } + + node->portals = NULL; +} + + +/* +================ +CalcNodeBounds +================ +*/ +void CalcNodeBounds (node_t *node) +{ + portal_t *p; + int s; + int i; + + // calc mins/maxs for both leafs and nodes + ClearBounds (node->mins, node->maxs); + for (p = node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + for (i=0 ; iwinding->numpoints ; i++) + AddPointToBounds (p->winding->p[i], node->mins, node->maxs); + } +} + + +/* +================== +MakeTreePortals_r +================== +*/ +void MakeTreePortals_r (node_t *node) +{ + int i; + + CalcNodeBounds (node); + if (node->mins[0] >= node->maxs[0]) + { + Warning("WARNING: node without a volume\n"); + } + + for (i=0 ; i<3 ; i++) + { + if (node->mins[i] < (MIN_COORD_INTEGER-SIDESPACE) || node->maxs[i] > (MAX_COORD_INTEGER+SIDESPACE)) + { + const char *pMatName = ""; + // split by brush side + if ( node->side ) + { + texinfo_t *pTexInfo = &texinfo[node->side->texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + pMatName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + } + Vector point = node->portals->winding->p[0]; + Warning("WARNING: BSP node with unbounded volume (material: %s, near %s)\n", pMatName, VecToString(point) ); + break; + } + } + if (node->planenum == PLANENUM_LEAF) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + MakeTreePortals_r (node->children[0]); + MakeTreePortals_r (node->children[1]); +} + +/* +================== +MakeTreePortals +================== +*/ +void MakeTreePortals (tree_t *tree) +{ + MakeHeadnodePortals (tree); + MakeTreePortals_r (tree->headnode); +} + +/* +========================================================= + +FLOOD ENTITIES + +========================================================= +*/ + +//----------------------------------------------------------------------------- +// Purpose: Floods outward from the given node, marking visited nodes with +// the number of hops from a node with an entity. If we ever mark +// the outside_node for this tree, we've leaked. +// Input : node - +// dist - +//----------------------------------------------------------------------------- +void FloodPortals_r (node_t *node, int dist) +{ + portal_t *p; + int s; + + node->occupied = dist; + + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + + // Skip nodes that have already been marked. + if (p->nodes[!s]->occupied) + continue; + + // Skip portals that lead to or from nodes with solid contents. + if (!Portal_EntityFlood (p, s)) + continue; + + FloodPortals_r (p->nodes[!s], dist+1); + } +} + +void FloodAreaLeak_r( node_t *node, int dist ) +{ + portal_t *p; + int s; + + node->occupied = dist; + + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + + if (p->nodes[!s]->occupied) + continue; + + if (!Portal_AreaLeakFlood (p, s)) + continue; + + FloodAreaLeak_r( p->nodes[!s], dist+1 ); + } +} + +void ClearOccupied_r( node_t *headnode ) +{ + if ( !headnode ) + return; + + headnode->occupied = 0; + ClearOccupied_r( headnode->children[0] ); + ClearOccupied_r( headnode->children[1] ); +} + +void FloodAreaLeak( node_t *headnode, node_t *pFirstSide ) +{ + ClearOccupied_r( headnode ); + FloodAreaLeak_r( pFirstSide, 2 ); +} + +//----------------------------------------------------------------------------- +// Purpose: For the given entity at the given origin, finds the leaf node in the +// BSP tree that the entity occupies. +// +// We then flood outward from that leaf to see if the entity leaks. +// Input : headnode - +// origin - +// occupant - +// Output : Returns false if the entity is in solid, true if it is not. +//----------------------------------------------------------------------------- +qboolean PlaceOccupant (node_t *headnode, Vector& origin, entity_t *occupant) +{ + node_t *node; + vec_t d; + plane_t *plane; + + // find the leaf to start in + node = headnode; + while (node->planenum != PLANENUM_LEAF) + { + plane = &g_MainMap->mapplanes[node->planenum]; + d = DotProduct (origin, plane->normal) - plane->dist; + if (d >= 0) + node = node->children[0]; + else + node = node->children[1]; + } + + if (node->contents == CONTENTS_SOLID) + return false; + + node->occupant = occupant; + + // Flood outward from here to see if this entity leaks. + FloodPortals_r (node, 1); + + return true; +} + +/* +============= +FloodEntities + +Marks all nodes that can be reached by entites +============= +*/ +qboolean FloodEntities (tree_t *tree) +{ + int i; + Vector origin; + char *cl; + qboolean inside; + node_t *headnode; + + headnode = tree->headnode; + qprintf ("--- FloodEntities ---\n"); + inside = false; + tree->outside_node.occupied = 0; + + for (i=1 ; ioutside_node.occupied) + { + qprintf ("entity reached from outside -- no filling\n" ); + } + + return (qboolean)(inside && !tree->outside_node.occupied); +} + + +/* +========================================================= + +FLOOD AREAS + +========================================================= +*/ + +int c_areas; + +bool IsAreaportalNode( node_t *node ) +{ + return ( node->contents & CONTENTS_AREAPORTAL ) ? true : false; +} +/* +============= +FloodAreas_r +============= +*/ + +void FloodAreas_r (node_t *node, portal_t *pSeeThrough) +{ + portal_t *p; + int s; + bspbrush_t *b; + entity_t *e; + + if ( IsAreaportalNode(node) ) + { + // this node is part of an area portal + b = AreaportalBrushForNode( node ); + e = &entities[b->original->entitynum]; + + // if the current area has allready touched this + // portal, we are done + if (e->portalareas[0] == c_areas || e->portalareas[1] == c_areas) + return; + + // note the current area as bounding the portal + if (e->portalareas[1]) + { + Warning("WARNING: areaportal entity %i (brush %i) touches > 2 areas\n", b->original->entitynum, b->original->id ); + return; + } + + if (e->portalareas[0]) + { + e->portalareas[1] = c_areas; + e->m_pPortalsLeadingIntoAreas[1] = pSeeThrough; + } + else + { + e->portalareas[0] = c_areas; + e->m_pPortalsLeadingIntoAreas[0] = pSeeThrough; + } + + return; + } + + if (node->area) + return; // allready got it + node->area = c_areas; + + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); +#if 0 + if (p->nodes[!s]->occupied) + continue; +#endif + if (!Portal_EntityFlood (p, s)) + continue; + + FloodAreas_r (p->nodes[!s], p); + } +} + +/* +============= +FindAreas_r + +Just decend the tree, and for each node that hasn't had an +area set, flood fill out from there +============= +*/ +void FindAreas_r (node_t *node) +{ + if (node->planenum != PLANENUM_LEAF) + { + FindAreas_r (node->children[0]); + FindAreas_r (node->children[1]); + return; + } + + if (node->area) + return; // allready got it + + if (node->contents & CONTENTS_SOLID) + return; + + if (!node->occupied) + return; // not reachable by entities + + // area portals are allways only flooded into, never + // out of + if (IsAreaportalNode(node)) + return; + + c_areas++; + FloodAreas_r (node, NULL); +} + + +void ReportAreaportalLeak( tree_t *tree, node_t *node ) +{ + portal_t *p, *pStart = NULL; + int s; + + // Find a portal out of this areaportal into empty space + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + if ( !Portal_EntityFlood( p, !s ) ) + continue; + if ( p->nodes[!s]->contents & CONTENTS_AREAPORTAL ) + continue; + + pStart = p; + break; + } + + if ( pStart ) + { + s = pStart->nodes[0] == node; + Assert(!(pStart->nodes[s]->contents & CONTENTS_AREAPORTAL) ); + // flood fill the area outside this areaportal brush + FloodAreaLeak( tree->headnode, pStart->nodes[s] ); + + // find the portal into the longest path around the portal + portal_t *pBest = NULL; + int bestDist = 0; + for (p=node->portals ; p ; p = p->next[s]) + { + if ( p == pStart ) + continue; + + s = (p->nodes[1] == node); + if ( p->nodes[!s]->occupied > bestDist ) + { + pBest = p; + bestDist = p->nodes[!s]->occupied; + } + } + if ( pBest ) + { + s = (pBest->nodes[0] == node); + // write the linefile that goes from pBest to pStart + AreaportalLeakFile( tree, pStart, pBest, pBest->nodes[s] ); + } + } +} + + +/* +============= +SetAreaPortalAreas_r + +Just decend the tree, and for each node that hasn't had an +area set, flood fill out from there +============= +*/ +void SetAreaPortalAreas_r (tree_t *tree, node_t *node) +{ + bspbrush_t *b; + entity_t *e; + + if (node->planenum != PLANENUM_LEAF) + { + SetAreaPortalAreas_r (tree, node->children[0]); + SetAreaPortalAreas_r (tree, node->children[1]); + return; + } + + if (IsAreaportalNode(node)) + { + if (node->area) + return; // already set + + b = AreaportalBrushForNode( node ); + e = &entities[b->original->entitynum]; + node->area = e->portalareas[0]; + if (!e->portalareas[1]) + { + ReportAreaportalLeak( tree, node ); + Warning("\nBrush %i: areaportal brush doesn't touch two areas\n", b->original->id); + return; + } + } +} + + +// Return a positive value between 0 and 2*PI telling the angle distance +// from flBaseAngle to flTestAngle. +float AngleOffset( float flBaseAngle, float flTestAngle ) +{ + while( flTestAngle > flBaseAngle ) + flTestAngle -= 2 * M_PI; + + return fmod( flBaseAngle - flTestAngle, (float) (2 * M_PI) ); +} + + +int FindUniquePoints( const Vector2D *pPoints, int nPoints, int *indexMap, int nMaxIndexMapPoints, float flTolerance ) +{ + float flToleranceSqr = flTolerance * flTolerance; + + // This could be slightly more efficient. + int nUniquePoints = 0; + for ( int i=0; i < nPoints; i++ ) + { + int j; + for ( j=0; j < nUniquePoints; j++ ) + { + if ( pPoints[i].DistToSqr( pPoints[indexMap[j]] ) < flToleranceSqr ) + break; + } + if ( j == nUniquePoints ) + { + if ( nUniquePoints >= nMaxIndexMapPoints ) + Error( "FindUniquePoints: overflowed unique point list (size %d).", nMaxIndexMapPoints ); + + indexMap[nUniquePoints++] = i; + } + } + + return nUniquePoints; +} + +// Build a 2D convex hull of the set of points. +// This essentially giftwraps the points as it walks around the perimeter. +int Convex2D( Vector2D const *pPoints, int nPoints, int *indices, int nMaxIndices ) +{ + int nIndices = 0; + bool touched[512]; + int indexMap[512]; + + if( nPoints == 0 ) + return 0; + + + // If we don't collapse the points into a unique set, we can loop around forever + // and max out nMaxIndices. + nPoints = FindUniquePoints( pPoints, nPoints, indexMap, ARRAYSIZE( indexMap ), 0.1f ); + memset( touched, 0, nPoints*sizeof(touched[0]) ); + + // Find the (lower) left side. + int i; + int iBest = 0; + for( i=1; i < nPoints; i++ ) + { + if( pPoints[indexMap[i]].x < pPoints[indexMap[iBest]].x || + (pPoints[indexMap[i]].x == pPoints[indexMap[iBest]].x && pPoints[indexMap[i]].y < pPoints[indexMap[iBest]].y) ) + { + iBest = i; + } + } + + touched[iBest] = true; + indices[0] = indexMap[iBest]; + nIndices = 1; + + Vector2D curEdge( 0, 1 ); + + // Wind around clockwise. + while( 1 ) + { + Vector2D const *pStartPoint = &pPoints[ indices[nIndices-1] ]; + + float flEdgeAngle = atan2( curEdge.y, curEdge.x ); + + int iMinAngle = -1; + float flMinAngle = 5000; + + for( i=0; i < nPoints; i++ ) + { + Vector2D vTo = pPoints[indexMap[i]] - *pStartPoint; + float flDistToSqr = vTo.LengthSqr(); + if ( flDistToSqr <= 0.1f ) + continue; + + // Get the angle from the edge to this point. + float flAngle = atan2( vTo.y, vTo.x ); + flAngle = AngleOffset( flEdgeAngle, flAngle ); + + if( fabs( flAngle - flMinAngle ) < 0.00001f ) + { + float flDistToTestSqr = pStartPoint->DistToSqr( pPoints[iMinAngle] ); + + // If the angle is the same, pick the point farthest away. + // unless the current one is closing the face loop + if ( iMinAngle != indices[0] && flDistToSqr > flDistToTestSqr ) + { + flMinAngle = flAngle; + iMinAngle = indexMap[i]; + } + } + else if( flAngle < flMinAngle ) + { + flMinAngle = flAngle; + iMinAngle = indexMap[i]; + } + } + + if( iMinAngle == -1 ) + { + // Couldn't find a point? + Assert( false ); + break; + } + else if( iMinAngle == indices[0] ) + { + // Finished. + break; + } + else + { + // Add this point. + if( nIndices >= nMaxIndices ) + break; + + for ( int jj = 0; jj < nIndices; jj++ ) + { + // if this assert hits, this routine is broken and is generating a spiral + // rather than a closed polygon - basically an edge overlap of some kind + Assert(indices[jj] != iMinAngle ); + } + + indices[nIndices] = iMinAngle; + ++nIndices; + } + + curEdge = pPoints[indices[nIndices-1]] - pPoints[indices[nIndices-2]]; + } + + return nIndices; +} + +void FindPortalsLeadingToArea_R( + node_t *pHeadNode, + int iSrcArea, + int iDestArea, + plane_t *pPlane, + CUtlVector &portals ) +{ + if (pHeadNode->planenum != PLANENUM_LEAF) + { + FindPortalsLeadingToArea_R( pHeadNode->children[0], iSrcArea, iDestArea, pPlane, portals ); + FindPortalsLeadingToArea_R( pHeadNode->children[1], iSrcArea, iDestArea, pPlane, portals ); + return; + } + + // Ok.. this is a leaf, check its portals. + int s; + for( portal_t *p = pHeadNode->portals; p ;p = p->next[!s] ) + { + s = (p->nodes[0] == pHeadNode); + + if( !p->nodes[0]->occupied || !p->nodes[1]->occupied ) + continue; + + if( p->nodes[1]->area == iDestArea && p->nodes[0]->area == iSrcArea || + p->nodes[0]->area == iDestArea && p->nodes[1]->area == iSrcArea ) + { + // Make sure the plane normals point the same way. + plane_t *pMapPlane = &g_MainMap->mapplanes[p->onnode->planenum]; + float flDot = fabs( pMapPlane->normal.Dot( pPlane->normal ) ); + if( fabs( 1 - flDot ) < 0.01f ) + { + Vector vPlanePt1 = pPlane->normal * pPlane->dist; + Vector vPlanePt2 = pMapPlane->normal * pMapPlane->dist; + + if( vPlanePt1.DistToSqr( vPlanePt2 ) < 0.01f ) + { + portals.AddToTail( p ); + } + } + } + } +} + + +void EmitClipPortalGeometry( node_t *pHeadNode, portal_t *pPortal, int iSrcArea, dareaportal_t *dp ) +{ + // Build a list of all the points in portals from the same original face. + CUtlVector portals; + FindPortalsLeadingToArea_R( + pHeadNode, + iSrcArea, + dp->otherarea, + &pPortal->plane, + portals ); + + CUtlVector points; + for( int iPortal=0; iPortal < portals.Size(); iPortal++ ) + { + portal_t *pPointPortal = portals[iPortal]; + winding_t *pWinding = pPointPortal->winding; + for( int i=0; i < pWinding->numpoints; i++ ) + { + points.AddToTail( pWinding->p[i] ); + } + } + + // Get the 2D convex hull. + + //// First transform them into a plane. + QAngle vAngles; + Vector vecs[3]; + + VectorAngles( pPortal->plane.normal, vAngles ); + AngleVectors( vAngles, &vecs[0], &vecs[1], &vecs[2] ); + VMatrix mTransform; + mTransform.Identity(); + mTransform.SetBasisVectors( vecs[0], vecs[1], vecs[2] ); + VMatrix mInvTransform = mTransform.Transpose(); + + int i; + CUtlVector points2D; + for( i=0; i < points.Size(); i++ ) + { + Vector vTest = mTransform * points[i]; + points2D.AddToTail( Vector2D( vTest.y, vTest.z ) ); + } + + // Build the hull. + int indices[512]; + int nIndices = Convex2D( points2D.Base(), points2D.Size(), indices, 512 ); + + // Output the hull. + dp->m_FirstClipPortalVert = g_nClipPortalVerts; + dp->m_nClipPortalVerts = nIndices; + + if ( nIndices >= 32 ) + { + Warning( "Warning: area portal has %d verts. Could be a vbsp bug.\n", nIndices ); + } + + if( dp->m_FirstClipPortalVert + dp->m_nClipPortalVerts >= MAX_MAP_PORTALVERTS ) + { + Vector *p = pPortal->winding->p; + Error( "MAX_MAP_PORTALVERTS (probably a broken areaportal near %.1f %.1f %.1f ", p->x, p->y, p->z ); + } + + for( i=0; i < nIndices; i++ ) + { + g_ClipPortalVerts[g_nClipPortalVerts] = points[ indices[i] ]; + ++g_nClipPortalVerts; + } +} + + +// Sets node_t::area for non-leaf nodes (this allows an optimization in the renderer). +void SetNodeAreaIndices_R( node_t *node ) +{ + // All leaf area indices should already be set. + if( node->planenum == PLANENUM_LEAF ) + return; + + // Have the children set their area indices. + SetNodeAreaIndices_R( node->children[0] ); + SetNodeAreaIndices_R( node->children[1] ); + + // If all children (leaves or nodes) are in the same area, then set our area + // to this area as well. Otherwise, set it to -1. + if( node->children[0]->area == node->children[1]->area ) + node->area = node->children[0]->area; + else + node->area = -1; +} + + +/* +============= +EmitAreaPortals + +============= +*/ +void EmitAreaPortals (node_t *headnode) +{ + entity_t *e; + dareaportal_t *dp; + + if (c_areas > MAX_MAP_AREAS) + Error ("Map is split into too many unique areas (max = %d)\nProbably too many areaportals", MAX_MAP_AREAS); + numareas = c_areas+1; + numareaportals = 1; // leave 0 as an error + + // Reset the clip portal vert info. + g_nClipPortalVerts = 0; + + for (int iSrcArea=1 ; iSrcArea<=c_areas ; iSrcArea++) + { + dareas[iSrcArea].firstareaportal = numareaportals; + for (int j=0 ; jareaportalnum) + continue; + + if (e->portalareas[0] == iSrcArea || e->portalareas[1] == iSrcArea) + { + int iSide = (e->portalareas[0] == iSrcArea); + + // We're only interested in the portal that divides the two areas. + // One of the portals that leads into the CONTENTS_AREAPORTAL just bounds + // the same two areas but the other bounds two different ones. + portal_t *pLeadingPortal = e->m_pPortalsLeadingIntoAreas[0]; + if( pLeadingPortal->nodes[0]->area == pLeadingPortal->nodes[1]->area ) + pLeadingPortal = e->m_pPortalsLeadingIntoAreas[1]; + + if( pLeadingPortal ) + { + Assert( pLeadingPortal->nodes[0]->area != pLeadingPortal->nodes[1]->area ); + + dp = &dareaportals[numareaportals]; + numareaportals++; + + dp->m_PortalKey = e->areaportalnum; + dp->otherarea = e->portalareas[iSide]; + dp->planenum = pLeadingPortal->onnode->planenum; + + Assert( pLeadingPortal->nodes[0]->planenum == PLANENUM_LEAF ); + Assert( pLeadingPortal->nodes[1]->planenum == PLANENUM_LEAF ); + + if( pLeadingPortal->nodes[0]->area == dp->otherarea ) + { + // Use the flipped version of the plane. + dp->planenum = (dp->planenum & ~1) | (~dp->planenum & 1); + } + + EmitClipPortalGeometry( headnode, pLeadingPortal, iSrcArea, dp ); + } + } + } + + dareas[iSrcArea].numareaportals = numareaportals - dareas[iSrcArea].firstareaportal; + } + + SetNodeAreaIndices_R( headnode ); + + qprintf ("%5i numareas\n", numareas); + qprintf ("%5i numareaportals\n", numareaportals); +} + +/* +============= +FloodAreas + +Mark each leaf with an area, bounded by CONTENTS_AREAPORTAL +============= +*/ +void FloodAreas (tree_t *tree) +{ + int start = Plat_FloatTime(); + qprintf ("--- FloodAreas ---\n"); + Msg("Processing areas..."); + FindAreas_r (tree->headnode); + SetAreaPortalAreas_r (tree, tree->headnode); + qprintf ("%5i areas\n", c_areas); + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); +} + +//====================================================== + +int c_outside; +int c_inside; +int c_solid; + +void FillOutside_r (node_t *node) +{ + if (node->planenum != PLANENUM_LEAF) + { + FillOutside_r (node->children[0]); + FillOutside_r (node->children[1]); + return; + } + + // anything not reachable by an entity + // can be filled away + if (!node->occupied) + { + if (node->contents != CONTENTS_SOLID) + { + c_outside++; + node->contents = CONTENTS_SOLID; + } + else + c_solid++; + } + else + c_inside++; + +} + +/* +============= +FillOutside + +Fill all nodes that can't be reached by entities +============= +*/ +void FillOutside (node_t *headnode) +{ + c_outside = 0; + c_inside = 0; + c_solid = 0; + qprintf ("--- FillOutside ---\n"); + FillOutside_r (headnode); + qprintf ("%5i solid leafs\n", c_solid); + qprintf ("%5i leafs filled\n", c_outside); + qprintf ("%5i inside leafs\n", c_inside); +} + + +static float ComputeDistFromPlane( winding_t *pWinding, plane_t *pPlane, float maxdist ) +{ + float totaldist = 0.0f; + for (int i = 0; i < pWinding->numpoints; ++i) + { + totaldist += fabs(DotProduct( pPlane->normal, pWinding->p[i] ) - pPlane->dist); + if (totaldist > maxdist) + return totaldist; + } + return totaldist; +} + + +//----------------------------------------------------------------------------- +// Display portal error +//----------------------------------------------------------------------------- +static void DisplayPortalError( portal_t *p, int viscontents ) +{ + char contents[3][1024]; + PrintBrushContentsToString( p->nodes[0]->contents, contents[0], sizeof( contents[0] ) ); + PrintBrushContentsToString( p->nodes[1]->contents, contents[1], sizeof( contents[1] ) ); + PrintBrushContentsToString( viscontents, contents[2], sizeof( contents[2] ) ); + + Vector center; + WindingCenter( p->winding, center ); + Warning( "\nFindPortalSide: Couldn't find a good match for which brush to assign to a portal near (%.1f %.1f %.1f)\n", center.x, center.y, center.z); + Warning( "Leaf 0 contents: %s\n", contents[0] ); + Warning( "Leaf 1 contents: %s\n", contents[1] ); + Warning( "viscontents (node 0 contents ^ node 1 contents): %s\n", contents[2] ); + Warning( "This means that none of the brushes in leaf 0 or 1 that touches the portal has %s\n", contents[2] ); + Warning( "Check for a huge brush enclosing the coordinates above that has contents %s\n", contents[2] ); + Warning( "Candidate brush IDs: " ); + + CUtlVector listed; + for (int j=0 ; j<2 ; j++) + { + node_t *n = p->nodes[j]; + for (bspbrush_t *bb=n->brushlist ; bb ; bb=bb->next) + { + mapbrush_t *brush = bb->original; + if ( brush->contents & viscontents ) + { + if ( listed.Find( brush->brushnum ) == -1 ) + { + listed.AddToTail( brush->brushnum ); + Warning( "Brush %d: ", brush->id ); + } + } + } + } + Warning( "\n\n" ); +} + + +//============================================================== + +/* +============ +FindPortalSide + +Finds a brush side to use for texturing the given portal +============ +*/ +void FindPortalSide (portal_t *p) +{ + int viscontents; + bspbrush_t *bb; + mapbrush_t *brush; + node_t *n; + int i,j; + int planenum; + side_t *side, *bestside; + float bestdist; + plane_t *p1, *p2; + + // decide which content change is strongest + // solid > lava > water, etc + viscontents = VisibleContents (p->nodes[0]->contents ^ p->nodes[1]->contents); + if (!viscontents) + { + return; + } + + planenum = p->onnode->planenum; + bestside = NULL; + bestdist = 1000000; + + for (j=0 ; j<2 ; j++) + { + n = p->nodes[j]; + p1 = &g_MainMap->mapplanes[p->onnode->planenum]; + + for (bb=n->brushlist ; bb ; bb=bb->next) + { + brush = bb->original; + if ( !(brush->contents & viscontents) ) + continue; + + for (i=0 ; inumsides ; i++) + { + side = &brush->original_sides[i]; + if (side->bevel) + continue; + if (side->texinfo == TEXINFO_NODE) + continue; // non-visible + + if ((side->planenum&~1) == planenum) + { // exact match + bestside = &brush->original_sides[i]; + bestdist = 0.0f; + goto gotit; + } + + p2 = &g_MainMap->mapplanes[side->planenum&~1]; + + float dist = ComputeDistFromPlane( p->winding, p2, bestdist ); + if (dist < bestdist) + { + bestside = side; + bestdist = dist; + } + } + } + } + +gotit: + if (!bestside) + qprintf ("WARNING: side not found for portal\n"); + + // Compute average dist, check for problems... + if ((bestdist / p->winding->numpoints) > 2) + { + static int nWarnCount = 0; + if ( nWarnCount < 8 ) + { + DisplayPortalError( p, viscontents ); + if ( ++nWarnCount == 8 ) + { + Warning("*** Suppressing further FindPortalSide errors.... ***\n" ); + } + } + } + + p->sidefound = true; + p->side = bestside; +} + + +/* +=============== +MarkVisibleSides_r + +=============== +*/ +void MarkVisibleSides_r (node_t *node) +{ + portal_t *p; + int s; + + if (node->planenum != PLANENUM_LEAF) + { + MarkVisibleSides_r (node->children[0]); + MarkVisibleSides_r (node->children[1]); + return; + } + + // empty leafs are never boundary leafs + if (!node->contents) + return; + + // see if there is a visible face + for (p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (!p->onnode) + continue; // edge of world + if (!p->sidefound) + FindPortalSide (p); + if (p->side) + p->side->visible = true; + } + +} + +/* +============= +MarkVisibleSides + +============= +*/ +// UNDONE: Put detail brushes in a separate list (not mapbrushes) ? +void MarkVisibleSides (tree_t *tree, int startbrush, int endbrush, int detailScreen) +{ + int i, j; + mapbrush_t *mb; + int numsides; + qboolean detail; + + qprintf ("--- MarkVisibleSides ---\n"); + + // clear all the visible flags + for (i=startbrush ; imapbrushes[i]; + + if ( detailScreen != FULL_DETAIL ) + { + qboolean onlyDetail = (detailScreen==ONLY_DETAIL)?true:false; + // true for detail brushes + detail = (mb->contents & CONTENTS_DETAIL) ? true : false; + if ( onlyDetail ^ detail ) + { + // both of these must have the same value or we're not interested in this brush + continue; + } + } + + numsides = mb->numsides; + for (j=0 ; joriginal_sides[j].visible = false; + } + + // set visible flags on the sides that are used by portals + MarkVisibleSides_r (tree->headnode); +} + + +//----------------------------------------------------------------------------- +// Used to determine which sides are visible +//----------------------------------------------------------------------------- +void MarkVisibleSides (tree_t *tree, mapbrush_t **ppBrushes, int nCount ) +{ + qprintf ("--- MarkVisibleSides ---\n"); + + // clear all the visible flags + int i, j; + for ( i=0; i < nCount; ++i ) + { + mapbrush_t *mb = ppBrushes[i]; + int numsides = mb->numsides; + for (j=0 ; joriginal_sides[j].visible = false; + } + } + + // set visible flags on the sides that are used by portals + MarkVisibleSides_r( tree->headnode ); +} + diff --git a/mp/src/utils/vbsp/portals.h b/mp/src/utils/vbsp/portals.h index e8b45c7b..cf65ac7c 100644 --- a/mp/src/utils/vbsp/portals.h +++ b/mp/src/utils/vbsp/portals.h @@ -1,19 +1,19 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef PORTALS_H -#define PORTALS_H -#ifdef _WIN32 -#pragma once -#endif - - -// Sets up the g_ClipPortalIndices array. -void TranslateClipPortalIndices(); - - -#endif // PORTALS_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef PORTALS_H +#define PORTALS_H +#ifdef _WIN32 +#pragma once +#endif + + +// Sets up the g_ClipPortalIndices array. +void TranslateClipPortalIndices(); + + +#endif // PORTALS_H diff --git a/mp/src/utils/vbsp/prtfile.cpp b/mp/src/utils/vbsp/prtfile.cpp index c192176b..71d7e5cc 100644 --- a/mp/src/utils/vbsp/prtfile.cpp +++ b/mp/src/utils/vbsp/prtfile.cpp @@ -1,374 +1,374 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "vbsp.h" -#include "collisionutils.h" -/* -============================================================================== - -PORTAL FILE GENERATION - -Save out name.prt for qvis to read -============================================================================== -*/ - - -#define PORTALFILE "PRT1" - -struct cluster_portals_t -{ - CUtlVector portals; -}; - -int num_visclusters; // clusters the player can be in -int num_visportals; - -int g_SkyCluster = -1; - -void WriteFloat (FILE *f, vec_t v) -{ - if ( fabs(v - RoundInt(v)) < 0.001 ) - fprintf (f,"%i ",(int)RoundInt(v)); - else - fprintf (f,"%f ",v); -} - - -/* -================= -WritePortalFile_r -================= -*/ -void WritePortalFile(FILE *pFile, const CUtlVector &list) -{ - portal_t *p; - winding_t *w; - Vector normal; - vec_t dist; - - for ( int clusterIndex = 0; clusterIndex < list.Count(); clusterIndex++ ) - { - for ( int j = 0; j < list[clusterIndex].portals.Count(); j++ ) - { - p = list[clusterIndex].portals[j]; - w = p->winding; - // write out to the file - - // sometimes planes get turned around when they are very near - // the changeover point between different axis. interpret the - // plane the same way vis will, and flip the side orders if needed - // FIXME: is this still relevent? - WindingPlane (w, normal, &dist); - if ( DotProduct (p->plane.normal, normal) < 0.99 ) - { // backwards... - fprintf (pFile,"%i %i %i ",w->numpoints, p->nodes[1]->cluster, p->nodes[0]->cluster); - } - else - { - fprintf (pFile,"%i %i %i ",w->numpoints, p->nodes[0]->cluster, p->nodes[1]->cluster); - } - - for (int i=0 ; inumpoints ; i++) - { - fprintf (pFile,"("); - WriteFloat (pFile, w->p[i][0]); - WriteFloat (pFile, w->p[i][1]); - WriteFloat (pFile, w->p[i][2]); - fprintf (pFile,") "); - } - fprintf (pFile,"\n"); - } - } -} - -struct viscluster_t -{ - bspbrush_t *pBrushes; - int clusterIndex; - int leafCount; - int leafStart; -}; - -static CUtlVector g_VisClusters; - -// add to the list of brushes the merge leaves into single vis clusters -void AddVisCluster( entity_t *pFuncVisCluster ) -{ - viscluster_t tmp; - Vector clipMins, clipMaxs; - clipMins[0] = clipMins[1] = clipMins[2] = MIN_COORD_INTEGER; - clipMaxs[0] = clipMaxs[1] = clipMaxs[2] = MAX_COORD_INTEGER; - - // build the map brushes out into the minimum non-overlapping set of brushes - bspbrush_t *pBSPBrush = MakeBspBrushList( pFuncVisCluster->firstbrush, pFuncVisCluster->firstbrush + pFuncVisCluster->numbrushes, - clipMins, clipMaxs, NO_DETAIL); - tmp.pBrushes = ChopBrushes( pBSPBrush ); - - // store the entry in the list - tmp.clusterIndex = -1; - tmp.leafCount = 0; - tmp.leafStart = 0; - -#if DEBUG_VISUALIZE_CLUSTERS - int debug = atoi(ValueForKey(pFuncVisCluster,"debug")); - if ( debug ) - { - Msg("Debug vis cluster %d\n", g_VisClusters.Count() ); - } -#endif - - g_VisClusters.AddToTail( tmp ); - - // clear out this entity so it won't get written to the bsp - pFuncVisCluster->epairs = NULL; - pFuncVisCluster->numbrushes = 0; -} - -// returns the total overlapping volume of intersection between the node and the brush list -float VolumeOfIntersection( bspbrush_t *pBrushList, node_t *pNode ) -{ - float volume = 0.0f; - for ( bspbrush_t *pBrush = pBrushList; pBrush; pBrush = pBrush->next ) - { - if ( IsBoxIntersectingBox( pNode->mins, pNode->maxs, pBrush->mins, pBrush->maxs ) ) - { - bspbrush_t *pIntersect = IntersectBrush( pNode->volume, pBrush ); - if ( pIntersect ) - { - volume += BrushVolume( pIntersect ); - FreeBrush( pIntersect ); - } - } - } - - return volume; -} - -// Search for a forced cluster that this node is within -// NOTE: Returns the first one found, these won't merge themselves together -int GetVisCluster( node_t *pNode ) -{ - float maxVolume = BrushVolume(pNode->volume) * 0.10f; // needs to cover at least 10% of the volume to overlap - int maxIndex = -1; - // UNDONE: This could get slow - for ( int i = g_VisClusters.Count(); --i >= 0; ) - { - float volume = VolumeOfIntersection( g_VisClusters[i].pBrushes, pNode ); - if ( volume > maxVolume ) - { - volume = maxVolume; - maxIndex = i; - } - } - return maxIndex; -} -/* -================ -NumberLeafs_r -================ -*/ -void BuildVisLeafList_r (node_t *node, CUtlVector &leaves) -{ - if (node->planenum != PLANENUM_LEAF) - { // decision node - node->cluster = -99; - BuildVisLeafList_r (node->children[0], leaves); - BuildVisLeafList_r (node->children[1], leaves); - return; - } - - if ( node->contents & CONTENTS_SOLID ) - { // solid block, viewpoint never inside - node->cluster = -1; - return; - } - leaves.AddToTail(node); -} - -// Give each leaf in the list of empty leaves a vis cluster number -// some are within func_viscluster volumes and will be merged together -// every other leaf gets its own unique number -void NumberLeafs( const CUtlVector &leaves ) -{ - for ( int i = 0; i < leaves.Count(); i++ ) - { - node_t *node = leaves[i]; - int visCluster = GetVisCluster( node ); - if ( visCluster >= 0 ) - { - if ( g_VisClusters[visCluster].clusterIndex < 0 ) - { - g_VisClusters[visCluster].clusterIndex = num_visclusters; - num_visclusters++; - } - g_VisClusters[visCluster].leafCount++; - node->cluster = g_VisClusters[visCluster].clusterIndex; - } - else - { - if ( !g_bSkyVis && Is3DSkyboxArea( node->area ) ) - { - if ( g_SkyCluster < 0 ) - { - // allocate a cluster for the sky - g_SkyCluster = num_visclusters; - num_visclusters++; - } - node->cluster = g_SkyCluster; - } - else - { - node->cluster = num_visclusters; - num_visclusters++; - } - } - } - -#if DEBUG_VISUALIZE_CLUSTERS - for ( int i = 0; i < g_VisClusters.Count(); i++ ) - { - char name[256]; - sprintf(name, "u:\\main\\game\\ep2\\maps\\vis_%02d.gl", i ); - FileHandle_t fp = g_pFileSystem->Open( name, "w" ); - Msg("Writing %s\n", name ); - for ( bspbrush_t *pBrush = g_VisClusters[i].pBrushes; pBrush; pBrush = pBrush->next ) - { - for (int i = 0; i < pBrush->numsides; i++ ) - OutputWindingColor( pBrush->sides[i].winding, fp, 0, 255, 0 ); - } - for ( int j = 0; j < leaves.Count(); j++ ) - { - if ( leaves[j]->cluster == g_VisClusters[i].clusterIndex ) - { - bspbrush_t *pBrush = leaves[j]->volume; - for (int k = 0; k < pBrush->numsides; k++ ) - OutputWindingColor( pBrush->sides[k].winding, fp, 64 + (j&31), 64, 64 - (j&31) ); - } - } - g_pFileSystem->Close(fp); - } -#endif -} - -// build a list of all vis portals that connect clusters -int BuildPortalList( CUtlVector &portalList, const CUtlVector &leaves ) -{ - int portalCount = 0; - for ( int i = 0; i < leaves.Count(); i++ ) - { - node_t *node = leaves[i]; - // count the portals - for (portal_t *p = node->portals ; p ; ) - { - if (p->nodes[0] == node) // only write out from first leaf - { - if ( p->nodes[0]->cluster != p->nodes[1]->cluster ) - { - if (Portal_VisFlood (p)) - { - portalCount++; - portalList[node->cluster].portals.AddToTail(p); - } - } - p = p->next[0]; - } - else - p = p->next[1]; - } - } - return portalCount; -} - - -/* -================ -CreateVisPortals_r -================ -*/ -void CreateVisPortals_r (node_t *node) -{ - // stop as soon as we get to a leaf - if (node->planenum == PLANENUM_LEAF ) - return; - - MakeNodePortal (node); - SplitNodePortals (node); - - CreateVisPortals_r (node->children[0]); - CreateVisPortals_r (node->children[1]); -} - -int clusterleaf; -void SaveClusters_r (node_t *node) -{ - if (node->planenum == PLANENUM_LEAF) - { - dleafs[clusterleaf++].cluster = node->cluster; - return; - } - SaveClusters_r (node->children[0]); - SaveClusters_r (node->children[1]); -} - -/* -================ -WritePortalFile -================ -*/ -void WritePortalFile (tree_t *tree) -{ - char filename[1024]; - node_t *headnode; - int start = Plat_FloatTime(); - - qprintf ("--- WritePortalFile ---\n"); - - sprintf (filename, "%s.prt", source); - Msg ("writing %s...", filename); - - headnode = tree->headnode; - - FreeTreePortals_r (headnode); - MakeHeadnodePortals (tree); - - CreateVisPortals_r (headnode); - -// set the cluster field in every leaf and count the total number of portals - num_visclusters = 0; - Msg("Building visibility clusters...\n"); - CUtlVector leaves; - BuildVisLeafList_r( headnode, leaves ); - - NumberLeafs (leaves); - CUtlVector portalList; - portalList.SetCount( num_visclusters ); - num_visportals = BuildPortalList( portalList, leaves ); -// write the file - FILE *pf = fopen (filename, "w"); - if (!pf) - Error ("Error opening %s", filename); - - fprintf (pf, "%s\n", PORTALFILE); - fprintf (pf, "%i\n", num_visclusters); - fprintf (pf, "%i\n", num_visportals); - - qprintf ("%5i visclusters\n", num_visclusters); - qprintf ("%5i visportals\n", num_visportals); - - WritePortalFile(pf, portalList); - - fclose (pf); - - // we need to store the clusters out now because ordering - // issues made us do this after writebsp... - clusterleaf = 1; - SaveClusters_r (headnode); - - Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" +#include "collisionutils.h" +/* +============================================================================== + +PORTAL FILE GENERATION + +Save out name.prt for qvis to read +============================================================================== +*/ + + +#define PORTALFILE "PRT1" + +struct cluster_portals_t +{ + CUtlVector portals; +}; + +int num_visclusters; // clusters the player can be in +int num_visportals; + +int g_SkyCluster = -1; + +void WriteFloat (FILE *f, vec_t v) +{ + if ( fabs(v - RoundInt(v)) < 0.001 ) + fprintf (f,"%i ",(int)RoundInt(v)); + else + fprintf (f,"%f ",v); +} + + +/* +================= +WritePortalFile_r +================= +*/ +void WritePortalFile(FILE *pFile, const CUtlVector &list) +{ + portal_t *p; + winding_t *w; + Vector normal; + vec_t dist; + + for ( int clusterIndex = 0; clusterIndex < list.Count(); clusterIndex++ ) + { + for ( int j = 0; j < list[clusterIndex].portals.Count(); j++ ) + { + p = list[clusterIndex].portals[j]; + w = p->winding; + // write out to the file + + // sometimes planes get turned around when they are very near + // the changeover point between different axis. interpret the + // plane the same way vis will, and flip the side orders if needed + // FIXME: is this still relevent? + WindingPlane (w, normal, &dist); + if ( DotProduct (p->plane.normal, normal) < 0.99 ) + { // backwards... + fprintf (pFile,"%i %i %i ",w->numpoints, p->nodes[1]->cluster, p->nodes[0]->cluster); + } + else + { + fprintf (pFile,"%i %i %i ",w->numpoints, p->nodes[0]->cluster, p->nodes[1]->cluster); + } + + for (int i=0 ; inumpoints ; i++) + { + fprintf (pFile,"("); + WriteFloat (pFile, w->p[i][0]); + WriteFloat (pFile, w->p[i][1]); + WriteFloat (pFile, w->p[i][2]); + fprintf (pFile,") "); + } + fprintf (pFile,"\n"); + } + } +} + +struct viscluster_t +{ + bspbrush_t *pBrushes; + int clusterIndex; + int leafCount; + int leafStart; +}; + +static CUtlVector g_VisClusters; + +// add to the list of brushes the merge leaves into single vis clusters +void AddVisCluster( entity_t *pFuncVisCluster ) +{ + viscluster_t tmp; + Vector clipMins, clipMaxs; + clipMins[0] = clipMins[1] = clipMins[2] = MIN_COORD_INTEGER; + clipMaxs[0] = clipMaxs[1] = clipMaxs[2] = MAX_COORD_INTEGER; + + // build the map brushes out into the minimum non-overlapping set of brushes + bspbrush_t *pBSPBrush = MakeBspBrushList( pFuncVisCluster->firstbrush, pFuncVisCluster->firstbrush + pFuncVisCluster->numbrushes, + clipMins, clipMaxs, NO_DETAIL); + tmp.pBrushes = ChopBrushes( pBSPBrush ); + + // store the entry in the list + tmp.clusterIndex = -1; + tmp.leafCount = 0; + tmp.leafStart = 0; + +#if DEBUG_VISUALIZE_CLUSTERS + int debug = atoi(ValueForKey(pFuncVisCluster,"debug")); + if ( debug ) + { + Msg("Debug vis cluster %d\n", g_VisClusters.Count() ); + } +#endif + + g_VisClusters.AddToTail( tmp ); + + // clear out this entity so it won't get written to the bsp + pFuncVisCluster->epairs = NULL; + pFuncVisCluster->numbrushes = 0; +} + +// returns the total overlapping volume of intersection between the node and the brush list +float VolumeOfIntersection( bspbrush_t *pBrushList, node_t *pNode ) +{ + float volume = 0.0f; + for ( bspbrush_t *pBrush = pBrushList; pBrush; pBrush = pBrush->next ) + { + if ( IsBoxIntersectingBox( pNode->mins, pNode->maxs, pBrush->mins, pBrush->maxs ) ) + { + bspbrush_t *pIntersect = IntersectBrush( pNode->volume, pBrush ); + if ( pIntersect ) + { + volume += BrushVolume( pIntersect ); + FreeBrush( pIntersect ); + } + } + } + + return volume; +} + +// Search for a forced cluster that this node is within +// NOTE: Returns the first one found, these won't merge themselves together +int GetVisCluster( node_t *pNode ) +{ + float maxVolume = BrushVolume(pNode->volume) * 0.10f; // needs to cover at least 10% of the volume to overlap + int maxIndex = -1; + // UNDONE: This could get slow + for ( int i = g_VisClusters.Count(); --i >= 0; ) + { + float volume = VolumeOfIntersection( g_VisClusters[i].pBrushes, pNode ); + if ( volume > maxVolume ) + { + volume = maxVolume; + maxIndex = i; + } + } + return maxIndex; +} +/* +================ +NumberLeafs_r +================ +*/ +void BuildVisLeafList_r (node_t *node, CUtlVector &leaves) +{ + if (node->planenum != PLANENUM_LEAF) + { // decision node + node->cluster = -99; + BuildVisLeafList_r (node->children[0], leaves); + BuildVisLeafList_r (node->children[1], leaves); + return; + } + + if ( node->contents & CONTENTS_SOLID ) + { // solid block, viewpoint never inside + node->cluster = -1; + return; + } + leaves.AddToTail(node); +} + +// Give each leaf in the list of empty leaves a vis cluster number +// some are within func_viscluster volumes and will be merged together +// every other leaf gets its own unique number +void NumberLeafs( const CUtlVector &leaves ) +{ + for ( int i = 0; i < leaves.Count(); i++ ) + { + node_t *node = leaves[i]; + int visCluster = GetVisCluster( node ); + if ( visCluster >= 0 ) + { + if ( g_VisClusters[visCluster].clusterIndex < 0 ) + { + g_VisClusters[visCluster].clusterIndex = num_visclusters; + num_visclusters++; + } + g_VisClusters[visCluster].leafCount++; + node->cluster = g_VisClusters[visCluster].clusterIndex; + } + else + { + if ( !g_bSkyVis && Is3DSkyboxArea( node->area ) ) + { + if ( g_SkyCluster < 0 ) + { + // allocate a cluster for the sky + g_SkyCluster = num_visclusters; + num_visclusters++; + } + node->cluster = g_SkyCluster; + } + else + { + node->cluster = num_visclusters; + num_visclusters++; + } + } + } + +#if DEBUG_VISUALIZE_CLUSTERS + for ( int i = 0; i < g_VisClusters.Count(); i++ ) + { + char name[256]; + sprintf(name, "u:\\main\\game\\ep2\\maps\\vis_%02d.gl", i ); + FileHandle_t fp = g_pFileSystem->Open( name, "w" ); + Msg("Writing %s\n", name ); + for ( bspbrush_t *pBrush = g_VisClusters[i].pBrushes; pBrush; pBrush = pBrush->next ) + { + for (int i = 0; i < pBrush->numsides; i++ ) + OutputWindingColor( pBrush->sides[i].winding, fp, 0, 255, 0 ); + } + for ( int j = 0; j < leaves.Count(); j++ ) + { + if ( leaves[j]->cluster == g_VisClusters[i].clusterIndex ) + { + bspbrush_t *pBrush = leaves[j]->volume; + for (int k = 0; k < pBrush->numsides; k++ ) + OutputWindingColor( pBrush->sides[k].winding, fp, 64 + (j&31), 64, 64 - (j&31) ); + } + } + g_pFileSystem->Close(fp); + } +#endif +} + +// build a list of all vis portals that connect clusters +int BuildPortalList( CUtlVector &portalList, const CUtlVector &leaves ) +{ + int portalCount = 0; + for ( int i = 0; i < leaves.Count(); i++ ) + { + node_t *node = leaves[i]; + // count the portals + for (portal_t *p = node->portals ; p ; ) + { + if (p->nodes[0] == node) // only write out from first leaf + { + if ( p->nodes[0]->cluster != p->nodes[1]->cluster ) + { + if (Portal_VisFlood (p)) + { + portalCount++; + portalList[node->cluster].portals.AddToTail(p); + } + } + p = p->next[0]; + } + else + p = p->next[1]; + } + } + return portalCount; +} + + +/* +================ +CreateVisPortals_r +================ +*/ +void CreateVisPortals_r (node_t *node) +{ + // stop as soon as we get to a leaf + if (node->planenum == PLANENUM_LEAF ) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + CreateVisPortals_r (node->children[0]); + CreateVisPortals_r (node->children[1]); +} + +int clusterleaf; +void SaveClusters_r (node_t *node) +{ + if (node->planenum == PLANENUM_LEAF) + { + dleafs[clusterleaf++].cluster = node->cluster; + return; + } + SaveClusters_r (node->children[0]); + SaveClusters_r (node->children[1]); +} + +/* +================ +WritePortalFile +================ +*/ +void WritePortalFile (tree_t *tree) +{ + char filename[1024]; + node_t *headnode; + int start = Plat_FloatTime(); + + qprintf ("--- WritePortalFile ---\n"); + + sprintf (filename, "%s.prt", source); + Msg ("writing %s...", filename); + + headnode = tree->headnode; + + FreeTreePortals_r (headnode); + MakeHeadnodePortals (tree); + + CreateVisPortals_r (headnode); + +// set the cluster field in every leaf and count the total number of portals + num_visclusters = 0; + Msg("Building visibility clusters...\n"); + CUtlVector leaves; + BuildVisLeafList_r( headnode, leaves ); + + NumberLeafs (leaves); + CUtlVector portalList; + portalList.SetCount( num_visclusters ); + num_visportals = BuildPortalList( portalList, leaves ); +// write the file + FILE *pf = fopen (filename, "w"); + if (!pf) + Error ("Error opening %s", filename); + + fprintf (pf, "%s\n", PORTALFILE); + fprintf (pf, "%i\n", num_visclusters); + fprintf (pf, "%i\n", num_visportals); + + qprintf ("%5i visclusters\n", num_visclusters); + qprintf ("%5i visportals\n", num_visportals); + + WritePortalFile(pf, portalList); + + fclose (pf); + + // we need to store the clusters out now because ordering + // issues made us do this after writebsp... + clusterleaf = 1; + SaveClusters_r (headnode); + + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); +} + diff --git a/mp/src/utils/vbsp/staticprop.cpp b/mp/src/utils/vbsp/staticprop.cpp index 810465b3..b7b9b6cb 100644 --- a/mp/src/utils/vbsp/staticprop.cpp +++ b/mp/src/utils/vbsp/staticprop.cpp @@ -1,741 +1,741 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: Places "detail" objects which are client-only renderable things -// -// $Revision: $ -// $NoKeywords: $ -//=============================================================================// - -#include "vbsp.h" -#include "bsplib.h" -#include "utlvector.h" -#include "bspfile.h" -#include "gamebspfile.h" -#include "VPhysics_Interface.h" -#include "Studio.h" -#include "byteswap.h" -#include "UtlBuffer.h" -#include "CollisionUtils.h" -#include -#include "CModel.h" -#include "PhysDll.h" -#include "utlsymbol.h" -#include "tier1/strtools.h" -#include "KeyValues.h" - -static void SetCurrentModel( studiohdr_t *pStudioHdr ); -static void FreeCurrentModelVertexes(); - -IPhysicsCollision *s_pPhysCollision = NULL; - -//----------------------------------------------------------------------------- -// These puppies are used to construct the game lumps -//----------------------------------------------------------------------------- -static CUtlVector s_StaticPropDictLump; -static CUtlVector s_StaticPropLump; -static CUtlVector s_StaticPropLeafLump; - - -//----------------------------------------------------------------------------- -// Used to build the static prop -//----------------------------------------------------------------------------- -struct StaticPropBuild_t -{ - char const* m_pModelName; - char const* m_pLightingOrigin; - Vector m_Origin; - QAngle m_Angles; - int m_Solid; - int m_Skin; - int m_Flags; - float m_FadeMinDist; - float m_FadeMaxDist; - bool m_FadesOut; - float m_flForcedFadeScale; - unsigned short m_nMinDXLevel; - unsigned short m_nMaxDXLevel; -}; - - -//----------------------------------------------------------------------------- -// Used to cache collision model generation -//----------------------------------------------------------------------------- -struct ModelCollisionLookup_t -{ - CUtlSymbol m_Name; - CPhysCollide* m_pCollide; -}; - -static bool ModelLess( ModelCollisionLookup_t const& src1, ModelCollisionLookup_t const& src2 ) -{ - return src1.m_Name < src2.m_Name; -} - -static CUtlRBTree s_ModelCollisionCache( 0, 32, ModelLess ); -static CUtlVector s_LightingInfo; - - -//----------------------------------------------------------------------------- -// Gets the keyvalues from a studiohdr -//----------------------------------------------------------------------------- -bool StudioKeyValues( studiohdr_t* pStudioHdr, KeyValues *pValue ) -{ - if ( !pStudioHdr ) - return false; - - return pValue->LoadFromBuffer( pStudioHdr->pszName(), pStudioHdr->KeyValueText() ); -} - - -//----------------------------------------------------------------------------- -// Makes sure the studio model is a static prop -//----------------------------------------------------------------------------- -enum isstaticprop_ret -{ - RET_VALID, - RET_FAIL_NOT_MARKED_STATIC_PROP, - RET_FAIL_DYNAMIC, -}; - -isstaticprop_ret IsStaticProp( studiohdr_t* pHdr ) -{ - if (!(pHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP)) - return RET_FAIL_NOT_MARKED_STATIC_PROP; - - // If it's got a propdata section in the model's keyvalues, it's not allowed to be a prop_static - KeyValues *modelKeyValues = new KeyValues(pHdr->pszName()); - if ( StudioKeyValues( pHdr, modelKeyValues ) ) - { - KeyValues *sub = modelKeyValues->FindKey("prop_data"); - if ( sub ) - { - if ( !(sub->GetInt( "allowstatic", 0 )) ) - { - modelKeyValues->deleteThis(); - return RET_FAIL_DYNAMIC; - } - } - } - modelKeyValues->deleteThis(); - - return RET_VALID; -} - - -//----------------------------------------------------------------------------- -// Add static prop model to the list of models -//----------------------------------------------------------------------------- - -static int AddStaticPropDictLump( char const* pModelName ) -{ - StaticPropDictLump_t dictLump; - strncpy( dictLump.m_Name, pModelName, DETAIL_NAME_LENGTH ); - - for (int i = s_StaticPropDictLump.Size(); --i >= 0; ) - { - if (!memcmp(&s_StaticPropDictLump[i], &dictLump, sizeof(dictLump) )) - return i; - } - - return s_StaticPropDictLump.AddToTail( dictLump ); -} - - -//----------------------------------------------------------------------------- -// Load studio model vertex data from a file... -//----------------------------------------------------------------------------- -bool LoadStudioModel( char const* pModelName, char const* pEntityType, CUtlBuffer& buf ) -{ - if ( !g_pFullFileSystem->ReadFile( pModelName, NULL, buf ) ) - return false; - - // Check that it's valid - if (strncmp ((const char *) buf.PeekGet(), "IDST", 4) && - strncmp ((const char *) buf.PeekGet(), "IDAG", 4)) - { - return false; - } - - studiohdr_t* pHdr = (studiohdr_t*)buf.PeekGet(); - - Studio_ConvertStudioHdrToNewVersion( pHdr ); - - if (pHdr->version != STUDIO_VERSION) - { - return false; - } - - isstaticprop_ret isStaticProp = IsStaticProp(pHdr); - if ( isStaticProp != RET_VALID ) - { - if ( isStaticProp == RET_FAIL_NOT_MARKED_STATIC_PROP ) - { - Warning("Error! To use model \"%s\"\n" - " with %s, it must be compiled with $staticprop!\n", pModelName, pEntityType ); - } - else if ( isStaticProp == RET_FAIL_DYNAMIC ) - { - Warning("Error! %s using model \"%s\", which must be used on a dynamic entity (i.e. prop_physics). Deleted.\n", pEntityType, pModelName ); - } - return false; - } - - // ensure reset - pHdr->pVertexBase = NULL; - pHdr->pIndexBase = NULL; - - return true; -} - - -//----------------------------------------------------------------------------- -// Computes a convex hull from a studio mesh -//----------------------------------------------------------------------------- -static CPhysConvex* ComputeConvexHull( mstudiomesh_t* pMesh ) -{ - // Generate a list of all verts in the mesh - Vector** ppVerts = (Vector**)stackalloc(pMesh->numvertices * sizeof(Vector*) ); - const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData(); - Assert( vertData ); // This can only return NULL on X360 for now - for (int i = 0; i < pMesh->numvertices; ++i) - { - ppVerts[i] = vertData->Position(i); - } - - // Generate a convex hull from the verts - return s_pPhysCollision->ConvexFromVerts( ppVerts, pMesh->numvertices ); -} - - -//----------------------------------------------------------------------------- -// Computes a convex hull from the studio model -//----------------------------------------------------------------------------- -CPhysCollide* ComputeConvexHull( studiohdr_t* pStudioHdr ) -{ - CUtlVector convexHulls; - - for (int body = 0; body < pStudioHdr->numbodyparts; ++body ) - { - mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( body ); - for( int model = 0; model < pBodyPart->nummodels; ++model ) - { - mstudiomodel_t *pStudioModel = pBodyPart->pModel( model ); - for( int mesh = 0; mesh < pStudioModel->nummeshes; ++mesh ) - { - // Make a convex hull for each mesh - // NOTE: This won't work unless the model has been compiled - // with $staticprop - mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( mesh ); - convexHulls.AddToTail( ComputeConvexHull( pStudioMesh ) ); - } - } - } - - // Convert an array of convex elements to a compiled collision model - // (this deletes the convex elements) - return s_pPhysCollision->ConvertConvexToCollide( convexHulls.Base(), convexHulls.Size() ); -} - - -//----------------------------------------------------------------------------- -// Add, find collision model in cache -//----------------------------------------------------------------------------- -static CPhysCollide* GetCollisionModel( char const* pModelName ) -{ - // Convert to a common string - char* pTemp = (char*)_alloca(strlen(pModelName) + 1); - strcpy( pTemp, pModelName ); - _strlwr( pTemp ); - - char* pSlash = strchr( pTemp, '\\' ); - while( pSlash ) - { - *pSlash = '/'; - pSlash = strchr( pTemp, '\\' ); - } - - // Find it in the cache - ModelCollisionLookup_t lookup; - lookup.m_Name = pTemp; - int i = s_ModelCollisionCache.Find( lookup ); - if (i != s_ModelCollisionCache.InvalidIndex()) - return s_ModelCollisionCache[i].m_pCollide; - - // Load the studio model file - CUtlBuffer buf; - if (!LoadStudioModel(pModelName, "prop_static", buf)) - { - Warning("Error loading studio model \"%s\"!\n", pModelName ); - - // This way we don't try to load it multiple times - lookup.m_pCollide = 0; - s_ModelCollisionCache.Insert( lookup ); - - return 0; - } - - // Compute the convex hull of the model... - studiohdr_t* pStudioHdr = (studiohdr_t*)buf.PeekGet(); - - // necessary for vertex access - SetCurrentModel( pStudioHdr ); - - lookup.m_pCollide = ComputeConvexHull( pStudioHdr ); - s_ModelCollisionCache.Insert( lookup ); - - if ( !lookup.m_pCollide ) - { - Warning("Bad geometry on \"%s\"!\n", pModelName ); - } - - // Debugging - if (g_DumpStaticProps) - { - static int propNum = 0; - char tmp[128]; - sprintf( tmp, "staticprop%03d.txt", propNum ); - DumpCollideToGlView( lookup.m_pCollide, tmp ); - ++propNum; - } - - FreeCurrentModelVertexes(); - - // Insert into cache... - return lookup.m_pCollide; -} - - -//----------------------------------------------------------------------------- -// Tests a single leaf against the static prop -//----------------------------------------------------------------------------- - -static bool TestLeafAgainstCollide( int depth, int* pNodeList, - Vector const& origin, QAngle const& angles, CPhysCollide* pCollide ) -{ - // Copy the planes in the node list into a list of planes - float* pPlanes = (float*)_alloca(depth * 4 * sizeof(float) ); - int idx = 0; - for (int i = depth; --i >= 0; ++idx ) - { - int sign = (pNodeList[i] < 0) ? -1 : 1; - int node = (sign < 0) ? - pNodeList[i] - 1 : pNodeList[i]; - dnode_t* pNode = &dnodes[node]; - dplane_t* pPlane = &dplanes[pNode->planenum]; - - pPlanes[idx*4] = sign * pPlane->normal[0]; - pPlanes[idx*4+1] = sign * pPlane->normal[1]; - pPlanes[idx*4+2] = sign * pPlane->normal[2]; - pPlanes[idx*4+3] = sign * pPlane->dist; - } - - // Make a convex solid out of the planes - CPhysConvex* pPhysConvex = s_pPhysCollision->ConvexFromPlanes( pPlanes, depth, 0.0f ); - - // This should never happen, but if it does, return no collision - Assert( pPhysConvex ); - if (!pPhysConvex) - return false; - - CPhysCollide* pLeafCollide = s_pPhysCollision->ConvertConvexToCollide( &pPhysConvex, 1 ); - - // Collide the leaf solid with the static prop solid - trace_t tr; - s_pPhysCollision->TraceCollide( vec3_origin, vec3_origin, pLeafCollide, vec3_angle, - pCollide, origin, angles, &tr ); - - s_pPhysCollision->DestroyCollide( pLeafCollide ); - - return (tr.startsolid != 0); -} - -//----------------------------------------------------------------------------- -// Find all leaves that intersect with this bbox + test against the static prop.. -//----------------------------------------------------------------------------- - -static void ComputeConvexHullLeaves_R( int node, int depth, int* pNodeList, - Vector const& mins, Vector const& maxs, - Vector const& origin, QAngle const& angles, CPhysCollide* pCollide, - CUtlVector& leafList ) -{ - Assert( pNodeList && pCollide ); - Vector cornermin, cornermax; - - while( node >= 0 ) - { - dnode_t* pNode = &dnodes[node]; - dplane_t* pPlane = &dplanes[pNode->planenum]; - - // Arbitrary split plane here - for (int i = 0; i < 3; ++i) - { - if (pPlane->normal[i] >= 0) - { - cornermin[i] = mins[i]; - cornermax[i] = maxs[i]; - } - else - { - cornermin[i] = maxs[i]; - cornermax[i] = mins[i]; - } - } - - if (DotProduct( pPlane->normal, cornermax ) <= pPlane->dist) - { - // Add the node to the list of nodes - pNodeList[depth] = node; - ++depth; - - node = pNode->children[1]; - } - else if (DotProduct( pPlane->normal, cornermin ) >= pPlane->dist) - { - // In this case, we are going in front of the plane. That means that - // this plane must have an outward normal facing in the oppisite direction - // We indicate this be storing a negative node index in the node list - pNodeList[depth] = - node - 1; - ++depth; - - node = pNode->children[0]; - } - else - { - // Here the box is split by the node. First, we'll add the plane as if its - // outward facing normal is in the direction of the node plane, then - // we'll have to reverse it for the other child... - pNodeList[depth] = node; - ++depth; - - ComputeConvexHullLeaves_R( pNode->children[1], - depth, pNodeList, mins, maxs, origin, angles, pCollide, leafList ); - - pNodeList[depth - 1] = - node - 1; - ComputeConvexHullLeaves_R( pNode->children[0], - depth, pNodeList, mins, maxs, origin, angles, pCollide, leafList ); - return; - } - } - - Assert( pNodeList && pCollide ); - - // Never add static props to solid leaves - if ( (dleafs[-node-1].contents & CONTENTS_SOLID) == 0 ) - { - if (TestLeafAgainstCollide( depth, pNodeList, origin, angles, pCollide )) - { - leafList.AddToTail( -node - 1 ); - } - } -} - -//----------------------------------------------------------------------------- -// Places Static Props in the level -//----------------------------------------------------------------------------- - -static void ComputeStaticPropLeaves( CPhysCollide* pCollide, Vector const& origin, - QAngle const& angles, CUtlVector& leafList ) -{ - // Compute an axis-aligned bounding box for the collide - Vector mins, maxs; - s_pPhysCollision->CollideGetAABB( &mins, &maxs, pCollide, origin, angles ); - - // Find all leaves that intersect with the bounds - int tempNodeList[1024]; - ComputeConvexHullLeaves_R( 0, 0, tempNodeList, mins, maxs, - origin, angles, pCollide, leafList ); -} - - -//----------------------------------------------------------------------------- -// Computes the lighting origin -//----------------------------------------------------------------------------- -static bool ComputeLightingOrigin( StaticPropBuild_t const& build, Vector& lightingOrigin ) -{ - for (int i = s_LightingInfo.Count(); --i >= 0; ) - { - int entIndex = s_LightingInfo[i]; - - // Check against all lighting info entities - char const* pTargetName = ValueForKey( &entities[entIndex], "targetname" ); - if (!Q_strcmp(pTargetName, build.m_pLightingOrigin)) - { - GetVectorForKey( &entities[entIndex], "origin", lightingOrigin ); - return true; - } - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Places Static Props in the level -//----------------------------------------------------------------------------- -static void AddStaticPropToLump( StaticPropBuild_t const& build ) -{ - // Get the collision model - CPhysCollide* pConvexHull = GetCollisionModel( build.m_pModelName ); - if (!pConvexHull) - return; - - // Compute the leaves the static prop's convex hull hits - CUtlVector< unsigned short > leafList; - ComputeStaticPropLeaves( pConvexHull, build.m_Origin, build.m_Angles, leafList ); - - if ( !leafList.Count() ) - { - Warning( "Static prop %s outside the map (%.2f, %.2f, %.2f)\n", build.m_pModelName, build.m_Origin.x, build.m_Origin.y, build.m_Origin.z ); - return; - } - // Insert an element into the lump data... - int i = s_StaticPropLump.AddToTail( ); - StaticPropLump_t& propLump = s_StaticPropLump[i]; - propLump.m_PropType = AddStaticPropDictLump( build.m_pModelName ); - VectorCopy( build.m_Origin, propLump.m_Origin ); - VectorCopy( build.m_Angles, propLump.m_Angles ); - propLump.m_FirstLeaf = s_StaticPropLeafLump.Count(); - propLump.m_LeafCount = leafList.Count(); - propLump.m_Solid = build.m_Solid; - propLump.m_Skin = build.m_Skin; - propLump.m_Flags = build.m_Flags; - if (build.m_FadesOut) - { - propLump.m_Flags |= STATIC_PROP_FLAG_FADES; - } - propLump.m_FadeMinDist = build.m_FadeMinDist; - propLump.m_FadeMaxDist = build.m_FadeMaxDist; - propLump.m_flForcedFadeScale = build.m_flForcedFadeScale; - propLump.m_nMinDXLevel = build.m_nMinDXLevel; - propLump.m_nMaxDXLevel = build.m_nMaxDXLevel; - - if (build.m_pLightingOrigin && *build.m_pLightingOrigin) - { - if (ComputeLightingOrigin( build, propLump.m_LightingOrigin )) - { - propLump.m_Flags |= STATIC_PROP_USE_LIGHTING_ORIGIN; - } - } - - // Add the leaves to the leaf lump - for (int j = 0; j < leafList.Size(); ++j) - { - StaticPropLeafLump_t insert; - insert.m_Leaf = leafList[j]; - s_StaticPropLeafLump.AddToTail( insert ); - } -} - - -//----------------------------------------------------------------------------- -// Places static props in the lump -//----------------------------------------------------------------------------- - -static void SetLumpData( ) -{ - GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(GAMELUMP_STATIC_PROPS); - if (handle != g_GameLumps.InvalidGameLump()) - g_GameLumps.DestroyGameLump(handle); - - int dictsize = s_StaticPropDictLump.Size() * sizeof(StaticPropDictLump_t); - int objsize = s_StaticPropLump.Size() * sizeof(StaticPropLump_t); - int leafsize = s_StaticPropLeafLump.Size() * sizeof(StaticPropLeafLump_t); - int size = dictsize + objsize + leafsize + 3 * sizeof(int); - - handle = g_GameLumps.CreateGameLump( GAMELUMP_STATIC_PROPS, size, 0, GAMELUMP_STATIC_PROPS_VERSION ); - - // Serialize the data - CUtlBuffer buf( g_GameLumps.GetGameLump(handle), size ); - buf.PutInt( s_StaticPropDictLump.Size() ); - if (dictsize) - buf.Put( s_StaticPropDictLump.Base(), dictsize ); - buf.PutInt( s_StaticPropLeafLump.Size() ); - if (leafsize) - buf.Put( s_StaticPropLeafLump.Base(), leafsize ); - buf.PutInt( s_StaticPropLump.Size() ); - if (objsize) - buf.Put( s_StaticPropLump.Base(), objsize ); -} - - -//----------------------------------------------------------------------------- -// Places Static Props in the level -//----------------------------------------------------------------------------- - -void EmitStaticProps() -{ - CreateInterfaceFn physicsFactory = GetPhysicsFactory(); - if ( physicsFactory ) - { - s_pPhysCollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); - if( !s_pPhysCollision ) - return; - } - - // Generate a list of lighting origins, and strip them out - int i; - for ( i = 0; i < num_entities; ++i) - { - char* pEntity = ValueForKey(&entities[i], "classname"); - if (!Q_strcmp(pEntity, "info_lighting")) - { - s_LightingInfo.AddToTail(i); - } - } - - // Emit specifically specified static props - for ( i = 0; i < num_entities; ++i) - { - char* pEntity = ValueForKey(&entities[i], "classname"); - if (!strcmp(pEntity, "static_prop") || !strcmp(pEntity, "prop_static")) - { - StaticPropBuild_t build; - - GetVectorForKey( &entities[i], "origin", build.m_Origin ); - GetAnglesForKey( &entities[i], "angles", build.m_Angles ); - build.m_pModelName = ValueForKey( &entities[i], "model" ); - build.m_Solid = IntForKey( &entities[i], "solid" ); - build.m_Skin = IntForKey( &entities[i], "skin" ); - build.m_FadeMaxDist = FloatForKey( &entities[i], "fademaxdist" ); - build.m_Flags = 0;//IntForKey( &entities[i], "spawnflags" ) & STATIC_PROP_WC_MASK; - if (IntForKey( &entities[i], "ignorenormals" ) == 1) - { - build.m_Flags |= STATIC_PROP_IGNORE_NORMALS; - } - if (IntForKey( &entities[i], "disableshadows" ) == 1) - { - build.m_Flags |= STATIC_PROP_NO_SHADOW; - } - if (IntForKey( &entities[i], "disablevertexlighting" ) == 1) - { - build.m_Flags |= STATIC_PROP_NO_PER_VERTEX_LIGHTING; - } - if (IntForKey( &entities[i], "disableselfshadowing" ) == 1) - { - build.m_Flags |= STATIC_PROP_NO_SELF_SHADOWING; - } - - if (IntForKey( &entities[i], "screenspacefade" ) == 1) - { - build.m_Flags |= STATIC_PROP_SCREEN_SPACE_FADE; - } - - const char *pKey = ValueForKey( &entities[i], "fadescale" ); - if ( pKey && pKey[0] ) - { - build.m_flForcedFadeScale = FloatForKey( &entities[i], "fadescale" ); - } - else - { - build.m_flForcedFadeScale = 1; - } - build.m_FadesOut = (build.m_FadeMaxDist > 0); - build.m_pLightingOrigin = ValueForKey( &entities[i], "lightingorigin" ); - if (build.m_FadesOut) - { - build.m_FadeMinDist = FloatForKey( &entities[i], "fademindist" ); - if (build.m_FadeMinDist < 0) - { - build.m_FadeMinDist = build.m_FadeMaxDist; - } - } - else - { - build.m_FadeMinDist = 0; - } - build.m_nMinDXLevel = (unsigned short)IntForKey( &entities[i], "mindxlevel" ); - build.m_nMaxDXLevel = (unsigned short)IntForKey( &entities[i], "maxdxlevel" ); - AddStaticPropToLump( build ); - - // strip this ent from the .bsp file - entities[i].epairs = 0; - } - } - - // Strip out lighting origins; has to be done here because they are used when - // static props are made - for ( i = s_LightingInfo.Count(); --i >= 0; ) - { - // strip this ent from the .bsp file - entities[s_LightingInfo[i]].epairs = 0; - } - - - SetLumpData( ); -} - -static studiohdr_t *g_pActiveStudioHdr; -static void SetCurrentModel( studiohdr_t *pStudioHdr ) -{ - // track the correct model - g_pActiveStudioHdr = pStudioHdr; -} - -static void FreeCurrentModelVertexes() -{ - Assert( g_pActiveStudioHdr ); - - if ( g_pActiveStudioHdr->pVertexBase ) - { - free( g_pActiveStudioHdr->pVertexBase ); - g_pActiveStudioHdr->pVertexBase = NULL; - } -} - -const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void * pModelData ) -{ - char fileName[260]; - FileHandle_t fileHandle; - vertexFileHeader_t *pVvdHdr; - - Assert( pModelData == NULL ); - Assert( g_pActiveStudioHdr ); - - if ( g_pActiveStudioHdr->pVertexBase ) - { - return (vertexFileHeader_t *)g_pActiveStudioHdr->pVertexBase; - } - - // mandatory callback to make requested data resident - // load and persist the vertex file - strcpy( fileName, "models/" ); - strcat( fileName, g_pActiveStudioHdr->pszName() ); - Q_StripExtension( fileName, fileName, sizeof( fileName ) ); - strcat( fileName, ".vvd" ); - - // load the model - fileHandle = g_pFileSystem->Open( fileName, "rb" ); - if ( !fileHandle ) - { - Error( "Unable to load vertex data \"%s\"\n", fileName ); - } - - // Get the file size - int size = g_pFileSystem->Size( fileHandle ); - if (size == 0) - { - g_pFileSystem->Close( fileHandle ); - Error( "Bad size for vertex data \"%s\"\n", fileName ); - } - - pVvdHdr = (vertexFileHeader_t *)malloc(size); - g_pFileSystem->Read( pVvdHdr, size, fileHandle ); - g_pFileSystem->Close( fileHandle ); - - // check header - if (pVvdHdr->id != MODEL_VERTEX_FILE_ID) - { - Error("Error Vertex File %s id %d should be %d\n", fileName, pVvdHdr->id, MODEL_VERTEX_FILE_ID); - } - if (pVvdHdr->version != MODEL_VERTEX_FILE_VERSION) - { - Error("Error Vertex File %s version %d should be %d\n", fileName, pVvdHdr->version, MODEL_VERTEX_FILE_VERSION); - } - if (pVvdHdr->checksum != g_pActiveStudioHdr->checksum) - { - Error("Error Vertex File %s checksum %d should be %d\n", fileName, pVvdHdr->checksum, g_pActiveStudioHdr->checksum); - } - - g_pActiveStudioHdr->pVertexBase = (void*)pVvdHdr; - return pVvdHdr; -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Places "detail" objects which are client-only renderable things +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "bsplib.h" +#include "utlvector.h" +#include "bspfile.h" +#include "gamebspfile.h" +#include "VPhysics_Interface.h" +#include "Studio.h" +#include "byteswap.h" +#include "UtlBuffer.h" +#include "CollisionUtils.h" +#include +#include "CModel.h" +#include "PhysDll.h" +#include "utlsymbol.h" +#include "tier1/strtools.h" +#include "KeyValues.h" + +static void SetCurrentModel( studiohdr_t *pStudioHdr ); +static void FreeCurrentModelVertexes(); + +IPhysicsCollision *s_pPhysCollision = NULL; + +//----------------------------------------------------------------------------- +// These puppies are used to construct the game lumps +//----------------------------------------------------------------------------- +static CUtlVector s_StaticPropDictLump; +static CUtlVector s_StaticPropLump; +static CUtlVector s_StaticPropLeafLump; + + +//----------------------------------------------------------------------------- +// Used to build the static prop +//----------------------------------------------------------------------------- +struct StaticPropBuild_t +{ + char const* m_pModelName; + char const* m_pLightingOrigin; + Vector m_Origin; + QAngle m_Angles; + int m_Solid; + int m_Skin; + int m_Flags; + float m_FadeMinDist; + float m_FadeMaxDist; + bool m_FadesOut; + float m_flForcedFadeScale; + unsigned short m_nMinDXLevel; + unsigned short m_nMaxDXLevel; +}; + + +//----------------------------------------------------------------------------- +// Used to cache collision model generation +//----------------------------------------------------------------------------- +struct ModelCollisionLookup_t +{ + CUtlSymbol m_Name; + CPhysCollide* m_pCollide; +}; + +static bool ModelLess( ModelCollisionLookup_t const& src1, ModelCollisionLookup_t const& src2 ) +{ + return src1.m_Name < src2.m_Name; +} + +static CUtlRBTree s_ModelCollisionCache( 0, 32, ModelLess ); +static CUtlVector s_LightingInfo; + + +//----------------------------------------------------------------------------- +// Gets the keyvalues from a studiohdr +//----------------------------------------------------------------------------- +bool StudioKeyValues( studiohdr_t* pStudioHdr, KeyValues *pValue ) +{ + if ( !pStudioHdr ) + return false; + + return pValue->LoadFromBuffer( pStudioHdr->pszName(), pStudioHdr->KeyValueText() ); +} + + +//----------------------------------------------------------------------------- +// Makes sure the studio model is a static prop +//----------------------------------------------------------------------------- +enum isstaticprop_ret +{ + RET_VALID, + RET_FAIL_NOT_MARKED_STATIC_PROP, + RET_FAIL_DYNAMIC, +}; + +isstaticprop_ret IsStaticProp( studiohdr_t* pHdr ) +{ + if (!(pHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP)) + return RET_FAIL_NOT_MARKED_STATIC_PROP; + + // If it's got a propdata section in the model's keyvalues, it's not allowed to be a prop_static + KeyValues *modelKeyValues = new KeyValues(pHdr->pszName()); + if ( StudioKeyValues( pHdr, modelKeyValues ) ) + { + KeyValues *sub = modelKeyValues->FindKey("prop_data"); + if ( sub ) + { + if ( !(sub->GetInt( "allowstatic", 0 )) ) + { + modelKeyValues->deleteThis(); + return RET_FAIL_DYNAMIC; + } + } + } + modelKeyValues->deleteThis(); + + return RET_VALID; +} + + +//----------------------------------------------------------------------------- +// Add static prop model to the list of models +//----------------------------------------------------------------------------- + +static int AddStaticPropDictLump( char const* pModelName ) +{ + StaticPropDictLump_t dictLump; + strncpy( dictLump.m_Name, pModelName, DETAIL_NAME_LENGTH ); + + for (int i = s_StaticPropDictLump.Size(); --i >= 0; ) + { + if (!memcmp(&s_StaticPropDictLump[i], &dictLump, sizeof(dictLump) )) + return i; + } + + return s_StaticPropDictLump.AddToTail( dictLump ); +} + + +//----------------------------------------------------------------------------- +// Load studio model vertex data from a file... +//----------------------------------------------------------------------------- +bool LoadStudioModel( char const* pModelName, char const* pEntityType, CUtlBuffer& buf ) +{ + if ( !g_pFullFileSystem->ReadFile( pModelName, NULL, buf ) ) + return false; + + // Check that it's valid + if (strncmp ((const char *) buf.PeekGet(), "IDST", 4) && + strncmp ((const char *) buf.PeekGet(), "IDAG", 4)) + { + return false; + } + + studiohdr_t* pHdr = (studiohdr_t*)buf.PeekGet(); + + Studio_ConvertStudioHdrToNewVersion( pHdr ); + + if (pHdr->version != STUDIO_VERSION) + { + return false; + } + + isstaticprop_ret isStaticProp = IsStaticProp(pHdr); + if ( isStaticProp != RET_VALID ) + { + if ( isStaticProp == RET_FAIL_NOT_MARKED_STATIC_PROP ) + { + Warning("Error! To use model \"%s\"\n" + " with %s, it must be compiled with $staticprop!\n", pModelName, pEntityType ); + } + else if ( isStaticProp == RET_FAIL_DYNAMIC ) + { + Warning("Error! %s using model \"%s\", which must be used on a dynamic entity (i.e. prop_physics). Deleted.\n", pEntityType, pModelName ); + } + return false; + } + + // ensure reset + pHdr->pVertexBase = NULL; + pHdr->pIndexBase = NULL; + + return true; +} + + +//----------------------------------------------------------------------------- +// Computes a convex hull from a studio mesh +//----------------------------------------------------------------------------- +static CPhysConvex* ComputeConvexHull( mstudiomesh_t* pMesh ) +{ + // Generate a list of all verts in the mesh + Vector** ppVerts = (Vector**)stackalloc(pMesh->numvertices * sizeof(Vector*) ); + const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData(); + Assert( vertData ); // This can only return NULL on X360 for now + for (int i = 0; i < pMesh->numvertices; ++i) + { + ppVerts[i] = vertData->Position(i); + } + + // Generate a convex hull from the verts + return s_pPhysCollision->ConvexFromVerts( ppVerts, pMesh->numvertices ); +} + + +//----------------------------------------------------------------------------- +// Computes a convex hull from the studio model +//----------------------------------------------------------------------------- +CPhysCollide* ComputeConvexHull( studiohdr_t* pStudioHdr ) +{ + CUtlVector convexHulls; + + for (int body = 0; body < pStudioHdr->numbodyparts; ++body ) + { + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( body ); + for( int model = 0; model < pBodyPart->nummodels; ++model ) + { + mstudiomodel_t *pStudioModel = pBodyPart->pModel( model ); + for( int mesh = 0; mesh < pStudioModel->nummeshes; ++mesh ) + { + // Make a convex hull for each mesh + // NOTE: This won't work unless the model has been compiled + // with $staticprop + mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( mesh ); + convexHulls.AddToTail( ComputeConvexHull( pStudioMesh ) ); + } + } + } + + // Convert an array of convex elements to a compiled collision model + // (this deletes the convex elements) + return s_pPhysCollision->ConvertConvexToCollide( convexHulls.Base(), convexHulls.Size() ); +} + + +//----------------------------------------------------------------------------- +// Add, find collision model in cache +//----------------------------------------------------------------------------- +static CPhysCollide* GetCollisionModel( char const* pModelName ) +{ + // Convert to a common string + char* pTemp = (char*)_alloca(strlen(pModelName) + 1); + strcpy( pTemp, pModelName ); + _strlwr( pTemp ); + + char* pSlash = strchr( pTemp, '\\' ); + while( pSlash ) + { + *pSlash = '/'; + pSlash = strchr( pTemp, '\\' ); + } + + // Find it in the cache + ModelCollisionLookup_t lookup; + lookup.m_Name = pTemp; + int i = s_ModelCollisionCache.Find( lookup ); + if (i != s_ModelCollisionCache.InvalidIndex()) + return s_ModelCollisionCache[i].m_pCollide; + + // Load the studio model file + CUtlBuffer buf; + if (!LoadStudioModel(pModelName, "prop_static", buf)) + { + Warning("Error loading studio model \"%s\"!\n", pModelName ); + + // This way we don't try to load it multiple times + lookup.m_pCollide = 0; + s_ModelCollisionCache.Insert( lookup ); + + return 0; + } + + // Compute the convex hull of the model... + studiohdr_t* pStudioHdr = (studiohdr_t*)buf.PeekGet(); + + // necessary for vertex access + SetCurrentModel( pStudioHdr ); + + lookup.m_pCollide = ComputeConvexHull( pStudioHdr ); + s_ModelCollisionCache.Insert( lookup ); + + if ( !lookup.m_pCollide ) + { + Warning("Bad geometry on \"%s\"!\n", pModelName ); + } + + // Debugging + if (g_DumpStaticProps) + { + static int propNum = 0; + char tmp[128]; + sprintf( tmp, "staticprop%03d.txt", propNum ); + DumpCollideToGlView( lookup.m_pCollide, tmp ); + ++propNum; + } + + FreeCurrentModelVertexes(); + + // Insert into cache... + return lookup.m_pCollide; +} + + +//----------------------------------------------------------------------------- +// Tests a single leaf against the static prop +//----------------------------------------------------------------------------- + +static bool TestLeafAgainstCollide( int depth, int* pNodeList, + Vector const& origin, QAngle const& angles, CPhysCollide* pCollide ) +{ + // Copy the planes in the node list into a list of planes + float* pPlanes = (float*)_alloca(depth * 4 * sizeof(float) ); + int idx = 0; + for (int i = depth; --i >= 0; ++idx ) + { + int sign = (pNodeList[i] < 0) ? -1 : 1; + int node = (sign < 0) ? - pNodeList[i] - 1 : pNodeList[i]; + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + pPlanes[idx*4] = sign * pPlane->normal[0]; + pPlanes[idx*4+1] = sign * pPlane->normal[1]; + pPlanes[idx*4+2] = sign * pPlane->normal[2]; + pPlanes[idx*4+3] = sign * pPlane->dist; + } + + // Make a convex solid out of the planes + CPhysConvex* pPhysConvex = s_pPhysCollision->ConvexFromPlanes( pPlanes, depth, 0.0f ); + + // This should never happen, but if it does, return no collision + Assert( pPhysConvex ); + if (!pPhysConvex) + return false; + + CPhysCollide* pLeafCollide = s_pPhysCollision->ConvertConvexToCollide( &pPhysConvex, 1 ); + + // Collide the leaf solid with the static prop solid + trace_t tr; + s_pPhysCollision->TraceCollide( vec3_origin, vec3_origin, pLeafCollide, vec3_angle, + pCollide, origin, angles, &tr ); + + s_pPhysCollision->DestroyCollide( pLeafCollide ); + + return (tr.startsolid != 0); +} + +//----------------------------------------------------------------------------- +// Find all leaves that intersect with this bbox + test against the static prop.. +//----------------------------------------------------------------------------- + +static void ComputeConvexHullLeaves_R( int node, int depth, int* pNodeList, + Vector const& mins, Vector const& maxs, + Vector const& origin, QAngle const& angles, CPhysCollide* pCollide, + CUtlVector& leafList ) +{ + Assert( pNodeList && pCollide ); + Vector cornermin, cornermax; + + while( node >= 0 ) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + // Arbitrary split plane here + for (int i = 0; i < 3; ++i) + { + if (pPlane->normal[i] >= 0) + { + cornermin[i] = mins[i]; + cornermax[i] = maxs[i]; + } + else + { + cornermin[i] = maxs[i]; + cornermax[i] = mins[i]; + } + } + + if (DotProduct( pPlane->normal, cornermax ) <= pPlane->dist) + { + // Add the node to the list of nodes + pNodeList[depth] = node; + ++depth; + + node = pNode->children[1]; + } + else if (DotProduct( pPlane->normal, cornermin ) >= pPlane->dist) + { + // In this case, we are going in front of the plane. That means that + // this plane must have an outward normal facing in the oppisite direction + // We indicate this be storing a negative node index in the node list + pNodeList[depth] = - node - 1; + ++depth; + + node = pNode->children[0]; + } + else + { + // Here the box is split by the node. First, we'll add the plane as if its + // outward facing normal is in the direction of the node plane, then + // we'll have to reverse it for the other child... + pNodeList[depth] = node; + ++depth; + + ComputeConvexHullLeaves_R( pNode->children[1], + depth, pNodeList, mins, maxs, origin, angles, pCollide, leafList ); + + pNodeList[depth - 1] = - node - 1; + ComputeConvexHullLeaves_R( pNode->children[0], + depth, pNodeList, mins, maxs, origin, angles, pCollide, leafList ); + return; + } + } + + Assert( pNodeList && pCollide ); + + // Never add static props to solid leaves + if ( (dleafs[-node-1].contents & CONTENTS_SOLID) == 0 ) + { + if (TestLeafAgainstCollide( depth, pNodeList, origin, angles, pCollide )) + { + leafList.AddToTail( -node - 1 ); + } + } +} + +//----------------------------------------------------------------------------- +// Places Static Props in the level +//----------------------------------------------------------------------------- + +static void ComputeStaticPropLeaves( CPhysCollide* pCollide, Vector const& origin, + QAngle const& angles, CUtlVector& leafList ) +{ + // Compute an axis-aligned bounding box for the collide + Vector mins, maxs; + s_pPhysCollision->CollideGetAABB( &mins, &maxs, pCollide, origin, angles ); + + // Find all leaves that intersect with the bounds + int tempNodeList[1024]; + ComputeConvexHullLeaves_R( 0, 0, tempNodeList, mins, maxs, + origin, angles, pCollide, leafList ); +} + + +//----------------------------------------------------------------------------- +// Computes the lighting origin +//----------------------------------------------------------------------------- +static bool ComputeLightingOrigin( StaticPropBuild_t const& build, Vector& lightingOrigin ) +{ + for (int i = s_LightingInfo.Count(); --i >= 0; ) + { + int entIndex = s_LightingInfo[i]; + + // Check against all lighting info entities + char const* pTargetName = ValueForKey( &entities[entIndex], "targetname" ); + if (!Q_strcmp(pTargetName, build.m_pLightingOrigin)) + { + GetVectorForKey( &entities[entIndex], "origin", lightingOrigin ); + return true; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Places Static Props in the level +//----------------------------------------------------------------------------- +static void AddStaticPropToLump( StaticPropBuild_t const& build ) +{ + // Get the collision model + CPhysCollide* pConvexHull = GetCollisionModel( build.m_pModelName ); + if (!pConvexHull) + return; + + // Compute the leaves the static prop's convex hull hits + CUtlVector< unsigned short > leafList; + ComputeStaticPropLeaves( pConvexHull, build.m_Origin, build.m_Angles, leafList ); + + if ( !leafList.Count() ) + { + Warning( "Static prop %s outside the map (%.2f, %.2f, %.2f)\n", build.m_pModelName, build.m_Origin.x, build.m_Origin.y, build.m_Origin.z ); + return; + } + // Insert an element into the lump data... + int i = s_StaticPropLump.AddToTail( ); + StaticPropLump_t& propLump = s_StaticPropLump[i]; + propLump.m_PropType = AddStaticPropDictLump( build.m_pModelName ); + VectorCopy( build.m_Origin, propLump.m_Origin ); + VectorCopy( build.m_Angles, propLump.m_Angles ); + propLump.m_FirstLeaf = s_StaticPropLeafLump.Count(); + propLump.m_LeafCount = leafList.Count(); + propLump.m_Solid = build.m_Solid; + propLump.m_Skin = build.m_Skin; + propLump.m_Flags = build.m_Flags; + if (build.m_FadesOut) + { + propLump.m_Flags |= STATIC_PROP_FLAG_FADES; + } + propLump.m_FadeMinDist = build.m_FadeMinDist; + propLump.m_FadeMaxDist = build.m_FadeMaxDist; + propLump.m_flForcedFadeScale = build.m_flForcedFadeScale; + propLump.m_nMinDXLevel = build.m_nMinDXLevel; + propLump.m_nMaxDXLevel = build.m_nMaxDXLevel; + + if (build.m_pLightingOrigin && *build.m_pLightingOrigin) + { + if (ComputeLightingOrigin( build, propLump.m_LightingOrigin )) + { + propLump.m_Flags |= STATIC_PROP_USE_LIGHTING_ORIGIN; + } + } + + // Add the leaves to the leaf lump + for (int j = 0; j < leafList.Size(); ++j) + { + StaticPropLeafLump_t insert; + insert.m_Leaf = leafList[j]; + s_StaticPropLeafLump.AddToTail( insert ); + } +} + + +//----------------------------------------------------------------------------- +// Places static props in the lump +//----------------------------------------------------------------------------- + +static void SetLumpData( ) +{ + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(GAMELUMP_STATIC_PROPS); + if (handle != g_GameLumps.InvalidGameLump()) + g_GameLumps.DestroyGameLump(handle); + + int dictsize = s_StaticPropDictLump.Size() * sizeof(StaticPropDictLump_t); + int objsize = s_StaticPropLump.Size() * sizeof(StaticPropLump_t); + int leafsize = s_StaticPropLeafLump.Size() * sizeof(StaticPropLeafLump_t); + int size = dictsize + objsize + leafsize + 3 * sizeof(int); + + handle = g_GameLumps.CreateGameLump( GAMELUMP_STATIC_PROPS, size, 0, GAMELUMP_STATIC_PROPS_VERSION ); + + // Serialize the data + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), size ); + buf.PutInt( s_StaticPropDictLump.Size() ); + if (dictsize) + buf.Put( s_StaticPropDictLump.Base(), dictsize ); + buf.PutInt( s_StaticPropLeafLump.Size() ); + if (leafsize) + buf.Put( s_StaticPropLeafLump.Base(), leafsize ); + buf.PutInt( s_StaticPropLump.Size() ); + if (objsize) + buf.Put( s_StaticPropLump.Base(), objsize ); +} + + +//----------------------------------------------------------------------------- +// Places Static Props in the level +//----------------------------------------------------------------------------- + +void EmitStaticProps() +{ + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); + if ( physicsFactory ) + { + s_pPhysCollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); + if( !s_pPhysCollision ) + return; + } + + // Generate a list of lighting origins, and strip them out + int i; + for ( i = 0; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!Q_strcmp(pEntity, "info_lighting")) + { + s_LightingInfo.AddToTail(i); + } + } + + // Emit specifically specified static props + for ( i = 0; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!strcmp(pEntity, "static_prop") || !strcmp(pEntity, "prop_static")) + { + StaticPropBuild_t build; + + GetVectorForKey( &entities[i], "origin", build.m_Origin ); + GetAnglesForKey( &entities[i], "angles", build.m_Angles ); + build.m_pModelName = ValueForKey( &entities[i], "model" ); + build.m_Solid = IntForKey( &entities[i], "solid" ); + build.m_Skin = IntForKey( &entities[i], "skin" ); + build.m_FadeMaxDist = FloatForKey( &entities[i], "fademaxdist" ); + build.m_Flags = 0;//IntForKey( &entities[i], "spawnflags" ) & STATIC_PROP_WC_MASK; + if (IntForKey( &entities[i], "ignorenormals" ) == 1) + { + build.m_Flags |= STATIC_PROP_IGNORE_NORMALS; + } + if (IntForKey( &entities[i], "disableshadows" ) == 1) + { + build.m_Flags |= STATIC_PROP_NO_SHADOW; + } + if (IntForKey( &entities[i], "disablevertexlighting" ) == 1) + { + build.m_Flags |= STATIC_PROP_NO_PER_VERTEX_LIGHTING; + } + if (IntForKey( &entities[i], "disableselfshadowing" ) == 1) + { + build.m_Flags |= STATIC_PROP_NO_SELF_SHADOWING; + } + + if (IntForKey( &entities[i], "screenspacefade" ) == 1) + { + build.m_Flags |= STATIC_PROP_SCREEN_SPACE_FADE; + } + + const char *pKey = ValueForKey( &entities[i], "fadescale" ); + if ( pKey && pKey[0] ) + { + build.m_flForcedFadeScale = FloatForKey( &entities[i], "fadescale" ); + } + else + { + build.m_flForcedFadeScale = 1; + } + build.m_FadesOut = (build.m_FadeMaxDist > 0); + build.m_pLightingOrigin = ValueForKey( &entities[i], "lightingorigin" ); + if (build.m_FadesOut) + { + build.m_FadeMinDist = FloatForKey( &entities[i], "fademindist" ); + if (build.m_FadeMinDist < 0) + { + build.m_FadeMinDist = build.m_FadeMaxDist; + } + } + else + { + build.m_FadeMinDist = 0; + } + build.m_nMinDXLevel = (unsigned short)IntForKey( &entities[i], "mindxlevel" ); + build.m_nMaxDXLevel = (unsigned short)IntForKey( &entities[i], "maxdxlevel" ); + AddStaticPropToLump( build ); + + // strip this ent from the .bsp file + entities[i].epairs = 0; + } + } + + // Strip out lighting origins; has to be done here because they are used when + // static props are made + for ( i = s_LightingInfo.Count(); --i >= 0; ) + { + // strip this ent from the .bsp file + entities[s_LightingInfo[i]].epairs = 0; + } + + + SetLumpData( ); +} + +static studiohdr_t *g_pActiveStudioHdr; +static void SetCurrentModel( studiohdr_t *pStudioHdr ) +{ + // track the correct model + g_pActiveStudioHdr = pStudioHdr; +} + +static void FreeCurrentModelVertexes() +{ + Assert( g_pActiveStudioHdr ); + + if ( g_pActiveStudioHdr->pVertexBase ) + { + free( g_pActiveStudioHdr->pVertexBase ); + g_pActiveStudioHdr->pVertexBase = NULL; + } +} + +const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void * pModelData ) +{ + char fileName[260]; + FileHandle_t fileHandle; + vertexFileHeader_t *pVvdHdr; + + Assert( pModelData == NULL ); + Assert( g_pActiveStudioHdr ); + + if ( g_pActiveStudioHdr->pVertexBase ) + { + return (vertexFileHeader_t *)g_pActiveStudioHdr->pVertexBase; + } + + // mandatory callback to make requested data resident + // load and persist the vertex file + strcpy( fileName, "models/" ); + strcat( fileName, g_pActiveStudioHdr->pszName() ); + Q_StripExtension( fileName, fileName, sizeof( fileName ) ); + strcat( fileName, ".vvd" ); + + // load the model + fileHandle = g_pFileSystem->Open( fileName, "rb" ); + if ( !fileHandle ) + { + Error( "Unable to load vertex data \"%s\"\n", fileName ); + } + + // Get the file size + int size = g_pFileSystem->Size( fileHandle ); + if (size == 0) + { + g_pFileSystem->Close( fileHandle ); + Error( "Bad size for vertex data \"%s\"\n", fileName ); + } + + pVvdHdr = (vertexFileHeader_t *)malloc(size); + g_pFileSystem->Read( pVvdHdr, size, fileHandle ); + g_pFileSystem->Close( fileHandle ); + + // check header + if (pVvdHdr->id != MODEL_VERTEX_FILE_ID) + { + Error("Error Vertex File %s id %d should be %d\n", fileName, pVvdHdr->id, MODEL_VERTEX_FILE_ID); + } + if (pVvdHdr->version != MODEL_VERTEX_FILE_VERSION) + { + Error("Error Vertex File %s version %d should be %d\n", fileName, pVvdHdr->version, MODEL_VERTEX_FILE_VERSION); + } + if (pVvdHdr->checksum != g_pActiveStudioHdr->checksum) + { + Error("Error Vertex File %s checksum %d should be %d\n", fileName, pVvdHdr->checksum, g_pActiveStudioHdr->checksum); + } + + g_pActiveStudioHdr->pVertexBase = (void*)pVvdHdr; + return pVvdHdr; +} + diff --git a/mp/src/utils/vbsp/textures.cpp b/mp/src/utils/vbsp/textures.cpp index fc2034eb..4f49c5d4 100644 --- a/mp/src/utils/vbsp/textures.cpp +++ b/mp/src/utils/vbsp/textures.cpp @@ -1,737 +1,737 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "vbsp.h" -#include "utilmatlib.h" -#include "physdll.h" -#include -#include -#include "tier1/strtools.h" -#include "materialpatch.h" -#include "KeyValues.h" - -void LoadSurfaceProperties( void ); - -IPhysicsSurfaceProps *physprops = NULL; - -int nummiptex; -textureref_t textureref[MAX_MAP_TEXTURES]; - -bool g_bHasWater = false; - -extern qboolean onlyents; - -dtexdata_t *GetTexData( int index ) -{ - if ( index < 0 ) - return NULL; - Assert( !onlyents ); - return &dtexdata[ index ]; -} - -static qboolean StringIsTrue( const char *str ) -{ - if( Q_strcasecmp( str, "true" ) == 0 ) - { - return true; - } - if( Q_strcasecmp( str, "1" ) == 0 ) - { - return true; - } - return false; -} - -int FindMiptex (const char *name) -{ - int i; - MaterialSystemMaterial_t matID; - const char *propVal, *propVal2; - int opacity; - bool found; - - for (i=0 ; inormal, baseaxis[i*3]); - if (dot > best) - { - best = dot; - bestaxis = i; - } - } - - VectorCopy (baseaxis[bestaxis*3+1], xv); - VectorCopy (baseaxis[bestaxis*3+2], yv); -} - - - -int g_SurfaceProperties[MAX_MAP_TEXDATA]; - - -int GetSurfaceProperties( MaterialSystemMaterial_t matID, const char *pMatName ) -{ - const char *pPropString = NULL; - int surfaceIndex = -1; - - if ( physprops ) - { - pPropString = GetMaterialVar( matID, "$surfaceprop" ); - if ( pPropString ) - { - surfaceIndex = physprops->GetSurfaceIndex( pPropString ); - if ( surfaceIndex < 0 ) - { - Msg("Can't find surfaceprop %s for material %s, using default\n", pPropString, pMatName ); - surfaceIndex = physprops->GetSurfaceIndex( pPropString ); - surfaceIndex = physprops->GetSurfaceIndex( "default" ); - } - } - } - - return surfaceIndex; -} - -int GetSurfaceProperties2( MaterialSystemMaterial_t matID, const char *pMatName ) -{ - const char *pPropString = NULL; - int surfaceIndex = -1; - - if ( physprops ) - { - pPropString = GetMaterialVar( matID, "$surfaceprop2" ); - if ( pPropString ) - { - surfaceIndex = physprops->GetSurfaceIndex( pPropString ); - if ( surfaceIndex < 0 ) - { - Msg("Can't find surfacepropblend %s for material %s, using default\n", pPropString, pMatName ); - surfaceIndex = physprops->GetSurfaceIndex( "default" ); - } - } - else - { - // No surface property 2. - return -1; - } - } - - return surfaceIndex; -} - -//----------------------------------------------------------------------------- -// Purpose: Finds or adds a texdata for the specified name ( same as below except -// instead of finding the named texture, copies the settings from the passed -// in sourceTexture. ) -// Used for creation of one off .vmt files for water surface textures -// Input : *pName - texture name -// Output : int index into dtexdata array -//----------------------------------------------------------------------------- -int FindAliasedTexData( const char *pName_, dtexdata_t *sourceTexture ) -{ - char *pName = ( char * )_alloca( strlen( pName_ ) + 1 ); - strcpy( pName, pName_ ); - strlwr( pName ); - int i, output; - bool found; - dtexdata_t *pTexData; - MaterialSystemMaterial_t matID; - - for ( i = 0; i < numtexdata; i++ ) - { - if ( !strcmp( pName, TexDataStringTable_GetString( GetTexData( i )->nameStringTableID ) ) ) - return i; - } - - - output = numtexdata; - if ( numtexdata >= MAX_MAP_TEXDATA ) - { - Error( "Too many unique texture mappings, max = %d\n", MAX_MAP_TEXDATA ); - } - pTexData = GetTexData( output ); - numtexdata++; - - // Save the name of the material. - pTexData->nameStringTableID = TexDataStringTable_AddOrFindString( pName ); - - // Get the width, height, view_width, view_height, and reflectivity from the material system. - matID = FindOriginalMaterial( TexDataStringTable_GetString( sourceTexture->nameStringTableID ), &found, false ); - if( matID == MATERIAL_NOT_FOUND || (!found) ) - { - qprintf( "WARNING: material not found: \"%s\"\n", pName ); - return -1; - } - - GetMaterialDimensions( matID, &pTexData->width, &pTexData->height ); - pTexData->view_width = pTexData->width; // undone: what is this? - pTexData->view_height = pTexData->height; // undone: what is this? - - GetMaterialReflectivity( matID, pTexData->reflectivity.Base() ); - g_SurfaceProperties[output] = GetSurfaceProperties( matID, pName ); - - return output; -} - - -//----------------------------------------------------------------------------- -// Finds a texdata for the specified name, returns -1 if not found -//----------------------------------------------------------------------------- -int FindTexData( const char *pName ) -{ - // Make sure the texdata doesn't already exist. - for( int i = 0; i < numtexdata; i++ ) - { - char const *pTexDataName = TexDataStringTable_GetString( GetTexData( i )->nameStringTableID ); - if ( !Q_stricmp( pTexDataName, pName ) ) - return i; - } - return -1; -} - - - -//----------------------------------------------------------------------------- -// Purpose: Finds or adds a texdata for the specified name -// Input : *pName - texture name -// Output : int index into dtexdata array -//----------------------------------------------------------------------------- -int FindOrCreateTexData( const char *pName_ ) -{ - char *pName = ( char * )_alloca( strlen( pName_ ) + 1 ); - strcpy( pName, pName_ ); - - int nOutput = FindTexData( pName ); - if ( nOutput >= 0 ) - return nOutput; - - // Didn't find it, add a new one - nOutput = numtexdata; - if ( numtexdata >= MAX_MAP_TEXDATA ) - { - Error( "Too many unique texture mappings, max = %d\n", MAX_MAP_TEXDATA ); - } - dtexdata_t *pTexData = GetTexData( nOutput ); - numtexdata++; - - // Save the name of the material. - pTexData->nameStringTableID = TexDataStringTable_AddOrFindString( pName ); - - // Get the width, height, view_width, view_height, and reflectivity from the material system. - bool bFound; - MaterialSystemMaterial_t matID = FindOriginalMaterial( pName, &bFound ); - if ( matID == MATERIAL_NOT_FOUND || (!bFound) ) - { - qprintf( "WARNING: material not found: \"%s\"\n", pName ); - return nOutput; - } - - GetMaterialDimensions( matID, &pTexData->width, &pTexData->height ); - pTexData->view_width = pTexData->width; // undone: what is this? - pTexData->view_height = pTexData->height; // undone: what is this? - - GetMaterialReflectivity( matID, pTexData->reflectivity.Base() ); - g_SurfaceProperties[nOutput] = GetSurfaceProperties( matID, pName ); - -#if 0 - Msg( "reflectivity: %f %f %f\n", - pTexData->reflectivity[0], - pTexData->reflectivity[1], - pTexData->reflectivity[2] ); -#endif - - return nOutput; -} - -int AddCloneTexData( dtexdata_t *pExistingTexData, char const *cloneTexDataName ) -{ - int existingIndex = pExistingTexData - GetTexData( 0 ); - dtexdata_t *pNewTexData = GetTexData( numtexdata ); - int newIndex = numtexdata; - numtexdata++; - - *pNewTexData = *pExistingTexData; - pNewTexData->nameStringTableID = TexDataStringTable_AddOrFindString( cloneTexDataName ); - g_SurfaceProperties[newIndex] = g_SurfaceProperties[existingIndex]; - - return newIndex; -} - - -//----------------------------------------------------------------------------- -// Finds a texinfo that exactly matches the passed in texinfo -//----------------------------------------------------------------------------- -int FindTexInfo( const texinfo_t &searchTexInfo ) -{ - for( int i = 0; i < texinfo.Count(); i++ ) - { - // Just an early-out for performance - if ( texinfo[i].texdata != searchTexInfo.texdata ) - continue; - - if ( !memcmp( &texinfo[i], &searchTexInfo, sizeof( texinfo_t ) ) ) - return i; - } - - return -1; -} - - -//----------------------------------------------------------------------------- -// Finds or creates a texinfo that exactly matches the passed in texinfo -//----------------------------------------------------------------------------- -int FindOrCreateTexInfo( const texinfo_t &searchTexInfo ) -{ - int i = FindTexInfo( searchTexInfo ); - if ( i >= 0 ) - return i; - - i = texinfo.AddToTail( searchTexInfo ); - - if ( onlyents ) - { - Error( "FindOrCreateTexInfo: Tried to create new texinfo during -onlyents compile!\nMust compile without -onlyents" ); - } - - return i; -} - -int TexinfoForBrushTexture (plane_t *plane, brush_texture_t *bt, const Vector& origin) -{ - Vector vecs[2]; - int sv, tv; - vec_t ang, sinv, cosv; - vec_t ns, nt; - texinfo_t tx; - int i, j; - - if (!bt->name[0]) - return 0; - - memset (&tx, 0, sizeof(tx)); - - // HLTOOLS - add support for texture vectors stored in the map file - if (g_nMapFileVersion < 220) - { - TextureAxisFromPlane(plane, vecs[0], vecs[1]); - } - - if (!bt->textureWorldUnitsPerTexel[0]) - bt->textureWorldUnitsPerTexel[0] = 1; - if (!bt->textureWorldUnitsPerTexel[1]) - bt->textureWorldUnitsPerTexel[1] = 1; - - - float shiftScaleU = 1.0f / 16.0f; - float shiftScaleV = 1.0f / 16.0f; - - if (g_nMapFileVersion < 220) - { - // rotate axis - if (bt->rotate == 0) - { sinv = 0 ; cosv = 1; } - else if (bt->rotate == 90) - { sinv = 1 ; cosv = 0; } - else if (bt->rotate == 180) - { sinv = 0 ; cosv = -1; } - else if (bt->rotate == 270) - { sinv = -1 ; cosv = 0; } - else - { - ang = bt->rotate / 180 * M_PI; - sinv = sin(ang); - cosv = cos(ang); - } - - if (vecs[0][0]) - sv = 0; - else if (vecs[0][1]) - sv = 1; - else - sv = 2; - - if (vecs[1][0]) - tv = 0; - else if (vecs[1][1]) - tv = 1; - else - tv = 2; - - for (i=0 ; i<2 ; i++) - { - ns = cosv * vecs[i][sv] - sinv * vecs[i][tv]; - nt = sinv * vecs[i][sv] + cosv * vecs[i][tv]; - vecs[i][sv] = ns; - vecs[i][tv] = nt; - } - - for (i=0 ; i<2 ; i++) - { - for (j=0 ; j<3 ; j++) - { - tx.textureVecsTexelsPerWorldUnits[i][j] = vecs[i][j] / bt->textureWorldUnitsPerTexel[i]; - tx.lightmapVecsLuxelsPerWorldUnits[i][j] = tx.textureVecsTexelsPerWorldUnits[i][j] / 16.0f; - } - } - } - else - { - tx.textureVecsTexelsPerWorldUnits[0][0] = bt->UAxis[0] / bt->textureWorldUnitsPerTexel[0]; - tx.textureVecsTexelsPerWorldUnits[0][1] = bt->UAxis[1] / bt->textureWorldUnitsPerTexel[0]; - tx.textureVecsTexelsPerWorldUnits[0][2] = bt->UAxis[2] / bt->textureWorldUnitsPerTexel[0]; - - tx.textureVecsTexelsPerWorldUnits[1][0] = bt->VAxis[0] / bt->textureWorldUnitsPerTexel[1]; - tx.textureVecsTexelsPerWorldUnits[1][1] = bt->VAxis[1] / bt->textureWorldUnitsPerTexel[1]; - tx.textureVecsTexelsPerWorldUnits[1][2] = bt->VAxis[2] / bt->textureWorldUnitsPerTexel[1]; - - tx.lightmapVecsLuxelsPerWorldUnits[0][0] = bt->UAxis[0] / bt->lightmapWorldUnitsPerLuxel; - tx.lightmapVecsLuxelsPerWorldUnits[0][1] = bt->UAxis[1] / bt->lightmapWorldUnitsPerLuxel; - tx.lightmapVecsLuxelsPerWorldUnits[0][2] = bt->UAxis[2] / bt->lightmapWorldUnitsPerLuxel; - - tx.lightmapVecsLuxelsPerWorldUnits[1][0] = bt->VAxis[0] / bt->lightmapWorldUnitsPerLuxel; - tx.lightmapVecsLuxelsPerWorldUnits[1][1] = bt->VAxis[1] / bt->lightmapWorldUnitsPerLuxel; - tx.lightmapVecsLuxelsPerWorldUnits[1][2] = bt->VAxis[2] / bt->lightmapWorldUnitsPerLuxel; - - shiftScaleU = bt->textureWorldUnitsPerTexel[0] / bt->lightmapWorldUnitsPerLuxel; - shiftScaleV = bt->textureWorldUnitsPerTexel[1] / bt->lightmapWorldUnitsPerLuxel; - } - - tx.textureVecsTexelsPerWorldUnits[0][3] = bt->shift[0] + - DOT_PRODUCT( origin, tx.textureVecsTexelsPerWorldUnits[0] ); - tx.textureVecsTexelsPerWorldUnits[1][3] = bt->shift[1] + - DOT_PRODUCT( origin, tx.textureVecsTexelsPerWorldUnits[1] ); - - tx.lightmapVecsLuxelsPerWorldUnits[0][3] = shiftScaleU * bt->shift[0] + - DOT_PRODUCT( origin, tx.lightmapVecsLuxelsPerWorldUnits[0] ); - tx.lightmapVecsLuxelsPerWorldUnits[1][3] = shiftScaleV * bt->shift[1] + - DOT_PRODUCT( origin, tx.lightmapVecsLuxelsPerWorldUnits[1] ); - - tx.flags = bt->flags; - tx.texdata = FindOrCreateTexData( bt->name ); - - // find the texinfo - return FindOrCreateTexInfo( tx ); -} - - -void LoadSurfacePropFile( const char *pMaterialFilename ) -{ - FileHandle_t fp = g_pFileSystem->Open( pMaterialFilename, "rb" ); - - if ( fp == FILESYSTEM_INVALID_HANDLE ) - { - return; - } - - int len = g_pFileSystem->Size( fp ); - - char *pText = new char[len]; - g_pFileSystem->Read( pText, len, fp ); - g_pFileSystem->Close( fp ); - - physprops->ParseSurfaceData( pMaterialFilename, pText ); - - delete[] pText; -} -//----------------------------------------------------------------------------- -// Purpose: Loads the surface properties database into the physics DLL -//----------------------------------------------------------------------------- -void LoadSurfaceProperties( void ) -{ - CreateInterfaceFn physicsFactory = GetPhysicsFactory(); - if ( !physicsFactory ) - return; - - physprops = (IPhysicsSurfaceProps *)physicsFactory( VPHYSICS_SURFACEPROPS_INTERFACE_VERSION, NULL ); - - const char *SURFACEPROP_MANIFEST_FILE = "scripts/surfaceproperties_manifest.txt"; - KeyValues *manifest = new KeyValues( SURFACEPROP_MANIFEST_FILE ); - if ( manifest->LoadFromFile( g_pFileSystem, SURFACEPROP_MANIFEST_FILE, "GAME" ) ) - { - for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() ) - { - if ( !Q_stricmp( sub->GetName(), "file" ) ) - { - // Add - LoadSurfacePropFile( sub->GetString() ); - continue; - } - } - } - - manifest->deleteThis(); -} - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" +#include "utilmatlib.h" +#include "physdll.h" +#include +#include +#include "tier1/strtools.h" +#include "materialpatch.h" +#include "KeyValues.h" + +void LoadSurfaceProperties( void ); + +IPhysicsSurfaceProps *physprops = NULL; + +int nummiptex; +textureref_t textureref[MAX_MAP_TEXTURES]; + +bool g_bHasWater = false; + +extern qboolean onlyents; + +dtexdata_t *GetTexData( int index ) +{ + if ( index < 0 ) + return NULL; + Assert( !onlyents ); + return &dtexdata[ index ]; +} + +static qboolean StringIsTrue( const char *str ) +{ + if( Q_strcasecmp( str, "true" ) == 0 ) + { + return true; + } + if( Q_strcasecmp( str, "1" ) == 0 ) + { + return true; + } + return false; +} + +int FindMiptex (const char *name) +{ + int i; + MaterialSystemMaterial_t matID; + const char *propVal, *propVal2; + int opacity; + bool found; + + for (i=0 ; inormal, baseaxis[i*3]); + if (dot > best) + { + best = dot; + bestaxis = i; + } + } + + VectorCopy (baseaxis[bestaxis*3+1], xv); + VectorCopy (baseaxis[bestaxis*3+2], yv); +} + + + +int g_SurfaceProperties[MAX_MAP_TEXDATA]; + + +int GetSurfaceProperties( MaterialSystemMaterial_t matID, const char *pMatName ) +{ + const char *pPropString = NULL; + int surfaceIndex = -1; + + if ( physprops ) + { + pPropString = GetMaterialVar( matID, "$surfaceprop" ); + if ( pPropString ) + { + surfaceIndex = physprops->GetSurfaceIndex( pPropString ); + if ( surfaceIndex < 0 ) + { + Msg("Can't find surfaceprop %s for material %s, using default\n", pPropString, pMatName ); + surfaceIndex = physprops->GetSurfaceIndex( pPropString ); + surfaceIndex = physprops->GetSurfaceIndex( "default" ); + } + } + } + + return surfaceIndex; +} + +int GetSurfaceProperties2( MaterialSystemMaterial_t matID, const char *pMatName ) +{ + const char *pPropString = NULL; + int surfaceIndex = -1; + + if ( physprops ) + { + pPropString = GetMaterialVar( matID, "$surfaceprop2" ); + if ( pPropString ) + { + surfaceIndex = physprops->GetSurfaceIndex( pPropString ); + if ( surfaceIndex < 0 ) + { + Msg("Can't find surfacepropblend %s for material %s, using default\n", pPropString, pMatName ); + surfaceIndex = physprops->GetSurfaceIndex( "default" ); + } + } + else + { + // No surface property 2. + return -1; + } + } + + return surfaceIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds or adds a texdata for the specified name ( same as below except +// instead of finding the named texture, copies the settings from the passed +// in sourceTexture. ) +// Used for creation of one off .vmt files for water surface textures +// Input : *pName - texture name +// Output : int index into dtexdata array +//----------------------------------------------------------------------------- +int FindAliasedTexData( const char *pName_, dtexdata_t *sourceTexture ) +{ + char *pName = ( char * )_alloca( strlen( pName_ ) + 1 ); + strcpy( pName, pName_ ); + strlwr( pName ); + int i, output; + bool found; + dtexdata_t *pTexData; + MaterialSystemMaterial_t matID; + + for ( i = 0; i < numtexdata; i++ ) + { + if ( !strcmp( pName, TexDataStringTable_GetString( GetTexData( i )->nameStringTableID ) ) ) + return i; + } + + + output = numtexdata; + if ( numtexdata >= MAX_MAP_TEXDATA ) + { + Error( "Too many unique texture mappings, max = %d\n", MAX_MAP_TEXDATA ); + } + pTexData = GetTexData( output ); + numtexdata++; + + // Save the name of the material. + pTexData->nameStringTableID = TexDataStringTable_AddOrFindString( pName ); + + // Get the width, height, view_width, view_height, and reflectivity from the material system. + matID = FindOriginalMaterial( TexDataStringTable_GetString( sourceTexture->nameStringTableID ), &found, false ); + if( matID == MATERIAL_NOT_FOUND || (!found) ) + { + qprintf( "WARNING: material not found: \"%s\"\n", pName ); + return -1; + } + + GetMaterialDimensions( matID, &pTexData->width, &pTexData->height ); + pTexData->view_width = pTexData->width; // undone: what is this? + pTexData->view_height = pTexData->height; // undone: what is this? + + GetMaterialReflectivity( matID, pTexData->reflectivity.Base() ); + g_SurfaceProperties[output] = GetSurfaceProperties( matID, pName ); + + return output; +} + + +//----------------------------------------------------------------------------- +// Finds a texdata for the specified name, returns -1 if not found +//----------------------------------------------------------------------------- +int FindTexData( const char *pName ) +{ + // Make sure the texdata doesn't already exist. + for( int i = 0; i < numtexdata; i++ ) + { + char const *pTexDataName = TexDataStringTable_GetString( GetTexData( i )->nameStringTableID ); + if ( !Q_stricmp( pTexDataName, pName ) ) + return i; + } + return -1; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Finds or adds a texdata for the specified name +// Input : *pName - texture name +// Output : int index into dtexdata array +//----------------------------------------------------------------------------- +int FindOrCreateTexData( const char *pName_ ) +{ + char *pName = ( char * )_alloca( strlen( pName_ ) + 1 ); + strcpy( pName, pName_ ); + + int nOutput = FindTexData( pName ); + if ( nOutput >= 0 ) + return nOutput; + + // Didn't find it, add a new one + nOutput = numtexdata; + if ( numtexdata >= MAX_MAP_TEXDATA ) + { + Error( "Too many unique texture mappings, max = %d\n", MAX_MAP_TEXDATA ); + } + dtexdata_t *pTexData = GetTexData( nOutput ); + numtexdata++; + + // Save the name of the material. + pTexData->nameStringTableID = TexDataStringTable_AddOrFindString( pName ); + + // Get the width, height, view_width, view_height, and reflectivity from the material system. + bool bFound; + MaterialSystemMaterial_t matID = FindOriginalMaterial( pName, &bFound ); + if ( matID == MATERIAL_NOT_FOUND || (!bFound) ) + { + qprintf( "WARNING: material not found: \"%s\"\n", pName ); + return nOutput; + } + + GetMaterialDimensions( matID, &pTexData->width, &pTexData->height ); + pTexData->view_width = pTexData->width; // undone: what is this? + pTexData->view_height = pTexData->height; // undone: what is this? + + GetMaterialReflectivity( matID, pTexData->reflectivity.Base() ); + g_SurfaceProperties[nOutput] = GetSurfaceProperties( matID, pName ); + +#if 0 + Msg( "reflectivity: %f %f %f\n", + pTexData->reflectivity[0], + pTexData->reflectivity[1], + pTexData->reflectivity[2] ); +#endif + + return nOutput; +} + +int AddCloneTexData( dtexdata_t *pExistingTexData, char const *cloneTexDataName ) +{ + int existingIndex = pExistingTexData - GetTexData( 0 ); + dtexdata_t *pNewTexData = GetTexData( numtexdata ); + int newIndex = numtexdata; + numtexdata++; + + *pNewTexData = *pExistingTexData; + pNewTexData->nameStringTableID = TexDataStringTable_AddOrFindString( cloneTexDataName ); + g_SurfaceProperties[newIndex] = g_SurfaceProperties[existingIndex]; + + return newIndex; +} + + +//----------------------------------------------------------------------------- +// Finds a texinfo that exactly matches the passed in texinfo +//----------------------------------------------------------------------------- +int FindTexInfo( const texinfo_t &searchTexInfo ) +{ + for( int i = 0; i < texinfo.Count(); i++ ) + { + // Just an early-out for performance + if ( texinfo[i].texdata != searchTexInfo.texdata ) + continue; + + if ( !memcmp( &texinfo[i], &searchTexInfo, sizeof( texinfo_t ) ) ) + return i; + } + + return -1; +} + + +//----------------------------------------------------------------------------- +// Finds or creates a texinfo that exactly matches the passed in texinfo +//----------------------------------------------------------------------------- +int FindOrCreateTexInfo( const texinfo_t &searchTexInfo ) +{ + int i = FindTexInfo( searchTexInfo ); + if ( i >= 0 ) + return i; + + i = texinfo.AddToTail( searchTexInfo ); + + if ( onlyents ) + { + Error( "FindOrCreateTexInfo: Tried to create new texinfo during -onlyents compile!\nMust compile without -onlyents" ); + } + + return i; +} + +int TexinfoForBrushTexture (plane_t *plane, brush_texture_t *bt, const Vector& origin) +{ + Vector vecs[2]; + int sv, tv; + vec_t ang, sinv, cosv; + vec_t ns, nt; + texinfo_t tx; + int i, j; + + if (!bt->name[0]) + return 0; + + memset (&tx, 0, sizeof(tx)); + + // HLTOOLS - add support for texture vectors stored in the map file + if (g_nMapFileVersion < 220) + { + TextureAxisFromPlane(plane, vecs[0], vecs[1]); + } + + if (!bt->textureWorldUnitsPerTexel[0]) + bt->textureWorldUnitsPerTexel[0] = 1; + if (!bt->textureWorldUnitsPerTexel[1]) + bt->textureWorldUnitsPerTexel[1] = 1; + + + float shiftScaleU = 1.0f / 16.0f; + float shiftScaleV = 1.0f / 16.0f; + + if (g_nMapFileVersion < 220) + { + // rotate axis + if (bt->rotate == 0) + { sinv = 0 ; cosv = 1; } + else if (bt->rotate == 90) + { sinv = 1 ; cosv = 0; } + else if (bt->rotate == 180) + { sinv = 0 ; cosv = -1; } + else if (bt->rotate == 270) + { sinv = -1 ; cosv = 0; } + else + { + ang = bt->rotate / 180 * M_PI; + sinv = sin(ang); + cosv = cos(ang); + } + + if (vecs[0][0]) + sv = 0; + else if (vecs[0][1]) + sv = 1; + else + sv = 2; + + if (vecs[1][0]) + tv = 0; + else if (vecs[1][1]) + tv = 1; + else + tv = 2; + + for (i=0 ; i<2 ; i++) + { + ns = cosv * vecs[i][sv] - sinv * vecs[i][tv]; + nt = sinv * vecs[i][sv] + cosv * vecs[i][tv]; + vecs[i][sv] = ns; + vecs[i][tv] = nt; + } + + for (i=0 ; i<2 ; i++) + { + for (j=0 ; j<3 ; j++) + { + tx.textureVecsTexelsPerWorldUnits[i][j] = vecs[i][j] / bt->textureWorldUnitsPerTexel[i]; + tx.lightmapVecsLuxelsPerWorldUnits[i][j] = tx.textureVecsTexelsPerWorldUnits[i][j] / 16.0f; + } + } + } + else + { + tx.textureVecsTexelsPerWorldUnits[0][0] = bt->UAxis[0] / bt->textureWorldUnitsPerTexel[0]; + tx.textureVecsTexelsPerWorldUnits[0][1] = bt->UAxis[1] / bt->textureWorldUnitsPerTexel[0]; + tx.textureVecsTexelsPerWorldUnits[0][2] = bt->UAxis[2] / bt->textureWorldUnitsPerTexel[0]; + + tx.textureVecsTexelsPerWorldUnits[1][0] = bt->VAxis[0] / bt->textureWorldUnitsPerTexel[1]; + tx.textureVecsTexelsPerWorldUnits[1][1] = bt->VAxis[1] / bt->textureWorldUnitsPerTexel[1]; + tx.textureVecsTexelsPerWorldUnits[1][2] = bt->VAxis[2] / bt->textureWorldUnitsPerTexel[1]; + + tx.lightmapVecsLuxelsPerWorldUnits[0][0] = bt->UAxis[0] / bt->lightmapWorldUnitsPerLuxel; + tx.lightmapVecsLuxelsPerWorldUnits[0][1] = bt->UAxis[1] / bt->lightmapWorldUnitsPerLuxel; + tx.lightmapVecsLuxelsPerWorldUnits[0][2] = bt->UAxis[2] / bt->lightmapWorldUnitsPerLuxel; + + tx.lightmapVecsLuxelsPerWorldUnits[1][0] = bt->VAxis[0] / bt->lightmapWorldUnitsPerLuxel; + tx.lightmapVecsLuxelsPerWorldUnits[1][1] = bt->VAxis[1] / bt->lightmapWorldUnitsPerLuxel; + tx.lightmapVecsLuxelsPerWorldUnits[1][2] = bt->VAxis[2] / bt->lightmapWorldUnitsPerLuxel; + + shiftScaleU = bt->textureWorldUnitsPerTexel[0] / bt->lightmapWorldUnitsPerLuxel; + shiftScaleV = bt->textureWorldUnitsPerTexel[1] / bt->lightmapWorldUnitsPerLuxel; + } + + tx.textureVecsTexelsPerWorldUnits[0][3] = bt->shift[0] + + DOT_PRODUCT( origin, tx.textureVecsTexelsPerWorldUnits[0] ); + tx.textureVecsTexelsPerWorldUnits[1][3] = bt->shift[1] + + DOT_PRODUCT( origin, tx.textureVecsTexelsPerWorldUnits[1] ); + + tx.lightmapVecsLuxelsPerWorldUnits[0][3] = shiftScaleU * bt->shift[0] + + DOT_PRODUCT( origin, tx.lightmapVecsLuxelsPerWorldUnits[0] ); + tx.lightmapVecsLuxelsPerWorldUnits[1][3] = shiftScaleV * bt->shift[1] + + DOT_PRODUCT( origin, tx.lightmapVecsLuxelsPerWorldUnits[1] ); + + tx.flags = bt->flags; + tx.texdata = FindOrCreateTexData( bt->name ); + + // find the texinfo + return FindOrCreateTexInfo( tx ); +} + + +void LoadSurfacePropFile( const char *pMaterialFilename ) +{ + FileHandle_t fp = g_pFileSystem->Open( pMaterialFilename, "rb" ); + + if ( fp == FILESYSTEM_INVALID_HANDLE ) + { + return; + } + + int len = g_pFileSystem->Size( fp ); + + char *pText = new char[len]; + g_pFileSystem->Read( pText, len, fp ); + g_pFileSystem->Close( fp ); + + physprops->ParseSurfaceData( pMaterialFilename, pText ); + + delete[] pText; +} +//----------------------------------------------------------------------------- +// Purpose: Loads the surface properties database into the physics DLL +//----------------------------------------------------------------------------- +void LoadSurfaceProperties( void ) +{ + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); + if ( !physicsFactory ) + return; + + physprops = (IPhysicsSurfaceProps *)physicsFactory( VPHYSICS_SURFACEPROPS_INTERFACE_VERSION, NULL ); + + const char *SURFACEPROP_MANIFEST_FILE = "scripts/surfaceproperties_manifest.txt"; + KeyValues *manifest = new KeyValues( SURFACEPROP_MANIFEST_FILE ); + if ( manifest->LoadFromFile( g_pFileSystem, SURFACEPROP_MANIFEST_FILE, "GAME" ) ) + { + for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() ) + { + if ( !Q_stricmp( sub->GetName(), "file" ) ) + { + // Add + LoadSurfacePropFile( sub->GetString() ); + continue; + } + } + } + + manifest->deleteThis(); +} + + diff --git a/mp/src/utils/vbsp/tree.cpp b/mp/src/utils/vbsp/tree.cpp index 0f72844c..529e9838 100644 --- a/mp/src/utils/vbsp/tree.cpp +++ b/mp/src/utils/vbsp/tree.cpp @@ -1,207 +1,207 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#include "vbsp.h" - -extern int c_nodes; - -void RemovePortalFromNode (portal_t *portal, node_t *l); - -node_t *NodeForPoint (node_t *node, Vector& origin) -{ - plane_t *plane; - vec_t d; - - while (node->planenum != PLANENUM_LEAF) - { - plane = &g_MainMap->mapplanes[node->planenum]; - d = DotProduct (origin, plane->normal) - plane->dist; - if (d >= 0) - node = node->children[0]; - else - node = node->children[1]; - } - - return node; -} - - - -/* -============= -FreeTreePortals_r -============= -*/ -void FreeTreePortals_r (node_t *node) -{ - portal_t *p, *nextp; - int s; - - // free children - if (node->planenum != PLANENUM_LEAF) - { - FreeTreePortals_r (node->children[0]); - FreeTreePortals_r (node->children[1]); - } - - // free portals - for (p=node->portals ; p ; p=nextp) - { - s = (p->nodes[1] == node); - nextp = p->next[s]; - - RemovePortalFromNode (p, p->nodes[!s]); - FreePortal (p); - } - node->portals = NULL; -} - -/* -============= -FreeTree_r -============= -*/ -void FreeTree_r (node_t *node) -{ - face_t *f, *nextf; - - // free children - if (node->planenum != PLANENUM_LEAF) - { - FreeTree_r (node->children[0]); - FreeTree_r (node->children[1]); - } - - // free bspbrushes - FreeBrushList (node->brushlist); - - // free faces - for (f=node->faces ; f ; f=nextf) - { - nextf = f->next; - FreeFace (f); - } - - // free the node - if (node->volume) - FreeBrush (node->volume); - - if (numthreads == 1) - c_nodes--; - free (node); -} - - -/* -============= -FreeTree -============= -*/ -void FreeTree (tree_t *tree) -{ - if ( !tree ) - return; - - FreeTreePortals_r (tree->headnode); - FreeTree_r (tree->headnode); - free (tree); -} - -//=============================================================== - -void PrintTree_r (node_t *node, int depth) -{ - int i; - plane_t *plane; - bspbrush_t *bb; - - for (i=0 ; iplanenum == PLANENUM_LEAF) - { - if (!node->brushlist) - Msg ("NULL\n"); - else - { - for (bb=node->brushlist ; bb ; bb=bb->next) - Msg ("%i ", bb->original->brushnum); - Msg ("\n"); - } - return; - } - - plane = &g_MainMap->mapplanes[node->planenum]; - Msg ("#%i (%5.2f %5.2f %5.2f):%5.2f\n", node->planenum, - plane->normal[0], plane->normal[1], plane->normal[2], - plane->dist); - PrintTree_r (node->children[0], depth+1); - PrintTree_r (node->children[1], depth+1); -} - -/* -========================================================= - -NODES THAT DON'T SEPERATE DIFFERENT CONTENTS CAN BE PRUNED - -========================================================= -*/ - -int c_pruned; - -/* -============ -PruneNodes_r -============ -*/ -void PruneNodes_r (node_t *node) -{ - bspbrush_t *b, *next; - - if (node->planenum == PLANENUM_LEAF) - return; - PruneNodes_r (node->children[0]); - PruneNodes_r (node->children[1]); - - if ( (node->children[0]->contents & CONTENTS_SOLID) - && (node->children[1]->contents & CONTENTS_SOLID) ) - { - if (node->faces) - Error ("node->faces seperating CONTENTS_SOLID"); - if (node->children[0]->faces || node->children[1]->faces) - Error ("!node->faces with children"); - - // FIXME: free stuff - node->planenum = PLANENUM_LEAF; - node->contents = CONTENTS_SOLID; - - if (node->brushlist) - Error ("PruneNodes: node->brushlist"); - - // combine brush lists - node->brushlist = node->children[1]->brushlist; - - for (b=node->children[0]->brushlist ; b ; b=next) - { - next = b->next; - b->next = node->brushlist; - node->brushlist = b; - } - - c_pruned++; - } -} - - -void PruneNodes (node_t *node) -{ - qprintf ("--- PruneNodes ---\n"); - c_pruned = 0; - PruneNodes_r (node); - qprintf ("%5i pruned nodes\n", c_pruned); -} - -//=========================================================== +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "vbsp.h" + +extern int c_nodes; + +void RemovePortalFromNode (portal_t *portal, node_t *l); + +node_t *NodeForPoint (node_t *node, Vector& origin) +{ + plane_t *plane; + vec_t d; + + while (node->planenum != PLANENUM_LEAF) + { + plane = &g_MainMap->mapplanes[node->planenum]; + d = DotProduct (origin, plane->normal) - plane->dist; + if (d >= 0) + node = node->children[0]; + else + node = node->children[1]; + } + + return node; +} + + + +/* +============= +FreeTreePortals_r +============= +*/ +void FreeTreePortals_r (node_t *node) +{ + portal_t *p, *nextp; + int s; + + // free children + if (node->planenum != PLANENUM_LEAF) + { + FreeTreePortals_r (node->children[0]); + FreeTreePortals_r (node->children[1]); + } + + // free portals + for (p=node->portals ; p ; p=nextp) + { + s = (p->nodes[1] == node); + nextp = p->next[s]; + + RemovePortalFromNode (p, p->nodes[!s]); + FreePortal (p); + } + node->portals = NULL; +} + +/* +============= +FreeTree_r +============= +*/ +void FreeTree_r (node_t *node) +{ + face_t *f, *nextf; + + // free children + if (node->planenum != PLANENUM_LEAF) + { + FreeTree_r (node->children[0]); + FreeTree_r (node->children[1]); + } + + // free bspbrushes + FreeBrushList (node->brushlist); + + // free faces + for (f=node->faces ; f ; f=nextf) + { + nextf = f->next; + FreeFace (f); + } + + // free the node + if (node->volume) + FreeBrush (node->volume); + + if (numthreads == 1) + c_nodes--; + free (node); +} + + +/* +============= +FreeTree +============= +*/ +void FreeTree (tree_t *tree) +{ + if ( !tree ) + return; + + FreeTreePortals_r (tree->headnode); + FreeTree_r (tree->headnode); + free (tree); +} + +//=============================================================== + +void PrintTree_r (node_t *node, int depth) +{ + int i; + plane_t *plane; + bspbrush_t *bb; + + for (i=0 ; iplanenum == PLANENUM_LEAF) + { + if (!node->brushlist) + Msg ("NULL\n"); + else + { + for (bb=node->brushlist ; bb ; bb=bb->next) + Msg ("%i ", bb->original->brushnum); + Msg ("\n"); + } + return; + } + + plane = &g_MainMap->mapplanes[node->planenum]; + Msg ("#%i (%5.2f %5.2f %5.2f):%5.2f\n", node->planenum, + plane->normal[0], plane->normal[1], plane->normal[2], + plane->dist); + PrintTree_r (node->children[0], depth+1); + PrintTree_r (node->children[1], depth+1); +} + +/* +========================================================= + +NODES THAT DON'T SEPERATE DIFFERENT CONTENTS CAN BE PRUNED + +========================================================= +*/ + +int c_pruned; + +/* +============ +PruneNodes_r +============ +*/ +void PruneNodes_r (node_t *node) +{ + bspbrush_t *b, *next; + + if (node->planenum == PLANENUM_LEAF) + return; + PruneNodes_r (node->children[0]); + PruneNodes_r (node->children[1]); + + if ( (node->children[0]->contents & CONTENTS_SOLID) + && (node->children[1]->contents & CONTENTS_SOLID) ) + { + if (node->faces) + Error ("node->faces seperating CONTENTS_SOLID"); + if (node->children[0]->faces || node->children[1]->faces) + Error ("!node->faces with children"); + + // FIXME: free stuff + node->planenum = PLANENUM_LEAF; + node->contents = CONTENTS_SOLID; + + if (node->brushlist) + Error ("PruneNodes: node->brushlist"); + + // combine brush lists + node->brushlist = node->children[1]->brushlist; + + for (b=node->children[0]->brushlist ; b ; b=next) + { + next = b->next; + b->next = node->brushlist; + node->brushlist = b; + } + + c_pruned++; + } +} + + +void PruneNodes (node_t *node) +{ + qprintf ("--- PruneNodes ---\n"); + c_pruned = 0; + PruneNodes_r (node); + qprintf ("%5i pruned nodes\n", c_pruned); +} + +//=========================================================== diff --git a/mp/src/utils/vbsp/vbsp.cpp b/mp/src/utils/vbsp/vbsp.cpp index 1c7b7ec9..97a4df4c 100644 --- a/mp/src/utils/vbsp/vbsp.cpp +++ b/mp/src/utils/vbsp/vbsp.cpp @@ -1,1404 +1,1404 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: BSP Building tool -// -// $NoKeywords: $ -//=============================================================================// - -#include "vbsp.h" -#include "detail.h" -#include "physdll.h" -#include "utilmatlib.h" -#include "disp_vbsp.h" -#include "writebsp.h" -#include "tier0/icommandline.h" -#include "materialsystem/imaterialsystem.h" -#include "map.h" -#include "tools_minidump.h" -#include "materialsub.h" -#include "loadcmdline.h" -#include "byteswap.h" -#include "worldvertextransitionfixup.h" - -extern float g_maxLightmapDimension; - -char source[1024]; -char mapbase[ 64 ]; -char name[1024]; -char materialPath[1024]; - -vec_t microvolume = 1.0; -qboolean noprune; -qboolean glview; -qboolean nodetail; -qboolean fulldetail; -qboolean onlyents; -bool onlyprops; -qboolean nomerge; -qboolean nomergewater = false; -qboolean nowater; -qboolean nocsg; -qboolean noweld; -qboolean noshare; -qboolean nosubdiv; -qboolean notjunc; -qboolean noopt; -qboolean leaktest; -qboolean verboseentities; -qboolean dumpcollide = false; -qboolean g_bLowPriority = false; -qboolean g_DumpStaticProps = false; -qboolean g_bSkyVis = false; // skybox vis is off by default, toggle this to enable it -bool g_bLightIfMissing = false; -bool g_snapAxialPlanes = false; -bool g_bKeepStaleZip = false; -bool g_NodrawTriggers = false; -bool g_DisableWaterLighting = false; -bool g_bAllowDetailCracks = false; -bool g_bNoVirtualMesh = false; - -float g_defaultLuxelSize = DEFAULT_LUXEL_SIZE; -float g_luxelScale = 1.0f; -float g_minLuxelScale = 1.0f; -bool g_BumpAll = false; - -int g_nDXLevel = 0; // default dxlevel if you don't specify it on the command-line. -CUtlVector g_SkyAreas; -char outbase[32]; - -// HLTOOLS: Introduce these calcs to make the block algorithm proportional to the proper -// world coordinate extents. Assumes square spatial constraints. -#define BLOCKS_SIZE 1024 -#define BLOCKS_SPACE (COORD_EXTENT/BLOCKS_SIZE) -#define BLOCKX_OFFSET ((BLOCKS_SPACE/2)+1) -#define BLOCKY_OFFSET ((BLOCKS_SPACE/2)+1) -#define BLOCKS_MIN (-(BLOCKS_SPACE/2)) -#define BLOCKS_MAX ((BLOCKS_SPACE/2)-1) - -int block_xl = BLOCKS_MIN, block_xh = BLOCKS_MAX, block_yl = BLOCKS_MIN, block_yh = BLOCKS_MAX; - -int entity_num; - - -node_t *block_nodes[BLOCKS_SPACE+2][BLOCKS_SPACE+2]; - -//----------------------------------------------------------------------------- -// Assign occluder areas (must happen *after* the world model is processed) -//----------------------------------------------------------------------------- -void AssignOccluderAreas( tree_t *pTree ); -static void Compute3DSkyboxAreas( node_t *headnode, CUtlVector& areas ); - - -/* -============ -BlockTree - -============ -*/ -node_t *BlockTree (int xl, int yl, int xh, int yh) -{ - node_t *node; - Vector normal; - float dist; - int mid; - - if (xl == xh && yl == yh) - { - node = block_nodes[xl+BLOCKX_OFFSET][yl+BLOCKY_OFFSET]; - if (!node) - { // return an empty leaf - node = AllocNode (); - node->planenum = PLANENUM_LEAF; - node->contents = 0; //CONTENTS_SOLID; - return node; - } - return node; - } - - // create a seperator along the largest axis - node = AllocNode (); - - if (xh - xl > yh - yl) - { // split x axis - mid = xl + (xh-xl)/2 + 1; - normal[0] = 1; - normal[1] = 0; - normal[2] = 0; - dist = mid*BLOCKS_SIZE; - node->planenum = g_MainMap->FindFloatPlane (normal, dist); - node->children[0] = BlockTree ( mid, yl, xh, yh); - node->children[1] = BlockTree ( xl, yl, mid-1, yh); - } - else - { - mid = yl + (yh-yl)/2 + 1; - normal[0] = 0; - normal[1] = 1; - normal[2] = 0; - dist = mid*BLOCKS_SIZE; - node->planenum = g_MainMap->FindFloatPlane (normal, dist); - node->children[0] = BlockTree ( xl, mid, xh, yh); - node->children[1] = BlockTree ( xl, yl, xh, mid-1); - } - - return node; -} - -/* -============ -ProcessBlock_Thread - -============ -*/ -int brush_start, brush_end; -void ProcessBlock_Thread (int threadnum, int blocknum) -{ - int xblock, yblock; - Vector mins, maxs; - bspbrush_t *brushes; - tree_t *tree; - node_t *node; - - yblock = block_yl + blocknum / (block_xh-block_xl+1); - xblock = block_xl + blocknum % (block_xh-block_xl+1); - - qprintf ("############### block %2i,%2i ###############\n", xblock, yblock); - - mins[0] = xblock*BLOCKS_SIZE; - mins[1] = yblock*BLOCKS_SIZE; - mins[2] = MIN_COORD_INTEGER; - maxs[0] = (xblock+1)*BLOCKS_SIZE; - maxs[1] = (yblock+1)*BLOCKS_SIZE; - maxs[2] = MAX_COORD_INTEGER; - - // the makelist and chopbrushes could be cached between the passes... - brushes = MakeBspBrushList (brush_start, brush_end, mins, maxs, NO_DETAIL); - if (!brushes) - { - node = AllocNode (); - node->planenum = PLANENUM_LEAF; - node->contents = CONTENTS_SOLID; - block_nodes[xblock+BLOCKX_OFFSET][yblock+BLOCKY_OFFSET] = node; - return; - } - - FixupAreaportalWaterBrushes( brushes ); - if (!nocsg) - brushes = ChopBrushes (brushes); - - tree = BrushBSP (brushes, mins, maxs); - - block_nodes[xblock+BLOCKX_OFFSET][yblock+BLOCKY_OFFSET] = tree->headnode; -} - - -/* -============ -ProcessWorldModel - -============ -*/ -void SplitSubdividedFaces( node_t *headnode ); // garymcthack -void ProcessWorldModel (void) -{ - entity_t *e; - tree_t *tree = NULL; - qboolean leaked; - int optimize; - int start; - - e = &entities[entity_num]; - - brush_start = e->firstbrush; - brush_end = brush_start + e->numbrushes; - leaked = false; - - // - // perform per-block operations - // - if (block_xh * BLOCKS_SIZE > g_MainMap->map_maxs[0]) - { - block_xh = floor(g_MainMap->map_maxs[0]/BLOCKS_SIZE); - } - if ( (block_xl+1) * BLOCKS_SIZE < g_MainMap->map_mins[0]) - { - block_xl = floor(g_MainMap->map_mins[0]/BLOCKS_SIZE); - } - if (block_yh * BLOCKS_SIZE > g_MainMap->map_maxs[1]) - { - block_yh = floor(g_MainMap->map_maxs[1]/BLOCKS_SIZE); - } - if ( (block_yl+1) * BLOCKS_SIZE < g_MainMap->map_mins[1]) - { - block_yl = floor(g_MainMap->map_mins[1]/BLOCKS_SIZE); - } - - // HLTOOLS: updated to +/- MAX_COORD_INTEGER ( new world size limits / worldsize.h ) - if (block_xl < BLOCKS_MIN) - { - block_xl = BLOCKS_MIN; - } - if (block_yl < BLOCKS_MIN) - { - block_yl = BLOCKS_MIN; - } - if (block_xh > BLOCKS_MAX) - { - block_xh = BLOCKS_MAX; - } - if (block_yh > BLOCKS_MAX) - { - block_yh = BLOCKS_MAX; - } - - for (optimize = 0 ; optimize <= 1 ; optimize++) - { - qprintf ("--------------------------------------------\n"); - - RunThreadsOnIndividual ((block_xh-block_xl+1)*(block_yh-block_yl+1), - !verbose, ProcessBlock_Thread); - - // - // build the division tree - // oversizing the blocks guarantees that all the boundaries - // will also get nodes. - // - - qprintf ("--------------------------------------------\n"); - - tree = AllocTree (); - tree->headnode = BlockTree (block_xl-1, block_yl-1, block_xh+1, block_yh+1); - - tree->mins[0] = (block_xl)*BLOCKS_SIZE; - tree->mins[1] = (block_yl)*BLOCKS_SIZE; - tree->mins[2] = g_MainMap->map_mins[2] - 8; - - tree->maxs[0] = (block_xh+1)*BLOCKS_SIZE; - tree->maxs[1] = (block_yh+1)*BLOCKS_SIZE; - tree->maxs[2] = g_MainMap->map_maxs[2] + 8; - - // - // perform the global operations - // - - // make the portals/faces by traversing down to each empty leaf - MakeTreePortals (tree); - - if (FloodEntities (tree)) - { - // turns everthing outside into solid - FillOutside (tree->headnode); - } - else - { - Warning( ("**** leaked ****\n") ); - leaked = true; - LeakFile (tree); - if (leaktest) - { - Warning( ("--- MAP LEAKED ---\n") ); - exit (0); - } - } - - // mark the brush sides that actually turned into faces - MarkVisibleSides (tree, brush_start, brush_end, NO_DETAIL); - if (noopt || leaked) - break; - if (!optimize) - { - // If we are optimizing, free the tree. Next time we will construct it again, but - // we'll use the information in MarkVisibleSides() so we'll only split with planes that - // actually contribute renderable geometry - FreeTree (tree); - } - } - - FloodAreas (tree); - - RemoveAreaPortalBrushes_R( tree->headnode ); - - start = Plat_FloatTime(); - Msg("Building Faces..."); - // this turns portals with one solid side into faces - // it also subdivides each face if necessary to fit max lightmap dimensions - MakeFaces (tree->headnode); - Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); - - if (glview) - { - WriteGLView (tree, source); - } - - AssignOccluderAreas( tree ); - Compute3DSkyboxAreas( tree->headnode, g_SkyAreas ); - face_t *pLeafFaceList = NULL; - if ( !nodetail ) - { - pLeafFaceList = MergeDetailTree( tree, brush_start, brush_end ); - } - - start = Plat_FloatTime(); - - Msg("FixTjuncs...\n"); - - // This unifies the vertex list for all edges (splits collinear edges to remove t-junctions) - // It also welds the list of vertices out of each winding/portal and rounds nearly integer verts to integer - pLeafFaceList = FixTjuncs (tree->headnode, pLeafFaceList); - - // this merges all of the solid nodes that have separating planes - if (!noprune) - { - Msg("PruneNodes...\n"); - PruneNodes (tree->headnode); - } - -// Msg( "SplitSubdividedFaces...\n" ); -// SplitSubdividedFaces( tree->headnode ); - - Msg("WriteBSP...\n"); - WriteBSP (tree->headnode, pLeafFaceList); - Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); - - if (!leaked) - { - WritePortalFile (tree); - } - - FreeTree( tree ); - FreeLeafFaces( pLeafFaceList ); -} - -/* -============ -ProcessSubModel - -============ -*/ -void ProcessSubModel( ) -{ - entity_t *e; - int start, end; - tree_t *tree; - bspbrush_t *list; - Vector mins, maxs; - - e = &entities[entity_num]; - - start = e->firstbrush; - end = start + e->numbrushes; - - mins[0] = mins[1] = mins[2] = MIN_COORD_INTEGER; - maxs[0] = maxs[1] = maxs[2] = MAX_COORD_INTEGER; - list = MakeBspBrushList (start, end, mins, maxs, FULL_DETAIL); - - if (!nocsg) - list = ChopBrushes (list); - tree = BrushBSP (list, mins, maxs); - - // This would wind up crashing the engine because we'd have a negative leaf index in dmodel_t::headnode. - if ( tree->headnode->planenum == PLANENUM_LEAF ) - { - const char *pClassName = ValueForKey( e, "classname" ); - const char *pTargetName = ValueForKey( e, "targetname" ); - Error( "bmodel %d has no head node (class '%s', targetname '%s')", entity_num, pClassName, pTargetName ); - } - - MakeTreePortals (tree); - -#if DEBUG_BRUSHMODEL - if ( entity_num == DEBUG_BRUSHMODEL ) - WriteGLView( tree, "tree_all" ); -#endif - - MarkVisibleSides (tree, start, end, FULL_DETAIL); - MakeFaces (tree->headnode); - - FixTjuncs( tree->headnode, NULL ); - WriteBSP( tree->headnode, NULL ); - -#if DEBUG_BRUSHMODEL - if ( entity_num == DEBUG_BRUSHMODEL ) - { - WriteGLView( tree, "tree_vis" ); - WriteGLViewFaces( tree, "tree_faces" ); - } -#endif - - FreeTree (tree); -} - - -//----------------------------------------------------------------------------- -// Returns true if the entity is a func_occluder -//----------------------------------------------------------------------------- -bool IsFuncOccluder( int entity_num ) -{ - entity_t *mapent = &entities[entity_num]; - const char *pClassName = ValueForKey( mapent, "classname" ); - return (strcmp("func_occluder", pClassName) == 0); -} - - -//----------------------------------------------------------------------------- -// Computes the area of a brush's occluders -//----------------------------------------------------------------------------- -float ComputeOccluderBrushArea( mapbrush_t *pBrush ) -{ - float flArea = 0.0f; - for ( int j = 0; j < pBrush->numsides; ++j ) - { - side_t *pSide = &(pBrush->original_sides[j]); - - // Skip nodraw surfaces - if ( texinfo[pSide->texinfo].flags & SURF_NODRAW ) - continue; - - if ( !pSide->winding ) - continue; - - flArea += WindingArea( pSide->winding ); - } - - return flArea; -} - - -//----------------------------------------------------------------------------- -// Clips all occluder brushes against each other -//----------------------------------------------------------------------------- -static tree_t *ClipOccluderBrushes( ) -{ - // Create a list of all occluder brushes in the level - CUtlVector< mapbrush_t * > mapBrushes( 1024, 1024 ); - for ( entity_num=0; entity_num < g_MainMap->num_entities; ++entity_num ) - { - if (!IsFuncOccluder(entity_num)) - continue; - - entity_t *e = &entities[entity_num]; - int end = e->firstbrush + e->numbrushes; - int i; - for ( i = e->firstbrush; i < end; ++i ) - { - mapBrushes.AddToTail( &g_MainMap->mapbrushes[i] ); - } - } - - int nBrushCount = mapBrushes.Count(); - if ( nBrushCount == 0 ) - return NULL; - - Vector mins, maxs; - mins[0] = mins[1] = mins[2] = MIN_COORD_INTEGER; - maxs[0] = maxs[1] = maxs[2] = MAX_COORD_INTEGER; - - bspbrush_t *list = MakeBspBrushList( mapBrushes.Base(), nBrushCount, mins, maxs ); - - if (!nocsg) - list = ChopBrushes (list); - tree_t *tree = BrushBSP (list, mins, maxs); - MakeTreePortals (tree); - MarkVisibleSides (tree, mapBrushes.Base(), nBrushCount); - MakeFaces( tree->headnode ); - - // NOTE: This will output the occluder face vertices + planes - FixTjuncs( tree->headnode, NULL ); - - return tree; -} - - -//----------------------------------------------------------------------------- -// Generate a list of unique sides in the occluder tree -//----------------------------------------------------------------------------- -static void GenerateOccluderSideList( int nEntity, CUtlVector &occluderSides ) -{ - entity_t *e = &entities[nEntity]; - int end = e->firstbrush + e->numbrushes; - int i, j; - for ( i = e->firstbrush; i < end; ++i ) - { - mapbrush_t *mb = &g_MainMap->mapbrushes[i]; - for ( j = 0; j < mb->numsides; ++j ) - { - occluderSides.AddToTail( &(mb->original_sides[j]) ); - } - } -} - - -//----------------------------------------------------------------------------- -// Generate a list of unique faces in the occluder tree -//----------------------------------------------------------------------------- -static void GenerateOccluderFaceList( node_t *pOccluderNode, CUtlVector &occluderFaces ) -{ - if (pOccluderNode->planenum == PLANENUM_LEAF) - return; - - for ( face_t *f=pOccluderNode->faces ; f ; f = f->next ) - { - occluderFaces.AddToTail( f ); - } - - GenerateOccluderFaceList( pOccluderNode->children[0], occluderFaces ); - GenerateOccluderFaceList( pOccluderNode->children[1], occluderFaces ); -} - - -//----------------------------------------------------------------------------- -// For occluder area assignment -//----------------------------------------------------------------------------- -struct OccluderInfo_t -{ - int m_nOccluderEntityIndex; -}; - -static CUtlVector< OccluderInfo_t > g_OccluderInfo; - - -//----------------------------------------------------------------------------- -// Emits occluder brushes -//----------------------------------------------------------------------------- -static void EmitOccluderBrushes() -{ - char str[64]; - - g_OccluderData.RemoveAll(); - g_OccluderPolyData.RemoveAll(); - g_OccluderVertexIndices.RemoveAll(); - - tree_t *pOccluderTree = ClipOccluderBrushes(); - if (!pOccluderTree) - return; - - CUtlVector faceList( 1024, 1024 ); - CUtlVector sideList( 1024, 1024 ); - GenerateOccluderFaceList( pOccluderTree->headnode, faceList ); - -#ifdef _DEBUG - int *pEmitted = (int*)stackalloc( faceList.Count() * sizeof(int) ); - memset( pEmitted, 0, faceList.Count() * sizeof(int) ); -#endif - - for ( entity_num=1; entity_num < num_entities; ++entity_num ) - { - if (!IsFuncOccluder(entity_num)) - continue; - - // Output only those parts of the occluder tree which are a part of the brush - int nOccluder = g_OccluderData.AddToTail(); - doccluderdata_t &occluderData = g_OccluderData[ nOccluder ]; - occluderData.firstpoly = g_OccluderPolyData.Count(); - occluderData.mins.Init( FLT_MAX, FLT_MAX, FLT_MAX ); - occluderData.maxs.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX ); - occluderData.flags = 0; - occluderData.area = -1; - - // NOTE: If you change the algorithm by which occluder numbers are allocated, - // then you must also change FixupOnlyEntsOccluderEntities() below - sprintf (str, "%i", nOccluder); - SetKeyValue (&entities[entity_num], "occludernumber", str); - - int nIndex = g_OccluderInfo.AddToTail(); - g_OccluderInfo[nIndex].m_nOccluderEntityIndex = entity_num; - - sideList.RemoveAll(); - GenerateOccluderSideList( entity_num, sideList ); - for ( int i = faceList.Count(); --i >= 0; ) - { - // Skip nodraw surfaces, but not triggers that have been marked as nodraw - face_t *f = faceList[i]; - if ( ( texinfo[f->texinfo].flags & SURF_NODRAW ) && - (( texinfo[f->texinfo].flags & SURF_TRIGGER ) == 0 ) ) - continue; - - // Only emit faces that appear in the side list of the occluder - for ( int j = sideList.Count(); --j >= 0; ) - { - if ( sideList[j] != f->originalface ) - continue; - - if ( f->numpoints < 3 ) - continue; - - // not a final face - Assert ( !f->merged && !f->split[0] && !f->split[1] ); - -#ifdef _DEBUG - Assert( !pEmitted[i] ); - pEmitted[i] = entity_num; -#endif - - int k = g_OccluderPolyData.AddToTail(); - doccluderpolydata_t *pOccluderPoly = &g_OccluderPolyData[k]; - - pOccluderPoly->planenum = f->planenum; - pOccluderPoly->vertexcount = f->numpoints; - pOccluderPoly->firstvertexindex = g_OccluderVertexIndices.Count(); - for( k = 0; k < f->numpoints; ++k ) - { - g_OccluderVertexIndices.AddToTail( f->vertexnums[k] ); - - const Vector &p = dvertexes[f->vertexnums[k]].point; - VectorMin( occluderData.mins, p, occluderData.mins ); - VectorMax( occluderData.maxs, p, occluderData.maxs ); - } - - break; - } - } - - occluderData.polycount = g_OccluderPolyData.Count() - occluderData.firstpoly; - - // Mark this brush as not having brush geometry so it won't be re-emitted with a brush model - entities[entity_num].numbrushes = 0; - } - - FreeTree( pOccluderTree ); -} - - -//----------------------------------------------------------------------------- -// Set occluder area -//----------------------------------------------------------------------------- -void SetOccluderArea( int nOccluder, int nArea, int nEntityNum ) -{ - if ( g_OccluderData[nOccluder].area <= 0 ) - { - g_OccluderData[nOccluder].area = nArea; - } - else if ( (nArea != 0) && (g_OccluderData[nOccluder].area != nArea) ) - { - const char *pTargetName = ValueForKey( &entities[nEntityNum], "targetname" ); - if (!pTargetName) - { - pTargetName = ""; - } - Warning("Occluder \"%s\" straddles multiple areas. This is invalid!\n", pTargetName ); - } -} - - -//----------------------------------------------------------------------------- -// Assign occluder areas (must happen *after* the world model is processed) -//----------------------------------------------------------------------------- -void AssignAreaToOccluder( int nOccluder, tree_t *pTree, bool bCrossAreaPortals ) -{ - int nFirstPoly = g_OccluderData[nOccluder].firstpoly; - int nEntityNum = g_OccluderInfo[nOccluder].m_nOccluderEntityIndex; - for ( int j = 0; j < g_OccluderData[nOccluder].polycount; ++j ) - { - doccluderpolydata_t *pOccluderPoly = &g_OccluderPolyData[nFirstPoly + j]; - int nFirstVertex = pOccluderPoly->firstvertexindex; - for ( int k = 0; k < pOccluderPoly->vertexcount; ++k ) - { - int nVertexIndex = g_OccluderVertexIndices[nFirstVertex + k]; - node_t *pNode = NodeForPoint( pTree->headnode, dvertexes[ nVertexIndex ].point ); - - SetOccluderArea( nOccluder, pNode->area, nEntityNum ); - - int nOtherSideIndex; - portal_t *pPortal; - for ( pPortal = pNode->portals; pPortal; pPortal = pPortal->next[!nOtherSideIndex] ) - { - nOtherSideIndex = (pPortal->nodes[0] == pNode) ? 1 : 0; - if (!pPortal->onnode) - continue; // edge of world - - // Don't cross over area portals for the area check - if ((!bCrossAreaPortals) && pPortal->nodes[nOtherSideIndex]->contents & CONTENTS_AREAPORTAL) - continue; - - int nAdjacentArea = pPortal->nodes[nOtherSideIndex] ? pPortal->nodes[nOtherSideIndex]->area : 0; - SetOccluderArea( nOccluder, nAdjacentArea, nEntityNum ); - } - } - } -} - - -//----------------------------------------------------------------------------- -// Assign occluder areas (must happen *after* the world model is processed) -//----------------------------------------------------------------------------- -void AssignOccluderAreas( tree_t *pTree ) -{ - for ( int i = 0; i < g_OccluderData.Count(); ++i ) - { - AssignAreaToOccluder( i, pTree, false ); - - // This can only have happened if the only valid portal out leads into an areaportal - if ( g_OccluderData[i].area <= 0 ) - { - AssignAreaToOccluder( i, pTree, true ); - } - } -} - - - -//----------------------------------------------------------------------------- -// Make sure the func_occluders have the appropriate data set -//----------------------------------------------------------------------------- -void FixupOnlyEntsOccluderEntities() -{ - char str[64]; - int nOccluder = 0; - for ( entity_num=1; entity_num < num_entities; ++entity_num ) - { - if (!IsFuncOccluder(entity_num)) - continue; - - // NOTE: If you change the algorithm by which occluder numbers are allocated above, - // then you must also change this - sprintf (str, "%i", nOccluder); - SetKeyValue (&entities[entity_num], "occludernumber", str); - ++nOccluder; - } -} - - -void MarkNoDynamicShadowSides() -{ - for ( int iSide=0; iSide < g_MainMap->nummapbrushsides; iSide++ ) - { - g_MainMap->brushsides[iSide].m_bDynamicShadowsEnabled = true; - } - - for ( int i=0; i < g_NoDynamicShadowSides.Count(); i++ ) - { - int brushSideID = g_NoDynamicShadowSides[i]; - - // Find the side with this ID. - for ( int iSide=0; iSide < g_MainMap->nummapbrushsides; iSide++ ) - { - if ( g_MainMap->brushsides[iSide].id == brushSideID ) - g_MainMap->brushsides[iSide].m_bDynamicShadowsEnabled = false; - } - } -} - -//----------------------------------------------------------------------------- -// Compute the 3D skybox areas -//----------------------------------------------------------------------------- -static void Compute3DSkyboxAreas( node_t *headnode, CUtlVector& areas ) -{ - for (int i = 0; i < g_MainMap->num_entities; ++i) - { - char* pEntity = ValueForKey(&entities[i], "classname"); - if (!strcmp(pEntity, "sky_camera")) - { - // Found a 3D skybox camera, get a leaf that lies in it - node_t *pLeaf = PointInLeaf( headnode, entities[i].origin ); - if (pLeaf->contents & CONTENTS_SOLID) - { - Error ("Error! Entity sky_camera in solid volume! at %.1f %.1f %.1f\n", entities[i].origin.x, entities[i].origin.y, entities[i].origin.z); - } - areas.AddToTail( pLeaf->area ); - } - } -} - -bool Is3DSkyboxArea( int area ) -{ - for ( int i = g_SkyAreas.Count(); --i >=0; ) - { - if ( g_SkyAreas[i] == area ) - return true; - } - return false; -} - - -/* -============ -ProcessModels -============ -*/ -void ProcessModels (void) -{ - BeginBSPFile (); - - // Mark sides that have no dynamic shadows. - MarkNoDynamicShadowSides(); - - // emit the displacement surfaces - EmitInitialDispInfos(); - - // Clip occluder brushes against each other, - // Remove them from the list of models to process below - EmitOccluderBrushes( ); - - for ( entity_num=0; entity_num < num_entities; ++entity_num ) - { - entity_t *pEntity = &entities[entity_num]; - if ( !pEntity->numbrushes ) - continue; - - qprintf ("############### model %i ###############\n", nummodels); - - BeginModel (); - - if (entity_num == 0) - { - ProcessWorldModel(); - } - else - { - ProcessSubModel( ); - } - - EndModel (); - - if (!verboseentities) - { - verbose = false; // don't bother printing submodels - } - } - - // Turn the skybox into a cubemap in case we don't build env_cubemap textures. - Cubemap_CreateDefaultCubemaps(); - EndBSPFile (); -} - - -void LoadPhysicsDLL( void ) -{ - PhysicsDLLPath( "vphysics.dll" ); -} - - -void PrintCommandLine( int argc, char **argv ) -{ - Warning( "Command line: " ); - for ( int z=0; z < argc; z++ ) - { - Warning( "\"%s\" ", argv[z] ); - } - Warning( "\n\n" ); -} - - -int RunVBSP( int argc, char **argv ) -{ - int i; - double start, end; - char path[1024]; - - CommandLine()->CreateCmdLine( argc, argv ); - MathLib_Init( 2.2f, 2.2f, 0.0f, OVERBRIGHT, false, false, false, false ); - InstallSpewFunction(); - SpewActivate( "developer", 1 ); - - CmdLib_InitFileSystem( argv[ argc-1 ] ); - - Q_StripExtension( ExpandArg( argv[ argc-1 ] ), source, sizeof( source ) ); - Q_FileBase( source, mapbase, sizeof( mapbase ) ); - strlwr( mapbase ); - - LoadCmdLineFromFile( argc, argv, mapbase, "vbsp" ); - - Msg( "Valve Software - vbsp.exe (%s)\n", __DATE__ ); - - for (i=1 ; i : Override the VPROJECT environment variable.\n" - " -game : Same as -vproject.\n" - "\n" ); - - if ( verbose ) - { - Warning( - "Other options :\n" - " -novconfig : Don't bring up graphical UI on vproject errors.\n" - " -threads : Control the number of threads vbsp uses (defaults to the # of\n" - " processors on your machine).\n" - " -verboseentities: If -v is on, this disables verbose output for submodels.\n" - " -noweld : Don't join face vertices together.\n" - " -nocsg : Don't chop out intersecting brush areas.\n" - " -noshare : Emit unique face edges instead of sharing them.\n" - " -notjunc : Don't fixup t-junctions.\n" - " -noopt : By default, vbsp removes the 'outer shell' of the map, which\n" - " are all the faces you can't see because you can never get\n" - " outside the map. -noopt disables this behaviour.\n" - " -noprune : Don't prune neighboring solid nodes.\n" - " -nomerge : Don't merge together chopped faces on nodes.\n" - " -nomergewater: Don't merge together chopped faces on water.\n" - " -nosubdiv : Don't subdivide faces for lightmapping.\n" - " -micro <#> : vbsp will warn when brushes are output with a volume less\n" - " than this number (default: 1.0).\n" - " -fulldetail : Mark all detail geometry as normal geometry (so all detail\n" - " geometry will affect visibility).\n" - " -leaktest : Stop processing the map if a leak is detected. Whether or not\n" - " this flag is set, a leak file will be written out at\n" - " .lin, and it can be imported into Hammer.\n" - " -bumpall : Force all surfaces to be bump mapped.\n" - " -snapaxial : Snap axial planes to integer coordinates.\n" - " -block # # : Control the grid size mins that vbsp chops the level on.\n" - " -blocks # # # # : Enter the mins and maxs for the grid size vbsp uses.\n" - " -dumpstaticprops: Dump static props to staticprop*.txt\n" - " -dumpcollide : Write files with collision info.\n" - " -forceskyvis : Enable vis calculations in 3d skybox leaves\n" - " -luxelscale # : Scale all lightmaps by this amount (default: 1.0).\n" - " -minluxelscale #: No luxel scale will be lower than this amount (default: 1.0).\n" - " -lightifmissing : Force lightmaps to be generated for all surfaces even if\n" - " they don't need lightmaps.\n" - " -keepstalezip : Keep the BSP's zip files intact but regenerate everything\n" - " else.\n" - " -virtualdispphysics : Use virtual (not precomputed) displacement collision models\n" - " -xbox : Enable mandatory xbox options\n" - " -x360 : Generate Xbox360 version of vsp\n" - " -nox360 : Disable generation Xbox360 version of vsp (default)\n" - " -replacematerials : Substitute materials according to materialsub.txt in content\\maps\n" - " -FullMinidumps : Write large minidumps on crash.\n" - ); - } - - DeleteCmdLine( argc, argv ); - CmdLib_Cleanup(); - CmdLib_Exit( 1 ); - } - - start = Plat_FloatTime(); - - // Run in the background? - if( g_bLowPriority ) - { - SetLowPriority(); - } - - if( ( g_nDXLevel != 0 ) && ( g_nDXLevel < 80 ) ) - { - g_BumpAll = false; - } - - if( g_luxelScale == 1.0f ) - { - if ( g_nDXLevel == 70 ) - { - g_luxelScale = 4.0f; - } - } - - ThreadSetDefault (); - numthreads = 1; // multiple threads aren't helping... - - // Setup the logfile. - char logFile[512]; - _snprintf( logFile, sizeof(logFile), "%s.log", source ); - SetSpewFunctionLogFile( logFile ); - - LoadPhysicsDLL(); - LoadSurfaceProperties(); - -#if 0 - Msg( "qdir: %s This is the the path of the initial source file \n", qdir ); - Msg( "gamedir: %s This is the base engine + mod-specific game dir (e.g. d:/tf2/mytfmod/) \n", gamedir ); - Msg( "basegamedir: %s This is the base engine + base game directory (e.g. e:/hl2/hl2/, or d:/tf2/tf2/ )\n", basegamedir ); -#endif - - sprintf( materialPath, "%smaterials", gamedir ); - InitMaterialSystem( materialPath, CmdLib_GetFileSystemFactory() ); - Msg( "materialPath: %s\n", materialPath ); - - // delete portal and line files - sprintf (path, "%s.prt", source); - remove (path); - sprintf (path, "%s.lin", source); - remove (path); - - strcpy (name, ExpandArg (argv[i])); - - const char *pszExtension = V_GetFileExtension( name ); - if ( !pszExtension ) - { - V_SetExtension( name, ".vmm", sizeof( name ) ); - if ( !FileExists( name ) ) - { - V_SetExtension( name, ".vmf", sizeof( name ) ); - } - } - - char platformBSPFileName[1024]; - GetPlatformMapPath( source, platformBSPFileName, g_nDXLevel, 1024 ); - - // if we're combining materials, load the script file - if ( g_ReplaceMaterials ) - { - LoadMaterialReplacementKeys( gamedir, mapbase ); - } - - // - // if onlyents, just grab the entites and resave - // - if (onlyents) - { - LoadBSPFile (platformBSPFileName); - num_entities = 0; - // Clear out the cubemap samples since they will be reparsed even with -onlyents - g_nCubemapSamples = 0; - - // Mark as stale since the lighting could be screwed with new ents. - AddBufferToPak( GetPakFile(), "stale.txt", "stale", strlen( "stale" ) + 1, false ); - - LoadMapFile (name); - SetModelNumbers (); - SetLightStyles (); - - // NOTE: If we ever precompute lighting for static props in - // vrad, EmitStaticProps should be removed here - - // Emit static props found in the .vmf file - EmitStaticProps(); - - // NOTE: Don't deal with detail props here, it blows away lighting - - // Recompute the skybox - ComputeBoundsNoSkybox(); - - // Make sure that we have a water lod control eneity if we have water in the map. - EnsurePresenceOfWaterLODControlEntity(); - - // Make sure the func_occluders have the appropriate data set - FixupOnlyEntsOccluderEntities(); - - // Doing this here because stuff abov may filter out entities - UnparseEntities (); - - WriteBSPFile (platformBSPFileName); - } - else if (onlyprops) - { - // In the only props case, deal with static + detail props only - LoadBSPFile (platformBSPFileName); - - LoadMapFile(name); - SetModelNumbers(); - SetLightStyles(); - - // Emit static props found in the .vmf file - EmitStaticProps(); - - // Place detail props found in .vmf and based on material properties - LoadEmitDetailObjectDictionary( gamedir ); - EmitDetailObjects(); - - WriteBSPFile (platformBSPFileName); - } - else - { - // - // start from scratch - // - - // Load just the file system from the bsp - if( g_bKeepStaleZip && FileExists( platformBSPFileName ) ) - { - LoadBSPFile_FileSystemOnly (platformBSPFileName); - // Mark as stale since the lighting could be screwed with new ents. - AddBufferToPak( GetPakFile(), "stale.txt", "stale", strlen( "stale" ) + 1, false ); - } - - LoadMapFile (name); - WorldVertexTransitionFixup(); - if( ( g_nDXLevel == 0 ) || ( g_nDXLevel >= 70 ) ) - { - Cubemap_FixupBrushSidesMaterials(); - Cubemap_AttachDefaultCubemapToSpecularSides(); - Cubemap_AddUnreferencedCubemaps(); - } - SetModelNumbers (); - SetLightStyles (); - LoadEmitDetailObjectDictionary( gamedir ); - ProcessModels (); - } - - end = Plat_FloatTime(); - - char str[512]; - GetHourMinuteSecondsString( (int)( end - start ), str, sizeof( str ) ); - Msg( "%s elapsed\n", str ); - - DeleteCmdLine( argc, argv ); - ReleasePakFileLumps(); - DeleteMaterialReplacementKeys(); - ShutdownMaterialSystem(); - CmdLib_Cleanup(); - return 0; -} - - -/* -============= -main -============ -*/ -int main (int argc, char **argv) -{ - // Install an exception handler. - SetupDefaultToolsMinidumpHandler(); - return RunVBSP( argc, argv ); -} - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: BSP Building tool +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "detail.h" +#include "physdll.h" +#include "utilmatlib.h" +#include "disp_vbsp.h" +#include "writebsp.h" +#include "tier0/icommandline.h" +#include "materialsystem/imaterialsystem.h" +#include "map.h" +#include "tools_minidump.h" +#include "materialsub.h" +#include "loadcmdline.h" +#include "byteswap.h" +#include "worldvertextransitionfixup.h" + +extern float g_maxLightmapDimension; + +char source[1024]; +char mapbase[ 64 ]; +char name[1024]; +char materialPath[1024]; + +vec_t microvolume = 1.0; +qboolean noprune; +qboolean glview; +qboolean nodetail; +qboolean fulldetail; +qboolean onlyents; +bool onlyprops; +qboolean nomerge; +qboolean nomergewater = false; +qboolean nowater; +qboolean nocsg; +qboolean noweld; +qboolean noshare; +qboolean nosubdiv; +qboolean notjunc; +qboolean noopt; +qboolean leaktest; +qboolean verboseentities; +qboolean dumpcollide = false; +qboolean g_bLowPriority = false; +qboolean g_DumpStaticProps = false; +qboolean g_bSkyVis = false; // skybox vis is off by default, toggle this to enable it +bool g_bLightIfMissing = false; +bool g_snapAxialPlanes = false; +bool g_bKeepStaleZip = false; +bool g_NodrawTriggers = false; +bool g_DisableWaterLighting = false; +bool g_bAllowDetailCracks = false; +bool g_bNoVirtualMesh = false; + +float g_defaultLuxelSize = DEFAULT_LUXEL_SIZE; +float g_luxelScale = 1.0f; +float g_minLuxelScale = 1.0f; +bool g_BumpAll = false; + +int g_nDXLevel = 0; // default dxlevel if you don't specify it on the command-line. +CUtlVector g_SkyAreas; +char outbase[32]; + +// HLTOOLS: Introduce these calcs to make the block algorithm proportional to the proper +// world coordinate extents. Assumes square spatial constraints. +#define BLOCKS_SIZE 1024 +#define BLOCKS_SPACE (COORD_EXTENT/BLOCKS_SIZE) +#define BLOCKX_OFFSET ((BLOCKS_SPACE/2)+1) +#define BLOCKY_OFFSET ((BLOCKS_SPACE/2)+1) +#define BLOCKS_MIN (-(BLOCKS_SPACE/2)) +#define BLOCKS_MAX ((BLOCKS_SPACE/2)-1) + +int block_xl = BLOCKS_MIN, block_xh = BLOCKS_MAX, block_yl = BLOCKS_MIN, block_yh = BLOCKS_MAX; + +int entity_num; + + +node_t *block_nodes[BLOCKS_SPACE+2][BLOCKS_SPACE+2]; + +//----------------------------------------------------------------------------- +// Assign occluder areas (must happen *after* the world model is processed) +//----------------------------------------------------------------------------- +void AssignOccluderAreas( tree_t *pTree ); +static void Compute3DSkyboxAreas( node_t *headnode, CUtlVector& areas ); + + +/* +============ +BlockTree + +============ +*/ +node_t *BlockTree (int xl, int yl, int xh, int yh) +{ + node_t *node; + Vector normal; + float dist; + int mid; + + if (xl == xh && yl == yh) + { + node = block_nodes[xl+BLOCKX_OFFSET][yl+BLOCKY_OFFSET]; + if (!node) + { // return an empty leaf + node = AllocNode (); + node->planenum = PLANENUM_LEAF; + node->contents = 0; //CONTENTS_SOLID; + return node; + } + return node; + } + + // create a seperator along the largest axis + node = AllocNode (); + + if (xh - xl > yh - yl) + { // split x axis + mid = xl + (xh-xl)/2 + 1; + normal[0] = 1; + normal[1] = 0; + normal[2] = 0; + dist = mid*BLOCKS_SIZE; + node->planenum = g_MainMap->FindFloatPlane (normal, dist); + node->children[0] = BlockTree ( mid, yl, xh, yh); + node->children[1] = BlockTree ( xl, yl, mid-1, yh); + } + else + { + mid = yl + (yh-yl)/2 + 1; + normal[0] = 0; + normal[1] = 1; + normal[2] = 0; + dist = mid*BLOCKS_SIZE; + node->planenum = g_MainMap->FindFloatPlane (normal, dist); + node->children[0] = BlockTree ( xl, mid, xh, yh); + node->children[1] = BlockTree ( xl, yl, xh, mid-1); + } + + return node; +} + +/* +============ +ProcessBlock_Thread + +============ +*/ +int brush_start, brush_end; +void ProcessBlock_Thread (int threadnum, int blocknum) +{ + int xblock, yblock; + Vector mins, maxs; + bspbrush_t *brushes; + tree_t *tree; + node_t *node; + + yblock = block_yl + blocknum / (block_xh-block_xl+1); + xblock = block_xl + blocknum % (block_xh-block_xl+1); + + qprintf ("############### block %2i,%2i ###############\n", xblock, yblock); + + mins[0] = xblock*BLOCKS_SIZE; + mins[1] = yblock*BLOCKS_SIZE; + mins[2] = MIN_COORD_INTEGER; + maxs[0] = (xblock+1)*BLOCKS_SIZE; + maxs[1] = (yblock+1)*BLOCKS_SIZE; + maxs[2] = MAX_COORD_INTEGER; + + // the makelist and chopbrushes could be cached between the passes... + brushes = MakeBspBrushList (brush_start, brush_end, mins, maxs, NO_DETAIL); + if (!brushes) + { + node = AllocNode (); + node->planenum = PLANENUM_LEAF; + node->contents = CONTENTS_SOLID; + block_nodes[xblock+BLOCKX_OFFSET][yblock+BLOCKY_OFFSET] = node; + return; + } + + FixupAreaportalWaterBrushes( brushes ); + if (!nocsg) + brushes = ChopBrushes (brushes); + + tree = BrushBSP (brushes, mins, maxs); + + block_nodes[xblock+BLOCKX_OFFSET][yblock+BLOCKY_OFFSET] = tree->headnode; +} + + +/* +============ +ProcessWorldModel + +============ +*/ +void SplitSubdividedFaces( node_t *headnode ); // garymcthack +void ProcessWorldModel (void) +{ + entity_t *e; + tree_t *tree = NULL; + qboolean leaked; + int optimize; + int start; + + e = &entities[entity_num]; + + brush_start = e->firstbrush; + brush_end = brush_start + e->numbrushes; + leaked = false; + + // + // perform per-block operations + // + if (block_xh * BLOCKS_SIZE > g_MainMap->map_maxs[0]) + { + block_xh = floor(g_MainMap->map_maxs[0]/BLOCKS_SIZE); + } + if ( (block_xl+1) * BLOCKS_SIZE < g_MainMap->map_mins[0]) + { + block_xl = floor(g_MainMap->map_mins[0]/BLOCKS_SIZE); + } + if (block_yh * BLOCKS_SIZE > g_MainMap->map_maxs[1]) + { + block_yh = floor(g_MainMap->map_maxs[1]/BLOCKS_SIZE); + } + if ( (block_yl+1) * BLOCKS_SIZE < g_MainMap->map_mins[1]) + { + block_yl = floor(g_MainMap->map_mins[1]/BLOCKS_SIZE); + } + + // HLTOOLS: updated to +/- MAX_COORD_INTEGER ( new world size limits / worldsize.h ) + if (block_xl < BLOCKS_MIN) + { + block_xl = BLOCKS_MIN; + } + if (block_yl < BLOCKS_MIN) + { + block_yl = BLOCKS_MIN; + } + if (block_xh > BLOCKS_MAX) + { + block_xh = BLOCKS_MAX; + } + if (block_yh > BLOCKS_MAX) + { + block_yh = BLOCKS_MAX; + } + + for (optimize = 0 ; optimize <= 1 ; optimize++) + { + qprintf ("--------------------------------------------\n"); + + RunThreadsOnIndividual ((block_xh-block_xl+1)*(block_yh-block_yl+1), + !verbose, ProcessBlock_Thread); + + // + // build the division tree + // oversizing the blocks guarantees that all the boundaries + // will also get nodes. + // + + qprintf ("--------------------------------------------\n"); + + tree = AllocTree (); + tree->headnode = BlockTree (block_xl-1, block_yl-1, block_xh+1, block_yh+1); + + tree->mins[0] = (block_xl)*BLOCKS_SIZE; + tree->mins[1] = (block_yl)*BLOCKS_SIZE; + tree->mins[2] = g_MainMap->map_mins[2] - 8; + + tree->maxs[0] = (block_xh+1)*BLOCKS_SIZE; + tree->maxs[1] = (block_yh+1)*BLOCKS_SIZE; + tree->maxs[2] = g_MainMap->map_maxs[2] + 8; + + // + // perform the global operations + // + + // make the portals/faces by traversing down to each empty leaf + MakeTreePortals (tree); + + if (FloodEntities (tree)) + { + // turns everthing outside into solid + FillOutside (tree->headnode); + } + else + { + Warning( ("**** leaked ****\n") ); + leaked = true; + LeakFile (tree); + if (leaktest) + { + Warning( ("--- MAP LEAKED ---\n") ); + exit (0); + } + } + + // mark the brush sides that actually turned into faces + MarkVisibleSides (tree, brush_start, brush_end, NO_DETAIL); + if (noopt || leaked) + break; + if (!optimize) + { + // If we are optimizing, free the tree. Next time we will construct it again, but + // we'll use the information in MarkVisibleSides() so we'll only split with planes that + // actually contribute renderable geometry + FreeTree (tree); + } + } + + FloodAreas (tree); + + RemoveAreaPortalBrushes_R( tree->headnode ); + + start = Plat_FloatTime(); + Msg("Building Faces..."); + // this turns portals with one solid side into faces + // it also subdivides each face if necessary to fit max lightmap dimensions + MakeFaces (tree->headnode); + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); + + if (glview) + { + WriteGLView (tree, source); + } + + AssignOccluderAreas( tree ); + Compute3DSkyboxAreas( tree->headnode, g_SkyAreas ); + face_t *pLeafFaceList = NULL; + if ( !nodetail ) + { + pLeafFaceList = MergeDetailTree( tree, brush_start, brush_end ); + } + + start = Plat_FloatTime(); + + Msg("FixTjuncs...\n"); + + // This unifies the vertex list for all edges (splits collinear edges to remove t-junctions) + // It also welds the list of vertices out of each winding/portal and rounds nearly integer verts to integer + pLeafFaceList = FixTjuncs (tree->headnode, pLeafFaceList); + + // this merges all of the solid nodes that have separating planes + if (!noprune) + { + Msg("PruneNodes...\n"); + PruneNodes (tree->headnode); + } + +// Msg( "SplitSubdividedFaces...\n" ); +// SplitSubdividedFaces( tree->headnode ); + + Msg("WriteBSP...\n"); + WriteBSP (tree->headnode, pLeafFaceList); + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); + + if (!leaked) + { + WritePortalFile (tree); + } + + FreeTree( tree ); + FreeLeafFaces( pLeafFaceList ); +} + +/* +============ +ProcessSubModel + +============ +*/ +void ProcessSubModel( ) +{ + entity_t *e; + int start, end; + tree_t *tree; + bspbrush_t *list; + Vector mins, maxs; + + e = &entities[entity_num]; + + start = e->firstbrush; + end = start + e->numbrushes; + + mins[0] = mins[1] = mins[2] = MIN_COORD_INTEGER; + maxs[0] = maxs[1] = maxs[2] = MAX_COORD_INTEGER; + list = MakeBspBrushList (start, end, mins, maxs, FULL_DETAIL); + + if (!nocsg) + list = ChopBrushes (list); + tree = BrushBSP (list, mins, maxs); + + // This would wind up crashing the engine because we'd have a negative leaf index in dmodel_t::headnode. + if ( tree->headnode->planenum == PLANENUM_LEAF ) + { + const char *pClassName = ValueForKey( e, "classname" ); + const char *pTargetName = ValueForKey( e, "targetname" ); + Error( "bmodel %d has no head node (class '%s', targetname '%s')", entity_num, pClassName, pTargetName ); + } + + MakeTreePortals (tree); + +#if DEBUG_BRUSHMODEL + if ( entity_num == DEBUG_BRUSHMODEL ) + WriteGLView( tree, "tree_all" ); +#endif + + MarkVisibleSides (tree, start, end, FULL_DETAIL); + MakeFaces (tree->headnode); + + FixTjuncs( tree->headnode, NULL ); + WriteBSP( tree->headnode, NULL ); + +#if DEBUG_BRUSHMODEL + if ( entity_num == DEBUG_BRUSHMODEL ) + { + WriteGLView( tree, "tree_vis" ); + WriteGLViewFaces( tree, "tree_faces" ); + } +#endif + + FreeTree (tree); +} + + +//----------------------------------------------------------------------------- +// Returns true if the entity is a func_occluder +//----------------------------------------------------------------------------- +bool IsFuncOccluder( int entity_num ) +{ + entity_t *mapent = &entities[entity_num]; + const char *pClassName = ValueForKey( mapent, "classname" ); + return (strcmp("func_occluder", pClassName) == 0); +} + + +//----------------------------------------------------------------------------- +// Computes the area of a brush's occluders +//----------------------------------------------------------------------------- +float ComputeOccluderBrushArea( mapbrush_t *pBrush ) +{ + float flArea = 0.0f; + for ( int j = 0; j < pBrush->numsides; ++j ) + { + side_t *pSide = &(pBrush->original_sides[j]); + + // Skip nodraw surfaces + if ( texinfo[pSide->texinfo].flags & SURF_NODRAW ) + continue; + + if ( !pSide->winding ) + continue; + + flArea += WindingArea( pSide->winding ); + } + + return flArea; +} + + +//----------------------------------------------------------------------------- +// Clips all occluder brushes against each other +//----------------------------------------------------------------------------- +static tree_t *ClipOccluderBrushes( ) +{ + // Create a list of all occluder brushes in the level + CUtlVector< mapbrush_t * > mapBrushes( 1024, 1024 ); + for ( entity_num=0; entity_num < g_MainMap->num_entities; ++entity_num ) + { + if (!IsFuncOccluder(entity_num)) + continue; + + entity_t *e = &entities[entity_num]; + int end = e->firstbrush + e->numbrushes; + int i; + for ( i = e->firstbrush; i < end; ++i ) + { + mapBrushes.AddToTail( &g_MainMap->mapbrushes[i] ); + } + } + + int nBrushCount = mapBrushes.Count(); + if ( nBrushCount == 0 ) + return NULL; + + Vector mins, maxs; + mins[0] = mins[1] = mins[2] = MIN_COORD_INTEGER; + maxs[0] = maxs[1] = maxs[2] = MAX_COORD_INTEGER; + + bspbrush_t *list = MakeBspBrushList( mapBrushes.Base(), nBrushCount, mins, maxs ); + + if (!nocsg) + list = ChopBrushes (list); + tree_t *tree = BrushBSP (list, mins, maxs); + MakeTreePortals (tree); + MarkVisibleSides (tree, mapBrushes.Base(), nBrushCount); + MakeFaces( tree->headnode ); + + // NOTE: This will output the occluder face vertices + planes + FixTjuncs( tree->headnode, NULL ); + + return tree; +} + + +//----------------------------------------------------------------------------- +// Generate a list of unique sides in the occluder tree +//----------------------------------------------------------------------------- +static void GenerateOccluderSideList( int nEntity, CUtlVector &occluderSides ) +{ + entity_t *e = &entities[nEntity]; + int end = e->firstbrush + e->numbrushes; + int i, j; + for ( i = e->firstbrush; i < end; ++i ) + { + mapbrush_t *mb = &g_MainMap->mapbrushes[i]; + for ( j = 0; j < mb->numsides; ++j ) + { + occluderSides.AddToTail( &(mb->original_sides[j]) ); + } + } +} + + +//----------------------------------------------------------------------------- +// Generate a list of unique faces in the occluder tree +//----------------------------------------------------------------------------- +static void GenerateOccluderFaceList( node_t *pOccluderNode, CUtlVector &occluderFaces ) +{ + if (pOccluderNode->planenum == PLANENUM_LEAF) + return; + + for ( face_t *f=pOccluderNode->faces ; f ; f = f->next ) + { + occluderFaces.AddToTail( f ); + } + + GenerateOccluderFaceList( pOccluderNode->children[0], occluderFaces ); + GenerateOccluderFaceList( pOccluderNode->children[1], occluderFaces ); +} + + +//----------------------------------------------------------------------------- +// For occluder area assignment +//----------------------------------------------------------------------------- +struct OccluderInfo_t +{ + int m_nOccluderEntityIndex; +}; + +static CUtlVector< OccluderInfo_t > g_OccluderInfo; + + +//----------------------------------------------------------------------------- +// Emits occluder brushes +//----------------------------------------------------------------------------- +static void EmitOccluderBrushes() +{ + char str[64]; + + g_OccluderData.RemoveAll(); + g_OccluderPolyData.RemoveAll(); + g_OccluderVertexIndices.RemoveAll(); + + tree_t *pOccluderTree = ClipOccluderBrushes(); + if (!pOccluderTree) + return; + + CUtlVector faceList( 1024, 1024 ); + CUtlVector sideList( 1024, 1024 ); + GenerateOccluderFaceList( pOccluderTree->headnode, faceList ); + +#ifdef _DEBUG + int *pEmitted = (int*)stackalloc( faceList.Count() * sizeof(int) ); + memset( pEmitted, 0, faceList.Count() * sizeof(int) ); +#endif + + for ( entity_num=1; entity_num < num_entities; ++entity_num ) + { + if (!IsFuncOccluder(entity_num)) + continue; + + // Output only those parts of the occluder tree which are a part of the brush + int nOccluder = g_OccluderData.AddToTail(); + doccluderdata_t &occluderData = g_OccluderData[ nOccluder ]; + occluderData.firstpoly = g_OccluderPolyData.Count(); + occluderData.mins.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + occluderData.maxs.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX ); + occluderData.flags = 0; + occluderData.area = -1; + + // NOTE: If you change the algorithm by which occluder numbers are allocated, + // then you must also change FixupOnlyEntsOccluderEntities() below + sprintf (str, "%i", nOccluder); + SetKeyValue (&entities[entity_num], "occludernumber", str); + + int nIndex = g_OccluderInfo.AddToTail(); + g_OccluderInfo[nIndex].m_nOccluderEntityIndex = entity_num; + + sideList.RemoveAll(); + GenerateOccluderSideList( entity_num, sideList ); + for ( int i = faceList.Count(); --i >= 0; ) + { + // Skip nodraw surfaces, but not triggers that have been marked as nodraw + face_t *f = faceList[i]; + if ( ( texinfo[f->texinfo].flags & SURF_NODRAW ) && + (( texinfo[f->texinfo].flags & SURF_TRIGGER ) == 0 ) ) + continue; + + // Only emit faces that appear in the side list of the occluder + for ( int j = sideList.Count(); --j >= 0; ) + { + if ( sideList[j] != f->originalface ) + continue; + + if ( f->numpoints < 3 ) + continue; + + // not a final face + Assert ( !f->merged && !f->split[0] && !f->split[1] ); + +#ifdef _DEBUG + Assert( !pEmitted[i] ); + pEmitted[i] = entity_num; +#endif + + int k = g_OccluderPolyData.AddToTail(); + doccluderpolydata_t *pOccluderPoly = &g_OccluderPolyData[k]; + + pOccluderPoly->planenum = f->planenum; + pOccluderPoly->vertexcount = f->numpoints; + pOccluderPoly->firstvertexindex = g_OccluderVertexIndices.Count(); + for( k = 0; k < f->numpoints; ++k ) + { + g_OccluderVertexIndices.AddToTail( f->vertexnums[k] ); + + const Vector &p = dvertexes[f->vertexnums[k]].point; + VectorMin( occluderData.mins, p, occluderData.mins ); + VectorMax( occluderData.maxs, p, occluderData.maxs ); + } + + break; + } + } + + occluderData.polycount = g_OccluderPolyData.Count() - occluderData.firstpoly; + + // Mark this brush as not having brush geometry so it won't be re-emitted with a brush model + entities[entity_num].numbrushes = 0; + } + + FreeTree( pOccluderTree ); +} + + +//----------------------------------------------------------------------------- +// Set occluder area +//----------------------------------------------------------------------------- +void SetOccluderArea( int nOccluder, int nArea, int nEntityNum ) +{ + if ( g_OccluderData[nOccluder].area <= 0 ) + { + g_OccluderData[nOccluder].area = nArea; + } + else if ( (nArea != 0) && (g_OccluderData[nOccluder].area != nArea) ) + { + const char *pTargetName = ValueForKey( &entities[nEntityNum], "targetname" ); + if (!pTargetName) + { + pTargetName = ""; + } + Warning("Occluder \"%s\" straddles multiple areas. This is invalid!\n", pTargetName ); + } +} + + +//----------------------------------------------------------------------------- +// Assign occluder areas (must happen *after* the world model is processed) +//----------------------------------------------------------------------------- +void AssignAreaToOccluder( int nOccluder, tree_t *pTree, bool bCrossAreaPortals ) +{ + int nFirstPoly = g_OccluderData[nOccluder].firstpoly; + int nEntityNum = g_OccluderInfo[nOccluder].m_nOccluderEntityIndex; + for ( int j = 0; j < g_OccluderData[nOccluder].polycount; ++j ) + { + doccluderpolydata_t *pOccluderPoly = &g_OccluderPolyData[nFirstPoly + j]; + int nFirstVertex = pOccluderPoly->firstvertexindex; + for ( int k = 0; k < pOccluderPoly->vertexcount; ++k ) + { + int nVertexIndex = g_OccluderVertexIndices[nFirstVertex + k]; + node_t *pNode = NodeForPoint( pTree->headnode, dvertexes[ nVertexIndex ].point ); + + SetOccluderArea( nOccluder, pNode->area, nEntityNum ); + + int nOtherSideIndex; + portal_t *pPortal; + for ( pPortal = pNode->portals; pPortal; pPortal = pPortal->next[!nOtherSideIndex] ) + { + nOtherSideIndex = (pPortal->nodes[0] == pNode) ? 1 : 0; + if (!pPortal->onnode) + continue; // edge of world + + // Don't cross over area portals for the area check + if ((!bCrossAreaPortals) && pPortal->nodes[nOtherSideIndex]->contents & CONTENTS_AREAPORTAL) + continue; + + int nAdjacentArea = pPortal->nodes[nOtherSideIndex] ? pPortal->nodes[nOtherSideIndex]->area : 0; + SetOccluderArea( nOccluder, nAdjacentArea, nEntityNum ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Assign occluder areas (must happen *after* the world model is processed) +//----------------------------------------------------------------------------- +void AssignOccluderAreas( tree_t *pTree ) +{ + for ( int i = 0; i < g_OccluderData.Count(); ++i ) + { + AssignAreaToOccluder( i, pTree, false ); + + // This can only have happened if the only valid portal out leads into an areaportal + if ( g_OccluderData[i].area <= 0 ) + { + AssignAreaToOccluder( i, pTree, true ); + } + } +} + + + +//----------------------------------------------------------------------------- +// Make sure the func_occluders have the appropriate data set +//----------------------------------------------------------------------------- +void FixupOnlyEntsOccluderEntities() +{ + char str[64]; + int nOccluder = 0; + for ( entity_num=1; entity_num < num_entities; ++entity_num ) + { + if (!IsFuncOccluder(entity_num)) + continue; + + // NOTE: If you change the algorithm by which occluder numbers are allocated above, + // then you must also change this + sprintf (str, "%i", nOccluder); + SetKeyValue (&entities[entity_num], "occludernumber", str); + ++nOccluder; + } +} + + +void MarkNoDynamicShadowSides() +{ + for ( int iSide=0; iSide < g_MainMap->nummapbrushsides; iSide++ ) + { + g_MainMap->brushsides[iSide].m_bDynamicShadowsEnabled = true; + } + + for ( int i=0; i < g_NoDynamicShadowSides.Count(); i++ ) + { + int brushSideID = g_NoDynamicShadowSides[i]; + + // Find the side with this ID. + for ( int iSide=0; iSide < g_MainMap->nummapbrushsides; iSide++ ) + { + if ( g_MainMap->brushsides[iSide].id == brushSideID ) + g_MainMap->brushsides[iSide].m_bDynamicShadowsEnabled = false; + } + } +} + +//----------------------------------------------------------------------------- +// Compute the 3D skybox areas +//----------------------------------------------------------------------------- +static void Compute3DSkyboxAreas( node_t *headnode, CUtlVector& areas ) +{ + for (int i = 0; i < g_MainMap->num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!strcmp(pEntity, "sky_camera")) + { + // Found a 3D skybox camera, get a leaf that lies in it + node_t *pLeaf = PointInLeaf( headnode, entities[i].origin ); + if (pLeaf->contents & CONTENTS_SOLID) + { + Error ("Error! Entity sky_camera in solid volume! at %.1f %.1f %.1f\n", entities[i].origin.x, entities[i].origin.y, entities[i].origin.z); + } + areas.AddToTail( pLeaf->area ); + } + } +} + +bool Is3DSkyboxArea( int area ) +{ + for ( int i = g_SkyAreas.Count(); --i >=0; ) + { + if ( g_SkyAreas[i] == area ) + return true; + } + return false; +} + + +/* +============ +ProcessModels +============ +*/ +void ProcessModels (void) +{ + BeginBSPFile (); + + // Mark sides that have no dynamic shadows. + MarkNoDynamicShadowSides(); + + // emit the displacement surfaces + EmitInitialDispInfos(); + + // Clip occluder brushes against each other, + // Remove them from the list of models to process below + EmitOccluderBrushes( ); + + for ( entity_num=0; entity_num < num_entities; ++entity_num ) + { + entity_t *pEntity = &entities[entity_num]; + if ( !pEntity->numbrushes ) + continue; + + qprintf ("############### model %i ###############\n", nummodels); + + BeginModel (); + + if (entity_num == 0) + { + ProcessWorldModel(); + } + else + { + ProcessSubModel( ); + } + + EndModel (); + + if (!verboseentities) + { + verbose = false; // don't bother printing submodels + } + } + + // Turn the skybox into a cubemap in case we don't build env_cubemap textures. + Cubemap_CreateDefaultCubemaps(); + EndBSPFile (); +} + + +void LoadPhysicsDLL( void ) +{ + PhysicsDLLPath( "vphysics.dll" ); +} + + +void PrintCommandLine( int argc, char **argv ) +{ + Warning( "Command line: " ); + for ( int z=0; z < argc; z++ ) + { + Warning( "\"%s\" ", argv[z] ); + } + Warning( "\n\n" ); +} + + +int RunVBSP( int argc, char **argv ) +{ + int i; + double start, end; + char path[1024]; + + CommandLine()->CreateCmdLine( argc, argv ); + MathLib_Init( 2.2f, 2.2f, 0.0f, OVERBRIGHT, false, false, false, false ); + InstallSpewFunction(); + SpewActivate( "developer", 1 ); + + CmdLib_InitFileSystem( argv[ argc-1 ] ); + + Q_StripExtension( ExpandArg( argv[ argc-1 ] ), source, sizeof( source ) ); + Q_FileBase( source, mapbase, sizeof( mapbase ) ); + strlwr( mapbase ); + + LoadCmdLineFromFile( argc, argv, mapbase, "vbsp" ); + + Msg( "Valve Software - vbsp.exe (%s)\n", __DATE__ ); + + for (i=1 ; i : Override the VPROJECT environment variable.\n" + " -game : Same as -vproject.\n" + "\n" ); + + if ( verbose ) + { + Warning( + "Other options :\n" + " -novconfig : Don't bring up graphical UI on vproject errors.\n" + " -threads : Control the number of threads vbsp uses (defaults to the # of\n" + " processors on your machine).\n" + " -verboseentities: If -v is on, this disables verbose output for submodels.\n" + " -noweld : Don't join face vertices together.\n" + " -nocsg : Don't chop out intersecting brush areas.\n" + " -noshare : Emit unique face edges instead of sharing them.\n" + " -notjunc : Don't fixup t-junctions.\n" + " -noopt : By default, vbsp removes the 'outer shell' of the map, which\n" + " are all the faces you can't see because you can never get\n" + " outside the map. -noopt disables this behaviour.\n" + " -noprune : Don't prune neighboring solid nodes.\n" + " -nomerge : Don't merge together chopped faces on nodes.\n" + " -nomergewater: Don't merge together chopped faces on water.\n" + " -nosubdiv : Don't subdivide faces for lightmapping.\n" + " -micro <#> : vbsp will warn when brushes are output with a volume less\n" + " than this number (default: 1.0).\n" + " -fulldetail : Mark all detail geometry as normal geometry (so all detail\n" + " geometry will affect visibility).\n" + " -leaktest : Stop processing the map if a leak is detected. Whether or not\n" + " this flag is set, a leak file will be written out at\n" + " .lin, and it can be imported into Hammer.\n" + " -bumpall : Force all surfaces to be bump mapped.\n" + " -snapaxial : Snap axial planes to integer coordinates.\n" + " -block # # : Control the grid size mins that vbsp chops the level on.\n" + " -blocks # # # # : Enter the mins and maxs for the grid size vbsp uses.\n" + " -dumpstaticprops: Dump static props to staticprop*.txt\n" + " -dumpcollide : Write files with collision info.\n" + " -forceskyvis : Enable vis calculations in 3d skybox leaves\n" + " -luxelscale # : Scale all lightmaps by this amount (default: 1.0).\n" + " -minluxelscale #: No luxel scale will be lower than this amount (default: 1.0).\n" + " -lightifmissing : Force lightmaps to be generated for all surfaces even if\n" + " they don't need lightmaps.\n" + " -keepstalezip : Keep the BSP's zip files intact but regenerate everything\n" + " else.\n" + " -virtualdispphysics : Use virtual (not precomputed) displacement collision models\n" + " -xbox : Enable mandatory xbox options\n" + " -x360 : Generate Xbox360 version of vsp\n" + " -nox360 : Disable generation Xbox360 version of vsp (default)\n" + " -replacematerials : Substitute materials according to materialsub.txt in content\\maps\n" + " -FullMinidumps : Write large minidumps on crash.\n" + ); + } + + DeleteCmdLine( argc, argv ); + CmdLib_Cleanup(); + CmdLib_Exit( 1 ); + } + + start = Plat_FloatTime(); + + // Run in the background? + if( g_bLowPriority ) + { + SetLowPriority(); + } + + if( ( g_nDXLevel != 0 ) && ( g_nDXLevel < 80 ) ) + { + g_BumpAll = false; + } + + if( g_luxelScale == 1.0f ) + { + if ( g_nDXLevel == 70 ) + { + g_luxelScale = 4.0f; + } + } + + ThreadSetDefault (); + numthreads = 1; // multiple threads aren't helping... + + // Setup the logfile. + char logFile[512]; + _snprintf( logFile, sizeof(logFile), "%s.log", source ); + SetSpewFunctionLogFile( logFile ); + + LoadPhysicsDLL(); + LoadSurfaceProperties(); + +#if 0 + Msg( "qdir: %s This is the the path of the initial source file \n", qdir ); + Msg( "gamedir: %s This is the base engine + mod-specific game dir (e.g. d:/tf2/mytfmod/) \n", gamedir ); + Msg( "basegamedir: %s This is the base engine + base game directory (e.g. e:/hl2/hl2/, or d:/tf2/tf2/ )\n", basegamedir ); +#endif + + sprintf( materialPath, "%smaterials", gamedir ); + InitMaterialSystem( materialPath, CmdLib_GetFileSystemFactory() ); + Msg( "materialPath: %s\n", materialPath ); + + // delete portal and line files + sprintf (path, "%s.prt", source); + remove (path); + sprintf (path, "%s.lin", source); + remove (path); + + strcpy (name, ExpandArg (argv[i])); + + const char *pszExtension = V_GetFileExtension( name ); + if ( !pszExtension ) + { + V_SetExtension( name, ".vmm", sizeof( name ) ); + if ( !FileExists( name ) ) + { + V_SetExtension( name, ".vmf", sizeof( name ) ); + } + } + + char platformBSPFileName[1024]; + GetPlatformMapPath( source, platformBSPFileName, g_nDXLevel, 1024 ); + + // if we're combining materials, load the script file + if ( g_ReplaceMaterials ) + { + LoadMaterialReplacementKeys( gamedir, mapbase ); + } + + // + // if onlyents, just grab the entites and resave + // + if (onlyents) + { + LoadBSPFile (platformBSPFileName); + num_entities = 0; + // Clear out the cubemap samples since they will be reparsed even with -onlyents + g_nCubemapSamples = 0; + + // Mark as stale since the lighting could be screwed with new ents. + AddBufferToPak( GetPakFile(), "stale.txt", "stale", strlen( "stale" ) + 1, false ); + + LoadMapFile (name); + SetModelNumbers (); + SetLightStyles (); + + // NOTE: If we ever precompute lighting for static props in + // vrad, EmitStaticProps should be removed here + + // Emit static props found in the .vmf file + EmitStaticProps(); + + // NOTE: Don't deal with detail props here, it blows away lighting + + // Recompute the skybox + ComputeBoundsNoSkybox(); + + // Make sure that we have a water lod control eneity if we have water in the map. + EnsurePresenceOfWaterLODControlEntity(); + + // Make sure the func_occluders have the appropriate data set + FixupOnlyEntsOccluderEntities(); + + // Doing this here because stuff abov may filter out entities + UnparseEntities (); + + WriteBSPFile (platformBSPFileName); + } + else if (onlyprops) + { + // In the only props case, deal with static + detail props only + LoadBSPFile (platformBSPFileName); + + LoadMapFile(name); + SetModelNumbers(); + SetLightStyles(); + + // Emit static props found in the .vmf file + EmitStaticProps(); + + // Place detail props found in .vmf and based on material properties + LoadEmitDetailObjectDictionary( gamedir ); + EmitDetailObjects(); + + WriteBSPFile (platformBSPFileName); + } + else + { + // + // start from scratch + // + + // Load just the file system from the bsp + if( g_bKeepStaleZip && FileExists( platformBSPFileName ) ) + { + LoadBSPFile_FileSystemOnly (platformBSPFileName); + // Mark as stale since the lighting could be screwed with new ents. + AddBufferToPak( GetPakFile(), "stale.txt", "stale", strlen( "stale" ) + 1, false ); + } + + LoadMapFile (name); + WorldVertexTransitionFixup(); + if( ( g_nDXLevel == 0 ) || ( g_nDXLevel >= 70 ) ) + { + Cubemap_FixupBrushSidesMaterials(); + Cubemap_AttachDefaultCubemapToSpecularSides(); + Cubemap_AddUnreferencedCubemaps(); + } + SetModelNumbers (); + SetLightStyles (); + LoadEmitDetailObjectDictionary( gamedir ); + ProcessModels (); + } + + end = Plat_FloatTime(); + + char str[512]; + GetHourMinuteSecondsString( (int)( end - start ), str, sizeof( str ) ); + Msg( "%s elapsed\n", str ); + + DeleteCmdLine( argc, argv ); + ReleasePakFileLumps(); + DeleteMaterialReplacementKeys(); + ShutdownMaterialSystem(); + CmdLib_Cleanup(); + return 0; +} + + +/* +============= +main +============ +*/ +int main (int argc, char **argv) +{ + // Install an exception handler. + SetupDefaultToolsMinidumpHandler(); + return RunVBSP( argc, argv ); +} + + diff --git a/mp/src/utils/vbsp/vbsp.h b/mp/src/utils/vbsp/vbsp.h index 9b994d38..689bbaa8 100644 --- a/mp/src/utils/vbsp/vbsp.h +++ b/mp/src/utils/vbsp/vbsp.h @@ -1,657 +1,657 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#if !defined( VBSP_H ) -#define VBSP_H - - -#include "cmdlib.h" -#include "mathlib/vector.h" -#include "scriplib.h" -#include "polylib.h" -#include "threads.h" -#include "bsplib.h" -#include "qfiles.h" -#include "utilmatlib.h" -#include "ChunkFile.h" - -#ifdef WIN32 -#pragma warning( disable: 4706 ) -#endif - -class CUtlBuffer; - -#define MAX_BRUSH_SIDES 128 -#define CLIP_EPSILON 0.1 - -#define TEXINFO_NODE -1 // side is allready on a node - -// this will output glview files for the given brushmodel. Brushmodel 1 is the world, 2 is the first brush entity, etc. -#define DEBUG_BRUSHMODEL 0 - -struct portal_t; -struct node_t; - -struct plane_t : public dplane_t -{ - plane_t *hash_chain; - - plane_t() { normal.Init(); } -}; - - -struct brush_texture_t -{ - Vector UAxis; - Vector VAxis; - vec_t shift[2]; - vec_t rotate; - vec_t textureWorldUnitsPerTexel[2]; - vec_t lightmapWorldUnitsPerLuxel; - char name[TEXTURE_NAME_LENGTH]; - int flags; - - brush_texture_t() : UAxis(0,0,0), VAxis(0,0,0) {} -}; - -struct mapdispinfo_t; - -struct side_t -{ - int planenum; - int texinfo; - mapdispinfo_t *pMapDisp; - - winding_t *winding; - side_t *original; // bspbrush_t sides will reference the mapbrush_t sides - int contents; // from miptex - int surf; // from miptex - qboolean visible; // choose visble planes first - qboolean tested; // this plane allready checked as a split - qboolean bevel; // don't ever use for bsp splitting - - side_t *next; - int origIndex; - int id; // This is the unique id generated by worldcraft for this side. - unsigned int smoothingGroups; - CUtlVector aOverlayIds; // List of overlays that reside on this side. - CUtlVector aWaterOverlayIds; // List of water overlays that reside on this side. - bool m_bDynamicShadowsEnabled; // Goes into dface_t::SetDynamicShadowsEnabled(). -}; - -struct mapbrush_t -{ - int entitynum; - int brushnum; - int id; // The unique ID of this brush in the editor, used for reporting errors. - int contents; - Vector mins, maxs; - int numsides; - side_t *original_sides; -}; - -#define PLANENUM_LEAF -1 - -#define MAXEDGES 32 - -struct face_t -{ - int id; - - face_t *next; // on node - - // the chain of faces off of a node can be merged or split, - // but each face_t along the way will remain in the chain - // until the entire tree is freed - face_t *merged; // if set, this face isn't valid anymore - face_t *split[2]; // if set, this face isn't valid anymore - - portal_t *portal; - int texinfo; - int dispinfo; - // This is only for surfaces that are the boundaries of fog volumes - // (ie. water surfaces) - // All of the rest of the surfaces can look at their leaf to find out - // what fog volume they are in. - node_t *fogVolumeLeaf; - - int planenum; - int contents; // faces in different contents can't merge - int outputnumber; - winding_t *w; - int numpoints; - qboolean badstartvert; // tjunctions cannot be fixed without a midpoint vertex - int vertexnums[MAXEDGES]; - side_t *originalface; // save the "side" this face came from - int firstPrimID; - int numPrims; - unsigned int smoothingGroups; -}; - -void EmitFace( face_t *f, qboolean onNode ); - -struct mapdispinfo_t -{ - face_t face; - int entitynum; - int power; - int minTess; - float smoothingAngle; - Vector uAxis; - Vector vAxis; - Vector startPosition; - float alphaValues[MAX_DISPVERTS]; - float maxDispDist; - float dispDists[MAX_DISPVERTS]; - Vector vectorDisps[MAX_DISPVERTS]; - Vector vectorOffsets[MAX_DISPVERTS]; - int contents; - int brushSideID; - unsigned short triTags[MAX_DISPTRIS]; - int flags; - -#ifdef VSVMFIO - float m_elevation; // "elevation" - Vector m_offsetNormals[ MAX_DISPTRIS ]; // "offset_normals" -#endif // VSVMFIO - -}; - -extern int nummapdispinfo; -extern mapdispinfo_t mapdispinfo[MAX_MAP_DISPINFO]; - -extern float g_defaultLuxelSize; -extern float g_luxelScale; -extern float g_minLuxelScale; -extern bool g_BumpAll; -extern int g_nDXLevel; - -int GetDispInfoEntityNum( mapdispinfo_t *pDisp ); -void ComputeBoundsNoSkybox( ); - -struct bspbrush_t -{ - int id; - bspbrush_t *next; - Vector mins, maxs; - int side, testside; // side of node during construction - mapbrush_t *original; - int numsides; - side_t sides[6]; // variably sized -}; - - -#define MAX_NODE_BRUSHES 8 - -struct leafface_t -{ - face_t *pFace; - leafface_t *pNext; -}; - -struct node_t -{ - int id; - - // both leafs and nodes - int planenum; // -1 = leaf node - node_t *parent; - Vector mins, maxs; // valid after portalization - bspbrush_t *volume; // one for each leaf/node - - // nodes only - side_t *side; // the side that created the node - node_t *children[2]; - face_t *faces; // these are the cutup ones that live in the plane of "side". - - // leafs only - bspbrush_t *brushlist; // fragments of all brushes in this leaf - leafface_t *leaffacelist; - int contents; // OR of all brush contents - int occupied; // 1 or greater can reach entity - entity_t *occupant; // for leak file testing - int cluster; // for portalfile writing - int area; // for areaportals - portal_t *portals; // also on nodes during construction - int diskId; // dnodes or dleafs index after this has been emitted -}; - - -struct portal_t -{ - int id; - plane_t plane; - node_t *onnode; // NULL = outside box - node_t *nodes[2]; // [0] = front side of plane - portal_t *next[2]; - winding_t *winding; - qboolean sidefound; // false if ->side hasn't been checked - side_t *side; // NULL = non-visible - face_t *face[2]; // output face in bsp file -}; - - -struct tree_t -{ - node_t *headnode; - node_t outside_node; - Vector mins, maxs; - bool leaked; -}; - - -extern int entity_num; - -struct LoadSide_t; -struct LoadEntity_t; -class CManifest; - -class CMapFile -{ -public: - CMapFile( void ) { Init(); } - - void Init( void ); - - void AddPlaneToHash (plane_t *p); - int CreateNewFloatPlane (Vector& normal, vec_t dist); - int FindFloatPlane (Vector& normal, vec_t dist); - int PlaneFromPoints(const Vector &p0, const Vector &p1, const Vector &p2); - void AddBrushBevels (mapbrush_t *b); - qboolean MakeBrushWindings (mapbrush_t *ob); - void MoveBrushesToWorld( entity_t *mapent ); - void MoveBrushesToWorldGeneral( entity_t *mapent ); - void RemoveContentsDetailFromEntity( entity_t *mapent ); - int SideIDToIndex( int brushSideID ); - void AddLadderKeys( entity_t *mapent ); - ChunkFileResult_t LoadEntityCallback(CChunkFile *pFile, int nParam); - void ForceFuncAreaPortalWindowContents(); - ChunkFileResult_t LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo); - ChunkFileResult_t LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity); - ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); - void TestExpandBrushes(void); - - static char m_InstancePath[ MAX_PATH ]; - static void SetInstancePath( const char *pszInstancePath ); - static const char *GetInstancePath( void ) { return m_InstancePath; } - static bool DeterminePath( const char *pszBaseFileName, const char *pszInstanceFileName, char *pszOutFileName ); - - void CheckForInstances( const char *pszFileName ); - void MergeInstance( entity_t *pInstanceEntity, CMapFile *Instance ); - void MergePlanes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); - void MergeBrushes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); - void MergeBrushSides( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); - void ReplaceInstancePair( epair_t *pPair, entity_t *pInstanceEntity ); - void MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); - void MergeOverlays( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); - - static int m_InstanceCount; - static int c_areaportals; - - plane_t mapplanes[MAX_MAP_PLANES]; - int nummapplanes; - - #define PLANE_HASHES 1024 - plane_t *planehash[PLANE_HASHES]; - - int nummapbrushes; - mapbrush_t mapbrushes[MAX_MAP_BRUSHES]; - - Vector map_mins, map_maxs; - - int nummapbrushsides; - side_t brushsides[MAX_MAP_BRUSHSIDES]; - - brush_texture_t side_brushtextures[MAX_MAP_BRUSHSIDES]; - - int num_entities; - entity_t entities[MAX_MAP_ENTITIES]; - - int c_boxbevels; - int c_edgebevels; - int c_clipbrushes; - int g_ClipTexinfo; - - class CConnectionPairs - { - public: - CConnectionPairs( epair_t *pair, CConnectionPairs *next ) - { - m_Pair = pair; - m_Next = next; - } - - epair_t *m_Pair; - CConnectionPairs *m_Next; - }; - - CConnectionPairs *m_ConnectionPairs; - - int m_StartMapOverlays; - int m_StartMapWaterOverlays; -}; - -extern CMapFile *g_MainMap; -extern CMapFile *g_LoadingMap; - -extern CUtlVector< CMapFile * > g_Maps; - -extern int g_nMapFileVersion; - -extern qboolean noprune; -extern qboolean nodetail; -extern qboolean fulldetail; -extern qboolean nomerge; -extern qboolean nomergewater; -extern qboolean nosubdiv; -extern qboolean nowater; -extern qboolean noweld; -extern qboolean noshare; -extern qboolean notjunc; -extern qboolean nocsg; -extern qboolean noopt; -extern qboolean dumpcollide; -extern qboolean nodetailcuts; -extern qboolean g_DumpStaticProps; -extern qboolean g_bSkyVis; -extern vec_t microvolume; -extern bool g_snapAxialPlanes; -extern bool g_NodrawTriggers; -extern bool g_DisableWaterLighting; -extern bool g_bAllowDetailCracks; -extern bool g_bNoVirtualMesh; -extern char outbase[32]; - -extern char source[1024]; -extern char mapbase[ 64 ]; -extern CUtlVector g_SkyAreas; - -bool LoadMapFile( const char *pszFileName ); -int GetVertexnum( Vector& v ); -bool Is3DSkyboxArea( int area ); - -//============================================================================= - -// textures.c - -struct textureref_t -{ - char name[TEXTURE_NAME_LENGTH]; - int flags; - float lightmapWorldUnitsPerLuxel; - int contents; -}; - -extern textureref_t textureref[MAX_MAP_TEXTURES]; - -int FindMiptex (const char *name); - -int TexinfoForBrushTexture (plane_t *plane, brush_texture_t *bt, const Vector& origin); -int GetSurfaceProperties2( MaterialSystemMaterial_t matID, const char *pMatName ); - -extern int g_SurfaceProperties[MAX_MAP_TEXDATA]; -void LoadSurfaceProperties( void ); - -int PointLeafnum ( dmodel_t* pModel, const Vector& p ); - -//============================================================================= - -void FindGCD (int *v); - -mapbrush_t *Brush_LoadEntity (entity_t *ent); -int PlaneTypeForNormal (Vector& normal); -qboolean MakeBrushPlanes (mapbrush_t *b); -int FindIntPlane (int *inormal, int *iorigin); -void CreateBrush (int brushnum); - - -//============================================================================= -// detail objects -//============================================================================= - -void LoadEmitDetailObjectDictionary( char const* pGameDir ); -void EmitDetailObjects(); - -//============================================================================= -// static props -//============================================================================= - -void EmitStaticProps(); -bool LoadStudioModel( char const* pFileName, char const* pEntityType, CUtlBuffer& buf ); - -//============================================================================= -//============================================================================= -// procedurally created .vmt files -//============================================================================= - -void EmitStaticProps(); - -// draw.c - -extern Vector draw_mins, draw_maxs; -extern bool g_bLightIfMissing; - -void Draw_ClearWindow (void); -void DrawWinding (winding_t *w); - -void GLS_BeginScene (void); -void GLS_Winding (winding_t *w, int code); -void GLS_EndScene (void); - -//============================================================================= - -// csg - -enum detailscreen_e -{ - FULL_DETAIL = 0, - ONLY_DETAIL = 1, - NO_DETAIL = 2, -}; - -#define TRANSPARENT_CONTENTS (CONTENTS_GRATE|CONTENTS_WINDOW) - -#include "csg.h" - -//============================================================================= - -// brushbsp - -void WriteBrushList (char *name, bspbrush_t *brush, qboolean onlyvis); - -bspbrush_t *CopyBrush (bspbrush_t *brush); - -void SplitBrush (bspbrush_t *brush, int planenum, - bspbrush_t **front, bspbrush_t **back); - -tree_t *AllocTree (void); -node_t *AllocNode (void); -bspbrush_t *AllocBrush (int numsides); -int CountBrushList (bspbrush_t *brushes); -void FreeBrush (bspbrush_t *brushes); -vec_t BrushVolume (bspbrush_t *brush); -node_t *NodeForPoint (node_t *node, Vector& origin); - -void BoundBrush (bspbrush_t *brush); -void FreeBrushList (bspbrush_t *brushes); -node_t *PointInLeaf (node_t *node, Vector& point); - -tree_t *BrushBSP (bspbrush_t *brushlist, Vector& mins, Vector& maxs); - -#define PSIDE_FRONT 1 -#define PSIDE_BACK 2 -#define PSIDE_BOTH (PSIDE_FRONT|PSIDE_BACK) -#define PSIDE_FACING 4 -int BrushBspBoxOnPlaneSide (const Vector& mins, const Vector& maxs, dplane_t *plane); -extern qboolean WindingIsTiny (winding_t *w); - -//============================================================================= - -// portals.c - -int VisibleContents (int contents); - -void MakeHeadnodePortals (tree_t *tree); -void MakeNodePortal (node_t *node); -void SplitNodePortals (node_t *node); - -qboolean Portal_VisFlood (portal_t *p); - -qboolean FloodEntities (tree_t *tree); -void FillOutside (node_t *headnode); -void FloodAreas (tree_t *tree); -void MarkVisibleSides (tree_t *tree, int start, int end, int detailScreen); -void MarkVisibleSides (tree_t *tree, mapbrush_t **ppBrushes, int nCount ); -void FreePortal (portal_t *p); -void EmitAreaPortals (node_t *headnode); - -void MakeTreePortals (tree_t *tree); - -//============================================================================= - -// glfile.c - -void OutputWinding (winding_t *w, FileHandle_t glview); -void OutputWindingColor (winding_t *w, FileHandle_t glview, int r, int g, int b); -void WriteGLView (tree_t *tree, char *source); -void WriteGLViewFaces (tree_t *tree, const char *source); -void WriteGLViewBrushList( bspbrush_t *pList, const char *pName ); -//============================================================================= - -// leakfile.c - -void LeakFile (tree_t *tree); -void AreaportalLeakFile( tree_t *tree, portal_t *pStartPortal, portal_t *pEndPortal, node_t *pStart ); - -//============================================================================= - -// prtfile.c - -void AddVisCluster( entity_t *pFuncVisCluster ); -void WritePortalFile (tree_t *tree); - -//============================================================================= - -// writebsp.c - -void SetModelNumbers (void); -void SetLightStyles (void); - -void BeginBSPFile (void); -void WriteBSP (node_t *headnode, face_t *pLeafFaceList); -void EndBSPFile (void); -void BeginModel (void); -void EndModel (void); - -extern int firstmodeledge; -extern int firstmodelface; - -//============================================================================= - -// faces.c - -void MakeFaces (node_t *headnode); -void MakeDetailFaces (node_t *headnode); -face_t *FixTjuncs( node_t *headnode, face_t *pLeafFaceList ); - -face_t *AllocFace (void); -void FreeFace (face_t *f); -void FreeFaceList( face_t *pFaces ); - -void MergeFaceList(face_t **pFaceList); -void SubdivideFaceList(face_t **pFaceList); - -extern face_t *edgefaces[MAX_MAP_EDGES][2]; - - -//============================================================================= - -// tree.c - -void FreeTree (tree_t *tree); -void FreeTree_r (node_t *node); -void PrintTree_r (node_t *node, int depth); -void FreeTreePortals_r (node_t *node); -void PruneNodes_r (node_t *node); -void PruneNodes (node_t *node); - -// Returns true if the entity is a func_occluder -bool IsFuncOccluder( int entity_num ); - - -//============================================================================= -// ivp.cpp -class CPhysCollide; -void EmitPhysCollision(); -void DumpCollideToGlView( CPhysCollide *pCollide, const char *pFilename ); -void EmitWaterVolumesForBSP( dmodel_t *pModel, node_t *headnode ); - -//============================================================================= -// find + find or create the texdata -int FindTexData( const char *pName ); -int FindOrCreateTexData( const char *pName ); -// Add a clone of an existing texdata with a new name -int AddCloneTexData( dtexdata_t *pExistingTexData, char const *cloneTexDataName ); -int FindOrCreateTexInfo( const texinfo_t &searchTexInfo ); -int FindAliasedTexData( const char *pName, dtexdata_t *sourceTexture ); -int FindTexInfo( const texinfo_t &searchTexInfo ); - -//============================================================================= -// normals.c -void SaveVertexNormals( void ); - -//============================================================================= -// cubemap.cpp -void Cubemap_InsertSample( const Vector& origin, int size ); -void Cubemap_CreateDefaultCubemaps( void ); -void Cubemap_SaveBrushSides( const char *pSideListStr ); -void Cubemap_FixupBrushSidesMaterials( void ); -void Cubemap_AttachDefaultCubemapToSpecularSides( void ); -// Add skipped cubemaps that are referenced by the engine -void Cubemap_AddUnreferencedCubemaps( void ); - -//============================================================================= -// overlay.cpp -#define OVERLAY_MAP_STRLEN 256 - -struct mapoverlay_t -{ - int nId; - unsigned short m_nRenderOrder; - char szMaterialName[OVERLAY_MAP_STRLEN]; - float flU[2]; - float flV[2]; - float flFadeDistMinSq; - float flFadeDistMaxSq; - Vector vecUVPoints[4]; - Vector vecOrigin; - Vector vecBasis[3]; - CUtlVector aSideList; - CUtlVector aFaceList; -}; - -extern CUtlVector g_aMapOverlays; -extern CUtlVector g_aMapWaterOverlays; - -int Overlay_GetFromEntity( entity_t *pMapEnt ); -void Overlay_UpdateSideLists( int StartIndex ); -void Overlay_AddFaceToLists( int iFace, side_t *pSide ); -void Overlay_EmitOverlayFaces( void ); -void OverlayTransition_UpdateSideLists( int StartIndex ); -void OverlayTransition_AddFaceToLists( int iFace, side_t *pSide ); -void OverlayTransition_EmitOverlayFaces( void ); -void Overlay_Translate( mapoverlay_t *pOverlay, Vector &OriginOffset, QAngle &AngleOffset, matrix3x4_t &Matrix ); - -//============================================================================= - -void RemoveAreaPortalBrushes_R( node_t *node ); - -dtexdata_t *GetTexData( int index ); - -#endif - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#if !defined( VBSP_H ) +#define VBSP_H + + +#include "cmdlib.h" +#include "mathlib/vector.h" +#include "scriplib.h" +#include "polylib.h" +#include "threads.h" +#include "bsplib.h" +#include "qfiles.h" +#include "utilmatlib.h" +#include "ChunkFile.h" + +#ifdef WIN32 +#pragma warning( disable: 4706 ) +#endif + +class CUtlBuffer; + +#define MAX_BRUSH_SIDES 128 +#define CLIP_EPSILON 0.1 + +#define TEXINFO_NODE -1 // side is allready on a node + +// this will output glview files for the given brushmodel. Brushmodel 1 is the world, 2 is the first brush entity, etc. +#define DEBUG_BRUSHMODEL 0 + +struct portal_t; +struct node_t; + +struct plane_t : public dplane_t +{ + plane_t *hash_chain; + + plane_t() { normal.Init(); } +}; + + +struct brush_texture_t +{ + Vector UAxis; + Vector VAxis; + vec_t shift[2]; + vec_t rotate; + vec_t textureWorldUnitsPerTexel[2]; + vec_t lightmapWorldUnitsPerLuxel; + char name[TEXTURE_NAME_LENGTH]; + int flags; + + brush_texture_t() : UAxis(0,0,0), VAxis(0,0,0) {} +}; + +struct mapdispinfo_t; + +struct side_t +{ + int planenum; + int texinfo; + mapdispinfo_t *pMapDisp; + + winding_t *winding; + side_t *original; // bspbrush_t sides will reference the mapbrush_t sides + int contents; // from miptex + int surf; // from miptex + qboolean visible; // choose visble planes first + qboolean tested; // this plane allready checked as a split + qboolean bevel; // don't ever use for bsp splitting + + side_t *next; + int origIndex; + int id; // This is the unique id generated by worldcraft for this side. + unsigned int smoothingGroups; + CUtlVector aOverlayIds; // List of overlays that reside on this side. + CUtlVector aWaterOverlayIds; // List of water overlays that reside on this side. + bool m_bDynamicShadowsEnabled; // Goes into dface_t::SetDynamicShadowsEnabled(). +}; + +struct mapbrush_t +{ + int entitynum; + int brushnum; + int id; // The unique ID of this brush in the editor, used for reporting errors. + int contents; + Vector mins, maxs; + int numsides; + side_t *original_sides; +}; + +#define PLANENUM_LEAF -1 + +#define MAXEDGES 32 + +struct face_t +{ + int id; + + face_t *next; // on node + + // the chain of faces off of a node can be merged or split, + // but each face_t along the way will remain in the chain + // until the entire tree is freed + face_t *merged; // if set, this face isn't valid anymore + face_t *split[2]; // if set, this face isn't valid anymore + + portal_t *portal; + int texinfo; + int dispinfo; + // This is only for surfaces that are the boundaries of fog volumes + // (ie. water surfaces) + // All of the rest of the surfaces can look at their leaf to find out + // what fog volume they are in. + node_t *fogVolumeLeaf; + + int planenum; + int contents; // faces in different contents can't merge + int outputnumber; + winding_t *w; + int numpoints; + qboolean badstartvert; // tjunctions cannot be fixed without a midpoint vertex + int vertexnums[MAXEDGES]; + side_t *originalface; // save the "side" this face came from + int firstPrimID; + int numPrims; + unsigned int smoothingGroups; +}; + +void EmitFace( face_t *f, qboolean onNode ); + +struct mapdispinfo_t +{ + face_t face; + int entitynum; + int power; + int minTess; + float smoothingAngle; + Vector uAxis; + Vector vAxis; + Vector startPosition; + float alphaValues[MAX_DISPVERTS]; + float maxDispDist; + float dispDists[MAX_DISPVERTS]; + Vector vectorDisps[MAX_DISPVERTS]; + Vector vectorOffsets[MAX_DISPVERTS]; + int contents; + int brushSideID; + unsigned short triTags[MAX_DISPTRIS]; + int flags; + +#ifdef VSVMFIO + float m_elevation; // "elevation" + Vector m_offsetNormals[ MAX_DISPTRIS ]; // "offset_normals" +#endif // VSVMFIO + +}; + +extern int nummapdispinfo; +extern mapdispinfo_t mapdispinfo[MAX_MAP_DISPINFO]; + +extern float g_defaultLuxelSize; +extern float g_luxelScale; +extern float g_minLuxelScale; +extern bool g_BumpAll; +extern int g_nDXLevel; + +int GetDispInfoEntityNum( mapdispinfo_t *pDisp ); +void ComputeBoundsNoSkybox( ); + +struct bspbrush_t +{ + int id; + bspbrush_t *next; + Vector mins, maxs; + int side, testside; // side of node during construction + mapbrush_t *original; + int numsides; + side_t sides[6]; // variably sized +}; + + +#define MAX_NODE_BRUSHES 8 + +struct leafface_t +{ + face_t *pFace; + leafface_t *pNext; +}; + +struct node_t +{ + int id; + + // both leafs and nodes + int planenum; // -1 = leaf node + node_t *parent; + Vector mins, maxs; // valid after portalization + bspbrush_t *volume; // one for each leaf/node + + // nodes only + side_t *side; // the side that created the node + node_t *children[2]; + face_t *faces; // these are the cutup ones that live in the plane of "side". + + // leafs only + bspbrush_t *brushlist; // fragments of all brushes in this leaf + leafface_t *leaffacelist; + int contents; // OR of all brush contents + int occupied; // 1 or greater can reach entity + entity_t *occupant; // for leak file testing + int cluster; // for portalfile writing + int area; // for areaportals + portal_t *portals; // also on nodes during construction + int diskId; // dnodes or dleafs index after this has been emitted +}; + + +struct portal_t +{ + int id; + plane_t plane; + node_t *onnode; // NULL = outside box + node_t *nodes[2]; // [0] = front side of plane + portal_t *next[2]; + winding_t *winding; + qboolean sidefound; // false if ->side hasn't been checked + side_t *side; // NULL = non-visible + face_t *face[2]; // output face in bsp file +}; + + +struct tree_t +{ + node_t *headnode; + node_t outside_node; + Vector mins, maxs; + bool leaked; +}; + + +extern int entity_num; + +struct LoadSide_t; +struct LoadEntity_t; +class CManifest; + +class CMapFile +{ +public: + CMapFile( void ) { Init(); } + + void Init( void ); + + void AddPlaneToHash (plane_t *p); + int CreateNewFloatPlane (Vector& normal, vec_t dist); + int FindFloatPlane (Vector& normal, vec_t dist); + int PlaneFromPoints(const Vector &p0, const Vector &p1, const Vector &p2); + void AddBrushBevels (mapbrush_t *b); + qboolean MakeBrushWindings (mapbrush_t *ob); + void MoveBrushesToWorld( entity_t *mapent ); + void MoveBrushesToWorldGeneral( entity_t *mapent ); + void RemoveContentsDetailFromEntity( entity_t *mapent ); + int SideIDToIndex( int brushSideID ); + void AddLadderKeys( entity_t *mapent ); + ChunkFileResult_t LoadEntityCallback(CChunkFile *pFile, int nParam); + void ForceFuncAreaPortalWindowContents(); + ChunkFileResult_t LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo); + ChunkFileResult_t LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity); + ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); + void TestExpandBrushes(void); + + static char m_InstancePath[ MAX_PATH ]; + static void SetInstancePath( const char *pszInstancePath ); + static const char *GetInstancePath( void ) { return m_InstancePath; } + static bool DeterminePath( const char *pszBaseFileName, const char *pszInstanceFileName, char *pszOutFileName ); + + void CheckForInstances( const char *pszFileName ); + void MergeInstance( entity_t *pInstanceEntity, CMapFile *Instance ); + void MergePlanes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); + void MergeBrushes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); + void MergeBrushSides( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); + void ReplaceInstancePair( epair_t *pPair, entity_t *pInstanceEntity ); + void MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); + void MergeOverlays( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); + + static int m_InstanceCount; + static int c_areaportals; + + plane_t mapplanes[MAX_MAP_PLANES]; + int nummapplanes; + + #define PLANE_HASHES 1024 + plane_t *planehash[PLANE_HASHES]; + + int nummapbrushes; + mapbrush_t mapbrushes[MAX_MAP_BRUSHES]; + + Vector map_mins, map_maxs; + + int nummapbrushsides; + side_t brushsides[MAX_MAP_BRUSHSIDES]; + + brush_texture_t side_brushtextures[MAX_MAP_BRUSHSIDES]; + + int num_entities; + entity_t entities[MAX_MAP_ENTITIES]; + + int c_boxbevels; + int c_edgebevels; + int c_clipbrushes; + int g_ClipTexinfo; + + class CConnectionPairs + { + public: + CConnectionPairs( epair_t *pair, CConnectionPairs *next ) + { + m_Pair = pair; + m_Next = next; + } + + epair_t *m_Pair; + CConnectionPairs *m_Next; + }; + + CConnectionPairs *m_ConnectionPairs; + + int m_StartMapOverlays; + int m_StartMapWaterOverlays; +}; + +extern CMapFile *g_MainMap; +extern CMapFile *g_LoadingMap; + +extern CUtlVector< CMapFile * > g_Maps; + +extern int g_nMapFileVersion; + +extern qboolean noprune; +extern qboolean nodetail; +extern qboolean fulldetail; +extern qboolean nomerge; +extern qboolean nomergewater; +extern qboolean nosubdiv; +extern qboolean nowater; +extern qboolean noweld; +extern qboolean noshare; +extern qboolean notjunc; +extern qboolean nocsg; +extern qboolean noopt; +extern qboolean dumpcollide; +extern qboolean nodetailcuts; +extern qboolean g_DumpStaticProps; +extern qboolean g_bSkyVis; +extern vec_t microvolume; +extern bool g_snapAxialPlanes; +extern bool g_NodrawTriggers; +extern bool g_DisableWaterLighting; +extern bool g_bAllowDetailCracks; +extern bool g_bNoVirtualMesh; +extern char outbase[32]; + +extern char source[1024]; +extern char mapbase[ 64 ]; +extern CUtlVector g_SkyAreas; + +bool LoadMapFile( const char *pszFileName ); +int GetVertexnum( Vector& v ); +bool Is3DSkyboxArea( int area ); + +//============================================================================= + +// textures.c + +struct textureref_t +{ + char name[TEXTURE_NAME_LENGTH]; + int flags; + float lightmapWorldUnitsPerLuxel; + int contents; +}; + +extern textureref_t textureref[MAX_MAP_TEXTURES]; + +int FindMiptex (const char *name); + +int TexinfoForBrushTexture (plane_t *plane, brush_texture_t *bt, const Vector& origin); +int GetSurfaceProperties2( MaterialSystemMaterial_t matID, const char *pMatName ); + +extern int g_SurfaceProperties[MAX_MAP_TEXDATA]; +void LoadSurfaceProperties( void ); + +int PointLeafnum ( dmodel_t* pModel, const Vector& p ); + +//============================================================================= + +void FindGCD (int *v); + +mapbrush_t *Brush_LoadEntity (entity_t *ent); +int PlaneTypeForNormal (Vector& normal); +qboolean MakeBrushPlanes (mapbrush_t *b); +int FindIntPlane (int *inormal, int *iorigin); +void CreateBrush (int brushnum); + + +//============================================================================= +// detail objects +//============================================================================= + +void LoadEmitDetailObjectDictionary( char const* pGameDir ); +void EmitDetailObjects(); + +//============================================================================= +// static props +//============================================================================= + +void EmitStaticProps(); +bool LoadStudioModel( char const* pFileName, char const* pEntityType, CUtlBuffer& buf ); + +//============================================================================= +//============================================================================= +// procedurally created .vmt files +//============================================================================= + +void EmitStaticProps(); + +// draw.c + +extern Vector draw_mins, draw_maxs; +extern bool g_bLightIfMissing; + +void Draw_ClearWindow (void); +void DrawWinding (winding_t *w); + +void GLS_BeginScene (void); +void GLS_Winding (winding_t *w, int code); +void GLS_EndScene (void); + +//============================================================================= + +// csg + +enum detailscreen_e +{ + FULL_DETAIL = 0, + ONLY_DETAIL = 1, + NO_DETAIL = 2, +}; + +#define TRANSPARENT_CONTENTS (CONTENTS_GRATE|CONTENTS_WINDOW) + +#include "csg.h" + +//============================================================================= + +// brushbsp + +void WriteBrushList (char *name, bspbrush_t *brush, qboolean onlyvis); + +bspbrush_t *CopyBrush (bspbrush_t *brush); + +void SplitBrush (bspbrush_t *brush, int planenum, + bspbrush_t **front, bspbrush_t **back); + +tree_t *AllocTree (void); +node_t *AllocNode (void); +bspbrush_t *AllocBrush (int numsides); +int CountBrushList (bspbrush_t *brushes); +void FreeBrush (bspbrush_t *brushes); +vec_t BrushVolume (bspbrush_t *brush); +node_t *NodeForPoint (node_t *node, Vector& origin); + +void BoundBrush (bspbrush_t *brush); +void FreeBrushList (bspbrush_t *brushes); +node_t *PointInLeaf (node_t *node, Vector& point); + +tree_t *BrushBSP (bspbrush_t *brushlist, Vector& mins, Vector& maxs); + +#define PSIDE_FRONT 1 +#define PSIDE_BACK 2 +#define PSIDE_BOTH (PSIDE_FRONT|PSIDE_BACK) +#define PSIDE_FACING 4 +int BrushBspBoxOnPlaneSide (const Vector& mins, const Vector& maxs, dplane_t *plane); +extern qboolean WindingIsTiny (winding_t *w); + +//============================================================================= + +// portals.c + +int VisibleContents (int contents); + +void MakeHeadnodePortals (tree_t *tree); +void MakeNodePortal (node_t *node); +void SplitNodePortals (node_t *node); + +qboolean Portal_VisFlood (portal_t *p); + +qboolean FloodEntities (tree_t *tree); +void FillOutside (node_t *headnode); +void FloodAreas (tree_t *tree); +void MarkVisibleSides (tree_t *tree, int start, int end, int detailScreen); +void MarkVisibleSides (tree_t *tree, mapbrush_t **ppBrushes, int nCount ); +void FreePortal (portal_t *p); +void EmitAreaPortals (node_t *headnode); + +void MakeTreePortals (tree_t *tree); + +//============================================================================= + +// glfile.c + +void OutputWinding (winding_t *w, FileHandle_t glview); +void OutputWindingColor (winding_t *w, FileHandle_t glview, int r, int g, int b); +void WriteGLView (tree_t *tree, char *source); +void WriteGLViewFaces (tree_t *tree, const char *source); +void WriteGLViewBrushList( bspbrush_t *pList, const char *pName ); +//============================================================================= + +// leakfile.c + +void LeakFile (tree_t *tree); +void AreaportalLeakFile( tree_t *tree, portal_t *pStartPortal, portal_t *pEndPortal, node_t *pStart ); + +//============================================================================= + +// prtfile.c + +void AddVisCluster( entity_t *pFuncVisCluster ); +void WritePortalFile (tree_t *tree); + +//============================================================================= + +// writebsp.c + +void SetModelNumbers (void); +void SetLightStyles (void); + +void BeginBSPFile (void); +void WriteBSP (node_t *headnode, face_t *pLeafFaceList); +void EndBSPFile (void); +void BeginModel (void); +void EndModel (void); + +extern int firstmodeledge; +extern int firstmodelface; + +//============================================================================= + +// faces.c + +void MakeFaces (node_t *headnode); +void MakeDetailFaces (node_t *headnode); +face_t *FixTjuncs( node_t *headnode, face_t *pLeafFaceList ); + +face_t *AllocFace (void); +void FreeFace (face_t *f); +void FreeFaceList( face_t *pFaces ); + +void MergeFaceList(face_t **pFaceList); +void SubdivideFaceList(face_t **pFaceList); + +extern face_t *edgefaces[MAX_MAP_EDGES][2]; + + +//============================================================================= + +// tree.c + +void FreeTree (tree_t *tree); +void FreeTree_r (node_t *node); +void PrintTree_r (node_t *node, int depth); +void FreeTreePortals_r (node_t *node); +void PruneNodes_r (node_t *node); +void PruneNodes (node_t *node); + +// Returns true if the entity is a func_occluder +bool IsFuncOccluder( int entity_num ); + + +//============================================================================= +// ivp.cpp +class CPhysCollide; +void EmitPhysCollision(); +void DumpCollideToGlView( CPhysCollide *pCollide, const char *pFilename ); +void EmitWaterVolumesForBSP( dmodel_t *pModel, node_t *headnode ); + +//============================================================================= +// find + find or create the texdata +int FindTexData( const char *pName ); +int FindOrCreateTexData( const char *pName ); +// Add a clone of an existing texdata with a new name +int AddCloneTexData( dtexdata_t *pExistingTexData, char const *cloneTexDataName ); +int FindOrCreateTexInfo( const texinfo_t &searchTexInfo ); +int FindAliasedTexData( const char *pName, dtexdata_t *sourceTexture ); +int FindTexInfo( const texinfo_t &searchTexInfo ); + +//============================================================================= +// normals.c +void SaveVertexNormals( void ); + +//============================================================================= +// cubemap.cpp +void Cubemap_InsertSample( const Vector& origin, int size ); +void Cubemap_CreateDefaultCubemaps( void ); +void Cubemap_SaveBrushSides( const char *pSideListStr ); +void Cubemap_FixupBrushSidesMaterials( void ); +void Cubemap_AttachDefaultCubemapToSpecularSides( void ); +// Add skipped cubemaps that are referenced by the engine +void Cubemap_AddUnreferencedCubemaps( void ); + +//============================================================================= +// overlay.cpp +#define OVERLAY_MAP_STRLEN 256 + +struct mapoverlay_t +{ + int nId; + unsigned short m_nRenderOrder; + char szMaterialName[OVERLAY_MAP_STRLEN]; + float flU[2]; + float flV[2]; + float flFadeDistMinSq; + float flFadeDistMaxSq; + Vector vecUVPoints[4]; + Vector vecOrigin; + Vector vecBasis[3]; + CUtlVector aSideList; + CUtlVector aFaceList; +}; + +extern CUtlVector g_aMapOverlays; +extern CUtlVector g_aMapWaterOverlays; + +int Overlay_GetFromEntity( entity_t *pMapEnt ); +void Overlay_UpdateSideLists( int StartIndex ); +void Overlay_AddFaceToLists( int iFace, side_t *pSide ); +void Overlay_EmitOverlayFaces( void ); +void OverlayTransition_UpdateSideLists( int StartIndex ); +void OverlayTransition_AddFaceToLists( int iFace, side_t *pSide ); +void OverlayTransition_EmitOverlayFaces( void ); +void Overlay_Translate( mapoverlay_t *pOverlay, Vector &OriginOffset, QAngle &AngleOffset, matrix3x4_t &Matrix ); + +//============================================================================= + +void RemoveAreaPortalBrushes_R( node_t *node ); + +dtexdata_t *GetTexData( int index ); + +#endif + diff --git a/mp/src/utils/vbsp/vbsp.vpc b/mp/src/utils/vbsp/vbsp.vpc index 4c6d1862..7065f0a5 100644 --- a/mp/src/utils/vbsp/vbsp.vpc +++ b/mp/src/utils/vbsp/vbsp.vpc @@ -1,184 +1,184 @@ -//----------------------------------------------------------------------------- -// VBSP.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" - -$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" - -$Configuration -{ - $Compiler - { - $AdditionalIncludeDirectories "$BASE,..\common,..\vmpi" - $PreprocessorDefinitions "$BASE;MACRO_MATHLIB;PROTECTED_THINGS_DISABLE" - } - - $Linker - { - $AdditionalDependencies "$BASE ws2_32.lib odbc32.lib odbccp32.lib winmm.lib" - } -} - -$Project "Vbsp" -{ - $Folder "Source Files" - { - $File "boundbox.cpp" - $File "brushbsp.cpp" - $File "$SRCDIR\public\CollisionUtils.cpp" - $File "csg.cpp" - $File "cubemap.cpp" - $File "detail.cpp" - $File "detailObjects.cpp" - $File "$SRCDIR\public\disp_common.cpp" - $File "disp_ivp.cpp" - $File "$SRCDIR\public\disp_powerinfo.cpp" - $File "disp_vbsp.cpp" - $File "faces.cpp" - $File "glfile.cpp" - $File "ivp.cpp" - $File "leakfile.cpp" - $File "$SRCDIR\public\loadcmdline.cpp" - $File "$SRCDIR\public\lumpfiles.cpp" - $File "map.cpp" - $File "manifest.cpp" - $File "materialpatch.cpp" - $File "materialsub.cpp" - $File "..\common\mstristrip.cpp" - $File "nodraw.cpp" - $File "normals.cpp" - $File "overlay.cpp" - $File "..\common\physdll.cpp" - $File "portals.cpp" - $File "prtfile.cpp" - $File "$SRCDIR\public\ScratchPad3D.cpp" - $File "..\common\scratchpad_helpers.cpp" - $File "StaticProp.cpp" - $File "textures.cpp" - $File "tree.cpp" - $File "..\common\utilmatlib.cpp" - $File "vbsp.cpp" - $File "worldvertextransitionfixup.cpp" - $File "writebsp.cpp" - $File "$SRCDIR\public\zip_utils.cpp" - - $Folder "Common Files" - { - $File "..\common\bsplib.cpp" - $File "$SRCDIR\public\builddisp.cpp" - $File "$SRCDIR\public\ChunkFile.cpp" - $File "..\common\cmdlib.cpp" - $File "$SRCDIR\public\filesystem_helpers.cpp" - $File "$SRCDIR\public\filesystem_init.cpp" - $File "..\common\filesystem_tools.cpp" - $File "..\common\map_shared.cpp" - $File "..\common\pacifier.cpp" - $File "..\common\polylib.cpp" - $File "..\common\scriplib.cpp" - $File "..\common\threads.cpp" - $File "..\common\tools_minidump.cpp" - $File "..\common\tools_minidump.h" - } - } - - $Folder "Header Files" - { - $File "boundbox.h" - $File "csg.h" - $File "detail.h" - $File "$SRCDIR\public\disp_powerinfo.h" - $File "disp_vbsp.h" - $File "$SRCDIR\public\disp_vertindex.h" - $File "faces.h" - $File "map.h" - $File "manifest.h" - $File "materialpatch.h" - $File "materialsub.h" - $File "..\common\scratchpad_helpers.h" - $File "vbsp.h" - $File "worldvertextransitionfixup.h" - $File "writebsp.h" - - $Folder "Common header files" - { - $File "..\common\bsplib.h" - $File "$SRCDIR\public\builddisp.h" - $File "$SRCDIR\public\ChunkFile.h" - $File "..\common\cmdlib.h" - $File "disp_ivp.h" - $File "$SRCDIR\public\filesystem.h" - $File "$SRCDIR\public\filesystem_helpers.h" - $File "..\common\FileSystem_Tools.h" - $File "$SRCDIR\public\GameBSPFile.h" - $File "$SRCDIR\public\tier1\interface.h" - $File "ivp.h" - $File "..\common\map_shared.h" - $File "..\common\pacifier.h" - $File "..\common\polylib.h" - $File "$SRCDIR\public\tier1\tokenreader.h" - $File "..\common\utilmatlib.h" - $File "..\vmpi\vmpi.h" - $File "$SRCDIR\public\zip_uncompressed.h" - } - } - - $Folder "Public Headers" - { - $File "$SRCDIR\public\mathlib\amd3dx.h" - $File "$SRCDIR\public\arraystack.h" - $File "$SRCDIR\public\tier0\basetypes.h" - $File "$SRCDIR\public\BSPFILE.H" - $File "$SRCDIR\public\bspflags.h" - $File "$SRCDIR\public\BSPTreeData.h" - $File "$SRCDIR\public\mathlib\bumpvects.h" - $File "$SRCDIR\public\tier1\byteswap.h" - $File "$SRCDIR\public\cmodel.h" - $File "$SRCDIR\public\CollisionUtils.h" - $File "$SRCDIR\public\tier0\commonmacros.h" - $File "$SRCDIR\public\tier0\dbg.h" - $File "$SRCDIR\public\disp_common.h" - $File "$SRCDIR\public\IScratchPad3D.h" - $File "$SRCDIR\public\mathlib\mathlib.h" - $File "..\common\mstristrip.h" - $File "$SRCDIR\public\nmatrix.h" - $File "$SRCDIR\public\NTree.h" - $File "$SRCDIR\public\nvector.h" - $File "$SRCDIR\public\phyfile.h" - $File "..\common\physdll.h" - $File "..\common\qfiles.h" - $File "$SRCDIR\public\ScratchPad3D.h" - $File "..\common\scriplib.h" - $File "$SRCDIR\public\studio.h" - $File "..\common\threads.h" - $File "$SRCDIR\public\tier1\utlbuffer.h" - $File "$SRCDIR\public\tier1\utllinkedlist.h" - $File "$SRCDIR\public\tier1\utlmemory.h" - $File "$SRCDIR\public\tier1\utlrbtree.h" - $File "$SRCDIR\public\tier1\utlsymbol.h" - $File "$SRCDIR\public\tier1\utlvector.h" - $File "$SRCDIR\public\vcollide.h" - $File "$SRCDIR\public\mathlib\vector.h" - $File "$SRCDIR\public\mathlib\vector2d.h" - $File "$SRCDIR\public\mathlib\vector4d.h" - $File "$SRCDIR\public\mathlib\vmatrix.h" - $File "$SRCDIR\public\vphysics_interface.h" - $File "$SRCDIR\public\mathlib\vplane.h" - $File "$SRCDIR\public\wadtypes.h" - $File "$SRCDIR\public\worldsize.h" - } - - $Folder "Link Libraries" - { - $Lib bitmap - $Lib fgdlib - $Lib mathlib - $Lib tier2 - $Lib vtf - } - - $File "notes.txt" -} +//----------------------------------------------------------------------------- +// VBSP.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\common,..\vmpi" + $PreprocessorDefinitions "$BASE;MACRO_MATHLIB;PROTECTED_THINGS_DISABLE" + } + + $Linker + { + $AdditionalDependencies "$BASE ws2_32.lib odbc32.lib odbccp32.lib winmm.lib" + } +} + +$Project "Vbsp" +{ + $Folder "Source Files" + { + $File "boundbox.cpp" + $File "brushbsp.cpp" + $File "$SRCDIR\public\CollisionUtils.cpp" + $File "csg.cpp" + $File "cubemap.cpp" + $File "detail.cpp" + $File "detailObjects.cpp" + $File "$SRCDIR\public\disp_common.cpp" + $File "disp_ivp.cpp" + $File "$SRCDIR\public\disp_powerinfo.cpp" + $File "disp_vbsp.cpp" + $File "faces.cpp" + $File "glfile.cpp" + $File "ivp.cpp" + $File "leakfile.cpp" + $File "$SRCDIR\public\loadcmdline.cpp" + $File "$SRCDIR\public\lumpfiles.cpp" + $File "map.cpp" + $File "manifest.cpp" + $File "materialpatch.cpp" + $File "materialsub.cpp" + $File "..\common\mstristrip.cpp" + $File "nodraw.cpp" + $File "normals.cpp" + $File "overlay.cpp" + $File "..\common\physdll.cpp" + $File "portals.cpp" + $File "prtfile.cpp" + $File "$SRCDIR\public\ScratchPad3D.cpp" + $File "..\common\scratchpad_helpers.cpp" + $File "StaticProp.cpp" + $File "textures.cpp" + $File "tree.cpp" + $File "..\common\utilmatlib.cpp" + $File "vbsp.cpp" + $File "worldvertextransitionfixup.cpp" + $File "writebsp.cpp" + $File "$SRCDIR\public\zip_utils.cpp" + + $Folder "Common Files" + { + $File "..\common\bsplib.cpp" + $File "$SRCDIR\public\builddisp.cpp" + $File "$SRCDIR\public\ChunkFile.cpp" + $File "..\common\cmdlib.cpp" + $File "$SRCDIR\public\filesystem_helpers.cpp" + $File "$SRCDIR\public\filesystem_init.cpp" + $File "..\common\filesystem_tools.cpp" + $File "..\common\map_shared.cpp" + $File "..\common\pacifier.cpp" + $File "..\common\polylib.cpp" + $File "..\common\scriplib.cpp" + $File "..\common\threads.cpp" + $File "..\common\tools_minidump.cpp" + $File "..\common\tools_minidump.h" + } + } + + $Folder "Header Files" + { + $File "boundbox.h" + $File "csg.h" + $File "detail.h" + $File "$SRCDIR\public\disp_powerinfo.h" + $File "disp_vbsp.h" + $File "$SRCDIR\public\disp_vertindex.h" + $File "faces.h" + $File "map.h" + $File "manifest.h" + $File "materialpatch.h" + $File "materialsub.h" + $File "..\common\scratchpad_helpers.h" + $File "vbsp.h" + $File "worldvertextransitionfixup.h" + $File "writebsp.h" + + $Folder "Common header files" + { + $File "..\common\bsplib.h" + $File "$SRCDIR\public\builddisp.h" + $File "$SRCDIR\public\ChunkFile.h" + $File "..\common\cmdlib.h" + $File "disp_ivp.h" + $File "$SRCDIR\public\filesystem.h" + $File "$SRCDIR\public\filesystem_helpers.h" + $File "..\common\FileSystem_Tools.h" + $File "$SRCDIR\public\GameBSPFile.h" + $File "$SRCDIR\public\tier1\interface.h" + $File "ivp.h" + $File "..\common\map_shared.h" + $File "..\common\pacifier.h" + $File "..\common\polylib.h" + $File "$SRCDIR\public\tier1\tokenreader.h" + $File "..\common\utilmatlib.h" + $File "..\vmpi\vmpi.h" + $File "$SRCDIR\public\zip_uncompressed.h" + } + } + + $Folder "Public Headers" + { + $File "$SRCDIR\public\mathlib\amd3dx.h" + $File "$SRCDIR\public\arraystack.h" + $File "$SRCDIR\public\tier0\basetypes.h" + $File "$SRCDIR\public\BSPFILE.H" + $File "$SRCDIR\public\bspflags.h" + $File "$SRCDIR\public\BSPTreeData.h" + $File "$SRCDIR\public\mathlib\bumpvects.h" + $File "$SRCDIR\public\tier1\byteswap.h" + $File "$SRCDIR\public\cmodel.h" + $File "$SRCDIR\public\CollisionUtils.h" + $File "$SRCDIR\public\tier0\commonmacros.h" + $File "$SRCDIR\public\tier0\dbg.h" + $File "$SRCDIR\public\disp_common.h" + $File "$SRCDIR\public\IScratchPad3D.h" + $File "$SRCDIR\public\mathlib\mathlib.h" + $File "..\common\mstristrip.h" + $File "$SRCDIR\public\nmatrix.h" + $File "$SRCDIR\public\NTree.h" + $File "$SRCDIR\public\nvector.h" + $File "$SRCDIR\public\phyfile.h" + $File "..\common\physdll.h" + $File "..\common\qfiles.h" + $File "$SRCDIR\public\ScratchPad3D.h" + $File "..\common\scriplib.h" + $File "$SRCDIR\public\studio.h" + $File "..\common\threads.h" + $File "$SRCDIR\public\tier1\utlbuffer.h" + $File "$SRCDIR\public\tier1\utllinkedlist.h" + $File "$SRCDIR\public\tier1\utlmemory.h" + $File "$SRCDIR\public\tier1\utlrbtree.h" + $File "$SRCDIR\public\tier1\utlsymbol.h" + $File "$SRCDIR\public\tier1\utlvector.h" + $File "$SRCDIR\public\vcollide.h" + $File "$SRCDIR\public\mathlib\vector.h" + $File "$SRCDIR\public\mathlib\vector2d.h" + $File "$SRCDIR\public\mathlib\vector4d.h" + $File "$SRCDIR\public\mathlib\vmatrix.h" + $File "$SRCDIR\public\vphysics_interface.h" + $File "$SRCDIR\public\mathlib\vplane.h" + $File "$SRCDIR\public\wadtypes.h" + $File "$SRCDIR\public\worldsize.h" + } + + $Folder "Link Libraries" + { + $Lib bitmap + $Lib fgdlib + $Lib mathlib + $Lib tier2 + $Lib vtf + } + + $File "notes.txt" +} diff --git a/mp/src/utils/vbsp/worldvertextransitionfixup.cpp b/mp/src/utils/vbsp/worldvertextransitionfixup.cpp index 5d00f3f5..f97f3933 100644 --- a/mp/src/utils/vbsp/worldvertextransitionfixup.cpp +++ b/mp/src/utils/vbsp/worldvertextransitionfixup.cpp @@ -1,212 +1,212 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//============================================================================= - -#include "bsplib.h" -#include "vbsp.h" -#include "tier1/UtlBuffer.h" -#include "tier1/utlvector.h" -#include "KeyValues.h" -#include "materialpatch.h" - -struct entitySideList_t -{ - int firstBrushSide; - int brushSideCount; -}; - -static bool SideIsNotDispAndHasDispMaterial( int iSide ) -{ - side_t *pSide = &g_MainMap->brushsides[iSide]; - - // If it's a displacement, then it's fine to have a displacement-only material. - if ( pSide->pMapDisp ) - { - return false; - } - - pSide->texinfo; - - return true; -} - -static void BackSlashToForwardSlash( char *pname ) -{ - while ( *pname ) { - if ( *pname == '\\' ) - *pname = '/'; - pname++; - } -} - -//----------------------------------------------------------------------------- -// Generate patched material name -//----------------------------------------------------------------------------- -static void GeneratePatchedMaterialName( const char *pMaterialName, char *pBuffer, int nMaxLen ) -{ - int nLen = Q_snprintf( pBuffer, nMaxLen, "maps/%s/%s_wvt_patch", mapbase, pMaterialName ); - - Assert( nLen < TEXTURE_NAME_LENGTH - 1 ); - if ( nLen >= TEXTURE_NAME_LENGTH - 1 ) - { - Error( "Generated worldvertextransition patch name : %s too long! (max = %d)\n", pBuffer, TEXTURE_NAME_LENGTH ); - } - - BackSlashToForwardSlash( pBuffer ); - Q_strlower( pBuffer ); -} - -static void RemoveKey( KeyValues *kv, const char *pSubKeyName ) -{ - KeyValues *pSubKey = kv->FindKey( pSubKeyName ); - if( pSubKey ) - { - kv->RemoveSubKey( pSubKey ); - pSubKey->deleteThis(); - } -} - -void CreateWorldVertexTransitionPatchedMaterial( const char *pOriginalMaterialName, const char *pPatchedMaterialName ) -{ - KeyValues *kv = LoadMaterialKeyValues( pOriginalMaterialName, 0 ); - if( kv ) - { - // change shader to Lightmappedgeneric (from worldvertextransition*) - kv->SetName( "LightmappedGeneric" ); - // don't need no stinking $basetexture2 or any other second texture vars - RemoveKey( kv, "$basetexture2" ); - RemoveKey( kv, "$bumpmap2" ); - RemoveKey( kv, "$bumpframe2" ); - RemoveKey( kv, "$basetexture2noenvmap" ); - RemoveKey( kv, "$blendmodulatetexture" ); - RemoveKey( kv, "$maskedblending" ); - RemoveKey( kv, "$surfaceprop2" ); - // If we didn't want a basetexture on the first texture in the blend, we don't want an envmap at all. - KeyValues *basetexturenoenvmap = kv->FindKey( "$BASETEXTURENOENVMAP" ); - if( basetexturenoenvmap->GetInt() ) - { - RemoveKey( kv, "$envmap" ); - } - - Warning( "Patching WVT material: %s\n", pPatchedMaterialName ); - WriteMaterialKeyValuesToPak( pPatchedMaterialName, kv ); - } -} - -int CreateBrushVersionOfWorldVertexTransitionMaterial( int originalTexInfo ) -{ - // Don't make cubemap tex infos for nodes - if ( originalTexInfo == TEXINFO_NODE ) - return originalTexInfo; - - texinfo_t *pTexInfo = &texinfo[originalTexInfo]; - dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); - const char *pOriginalMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); - - // Get out of here if the originalTexInfo is already a patched wvt material - if ( Q_stristr( pOriginalMaterialName, "_wvt_patch" ) ) - return originalTexInfo; - - char patchedMaterialName[1024]; - GeneratePatchedMaterialName( pOriginalMaterialName, patchedMaterialName, 1024 ); -// Warning( "GeneratePatchedMaterialName: %s %s\n", pMaterialName, patchedMaterialName ); - - // Make sure the texdata doesn't already exist. - int nTexDataID = FindTexData( patchedMaterialName ); - bool bHasTexData = (nTexDataID != -1); - if( !bHasTexData ) - { - // Create the new vmt material file - CreateWorldVertexTransitionPatchedMaterial( pOriginalMaterialName, patchedMaterialName ); - - // Make a new texdata - nTexDataID = AddCloneTexData( pTexData, patchedMaterialName ); - } - - Assert( nTexDataID != -1 ); - - texinfo_t newTexInfo; - newTexInfo = *pTexInfo; - newTexInfo.texdata = nTexDataID; - - int nTexInfoID = -1; - - // See if we need to make a new texinfo - bool bHasTexInfo = false; - if( bHasTexData ) - { - nTexInfoID = FindTexInfo( newTexInfo ); - bHasTexInfo = (nTexInfoID != -1); - } - - // Make a new texinfo if we need to. - if( !bHasTexInfo ) - { - nTexInfoID = texinfo.AddToTail( newTexInfo ); - } - - Assert( nTexInfoID != -1 ); - return nTexInfoID; -} - -const char *GetShaderNameForTexInfo( int iTexInfo ) -{ - texinfo_t *pTexInfo = &texinfo[iTexInfo]; - dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); - const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); - MaterialSystemMaterial_t hMaterial = FindMaterial( pMaterialName, NULL, false ); - const char *pShaderName = GetMaterialShaderName( hMaterial ); - return pShaderName; -} - -void WorldVertexTransitionFixup( void ) -{ - CUtlVector sideList; - sideList.SetCount( g_MainMap->num_entities ); - int i; - for ( i = 0; i < g_MainMap->num_entities; i++ ) - { - sideList[i].firstBrushSide = 0; - sideList[i].brushSideCount = 0; - } - - for ( i = 0; i < g_MainMap->nummapbrushes; i++ ) - { - sideList[g_MainMap->mapbrushes[i].entitynum].brushSideCount += g_MainMap->mapbrushes[i].numsides; - } - int curSide = 0; - for ( i = 0; i < g_MainMap->num_entities; i++ ) - { - sideList[i].firstBrushSide = curSide; - curSide += sideList[i].brushSideCount; - } - - int currentEntity = 0; - for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide ) - { - side_t *pSide = &g_MainMap->brushsides[iSide]; - - // skip displacments - if ( pSide->pMapDisp ) - continue; - - if( pSide->texinfo < 0 ) - continue; - - const char *pShaderName = GetShaderNameForTexInfo( pSide->texinfo ); - if ( !pShaderName || !Q_stristr( pShaderName, "worldvertextransition" ) ) - { - continue; - } - - while ( currentEntity < g_MainMap->num_entities-1 && - iSide > sideList[currentEntity].firstBrushSide + sideList[currentEntity].brushSideCount ) - { - currentEntity++; - } - - pSide->texinfo = CreateBrushVersionOfWorldVertexTransitionMaterial( pSide->texinfo ); - } +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "bsplib.h" +#include "vbsp.h" +#include "tier1/UtlBuffer.h" +#include "tier1/utlvector.h" +#include "KeyValues.h" +#include "materialpatch.h" + +struct entitySideList_t +{ + int firstBrushSide; + int brushSideCount; +}; + +static bool SideIsNotDispAndHasDispMaterial( int iSide ) +{ + side_t *pSide = &g_MainMap->brushsides[iSide]; + + // If it's a displacement, then it's fine to have a displacement-only material. + if ( pSide->pMapDisp ) + { + return false; + } + + pSide->texinfo; + + return true; +} + +static void BackSlashToForwardSlash( char *pname ) +{ + while ( *pname ) { + if ( *pname == '\\' ) + *pname = '/'; + pname++; + } +} + +//----------------------------------------------------------------------------- +// Generate patched material name +//----------------------------------------------------------------------------- +static void GeneratePatchedMaterialName( const char *pMaterialName, char *pBuffer, int nMaxLen ) +{ + int nLen = Q_snprintf( pBuffer, nMaxLen, "maps/%s/%s_wvt_patch", mapbase, pMaterialName ); + + Assert( nLen < TEXTURE_NAME_LENGTH - 1 ); + if ( nLen >= TEXTURE_NAME_LENGTH - 1 ) + { + Error( "Generated worldvertextransition patch name : %s too long! (max = %d)\n", pBuffer, TEXTURE_NAME_LENGTH ); + } + + BackSlashToForwardSlash( pBuffer ); + Q_strlower( pBuffer ); +} + +static void RemoveKey( KeyValues *kv, const char *pSubKeyName ) +{ + KeyValues *pSubKey = kv->FindKey( pSubKeyName ); + if( pSubKey ) + { + kv->RemoveSubKey( pSubKey ); + pSubKey->deleteThis(); + } +} + +void CreateWorldVertexTransitionPatchedMaterial( const char *pOriginalMaterialName, const char *pPatchedMaterialName ) +{ + KeyValues *kv = LoadMaterialKeyValues( pOriginalMaterialName, 0 ); + if( kv ) + { + // change shader to Lightmappedgeneric (from worldvertextransition*) + kv->SetName( "LightmappedGeneric" ); + // don't need no stinking $basetexture2 or any other second texture vars + RemoveKey( kv, "$basetexture2" ); + RemoveKey( kv, "$bumpmap2" ); + RemoveKey( kv, "$bumpframe2" ); + RemoveKey( kv, "$basetexture2noenvmap" ); + RemoveKey( kv, "$blendmodulatetexture" ); + RemoveKey( kv, "$maskedblending" ); + RemoveKey( kv, "$surfaceprop2" ); + // If we didn't want a basetexture on the first texture in the blend, we don't want an envmap at all. + KeyValues *basetexturenoenvmap = kv->FindKey( "$BASETEXTURENOENVMAP" ); + if( basetexturenoenvmap->GetInt() ) + { + RemoveKey( kv, "$envmap" ); + } + + Warning( "Patching WVT material: %s\n", pPatchedMaterialName ); + WriteMaterialKeyValuesToPak( pPatchedMaterialName, kv ); + } +} + +int CreateBrushVersionOfWorldVertexTransitionMaterial( int originalTexInfo ) +{ + // Don't make cubemap tex infos for nodes + if ( originalTexInfo == TEXINFO_NODE ) + return originalTexInfo; + + texinfo_t *pTexInfo = &texinfo[originalTexInfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + const char *pOriginalMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + + // Get out of here if the originalTexInfo is already a patched wvt material + if ( Q_stristr( pOriginalMaterialName, "_wvt_patch" ) ) + return originalTexInfo; + + char patchedMaterialName[1024]; + GeneratePatchedMaterialName( pOriginalMaterialName, patchedMaterialName, 1024 ); +// Warning( "GeneratePatchedMaterialName: %s %s\n", pMaterialName, patchedMaterialName ); + + // Make sure the texdata doesn't already exist. + int nTexDataID = FindTexData( patchedMaterialName ); + bool bHasTexData = (nTexDataID != -1); + if( !bHasTexData ) + { + // Create the new vmt material file + CreateWorldVertexTransitionPatchedMaterial( pOriginalMaterialName, patchedMaterialName ); + + // Make a new texdata + nTexDataID = AddCloneTexData( pTexData, patchedMaterialName ); + } + + Assert( nTexDataID != -1 ); + + texinfo_t newTexInfo; + newTexInfo = *pTexInfo; + newTexInfo.texdata = nTexDataID; + + int nTexInfoID = -1; + + // See if we need to make a new texinfo + bool bHasTexInfo = false; + if( bHasTexData ) + { + nTexInfoID = FindTexInfo( newTexInfo ); + bHasTexInfo = (nTexInfoID != -1); + } + + // Make a new texinfo if we need to. + if( !bHasTexInfo ) + { + nTexInfoID = texinfo.AddToTail( newTexInfo ); + } + + Assert( nTexInfoID != -1 ); + return nTexInfoID; +} + +const char *GetShaderNameForTexInfo( int iTexInfo ) +{ + texinfo_t *pTexInfo = &texinfo[iTexInfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + MaterialSystemMaterial_t hMaterial = FindMaterial( pMaterialName, NULL, false ); + const char *pShaderName = GetMaterialShaderName( hMaterial ); + return pShaderName; +} + +void WorldVertexTransitionFixup( void ) +{ + CUtlVector sideList; + sideList.SetCount( g_MainMap->num_entities ); + int i; + for ( i = 0; i < g_MainMap->num_entities; i++ ) + { + sideList[i].firstBrushSide = 0; + sideList[i].brushSideCount = 0; + } + + for ( i = 0; i < g_MainMap->nummapbrushes; i++ ) + { + sideList[g_MainMap->mapbrushes[i].entitynum].brushSideCount += g_MainMap->mapbrushes[i].numsides; + } + int curSide = 0; + for ( i = 0; i < g_MainMap->num_entities; i++ ) + { + sideList[i].firstBrushSide = curSide; + curSide += sideList[i].brushSideCount; + } + + int currentEntity = 0; + for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide ) + { + side_t *pSide = &g_MainMap->brushsides[iSide]; + + // skip displacments + if ( pSide->pMapDisp ) + continue; + + if( pSide->texinfo < 0 ) + continue; + + const char *pShaderName = GetShaderNameForTexInfo( pSide->texinfo ); + if ( !pShaderName || !Q_stristr( pShaderName, "worldvertextransition" ) ) + { + continue; + } + + while ( currentEntity < g_MainMap->num_entities-1 && + iSide > sideList[currentEntity].firstBrushSide + sideList[currentEntity].brushSideCount ) + { + currentEntity++; + } + + pSide->texinfo = CreateBrushVersionOfWorldVertexTransitionMaterial( pSide->texinfo ); + } } \ No newline at end of file diff --git a/mp/src/utils/vbsp/worldvertextransitionfixup.h b/mp/src/utils/vbsp/worldvertextransitionfixup.h index 6297dca5..9a1f0d38 100644 --- a/mp/src/utils/vbsp/worldvertextransitionfixup.h +++ b/mp/src/utils/vbsp/worldvertextransitionfixup.h @@ -1,15 +1,15 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//============================================================================= - -#ifndef WORLDVERTEXTRANSITIONFIXUP_H -#define WORLDVERTEXTRANSITIONFIXUP_H -#ifdef _WIN32 -#pragma once -#endif - -void WorldVertexTransitionFixup( void ); - -#endif // WORLDVERTEXTRANSITIONFIXUP_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef WORLDVERTEXTRANSITIONFIXUP_H +#define WORLDVERTEXTRANSITIONFIXUP_H +#ifdef _WIN32 +#pragma once +#endif + +void WorldVertexTransitionFixup( void ); + +#endif // WORLDVERTEXTRANSITIONFIXUP_H diff --git a/mp/src/utils/vbsp/writebsp.cpp b/mp/src/utils/vbsp/writebsp.cpp index c776bede..60d05980 100644 --- a/mp/src/utils/vbsp/writebsp.cpp +++ b/mp/src/utils/vbsp/writebsp.cpp @@ -1,1552 +1,1552 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "vbsp.h" -#include "disp_vbsp.h" -#include "utlvector.h" -#include "faces.h" -#include "builddisp.h" -#include "tier1/strtools.h" -#include "utilmatlib.h" -#include "utldict.h" -#include "map.h" - -int c_nofaces; -int c_facenodes; - -// NOTE: This is a global used to link faces back to the tree node/portals they came from -// it's used when filling water volumes -node_t *dfacenodes[MAX_MAP_FACES]; - - -/* -========================================================= - -ONLY SAVE OUT PLANES THAT ARE ACTUALLY USED AS NODES - -========================================================= -*/ - -void EmitFaceVertexes (face_t **list, face_t *f); -void AssignOccluderAreas(); - -/* -============ -EmitPlanes - -There is no oportunity to discard planes, because all of the original -brushes will be saved in the map. -============ -*/ -void EmitPlanes (void) -{ - int i; - dplane_t *dp; - plane_t *mp; - int planetranslate[MAX_MAP_PLANES]; - - mp = g_MainMap->mapplanes; - for (i=0 ; inummapplanes ; i++, mp++) - { - dp = &dplanes[numplanes]; - planetranslate[i] = numplanes; - VectorCopy ( mp->normal, dp->normal); - dp->dist = mp->dist; - dp->type = mp->type; - numplanes++; - } -} - - -//======================================================== - -void EmitMarkFace (dleaf_t *leaf_p, face_t *f) -{ - int i; - int facenum; - - while (f->merged) - f = f->merged; - - if (f->split[0]) - { - EmitMarkFace (leaf_p, f->split[0]); - EmitMarkFace (leaf_p, f->split[1]); - return; - } - - facenum = f->outputnumber; - if (facenum == -1) - return; // degenerate face - - if (facenum < 0 || facenum >= numfaces) - Error ("Bad leafface"); - for (i=leaf_p->firstleafface ; i= MAX_MAP_LEAFFACES) - Error ("Too many detail brush faces, max = %d\n", MAX_MAP_LEAFFACES); - - dleaffaces[numleaffaces] = facenum; - numleaffaces++; - } - -} - - -/* -================== -EmitLeaf -================== -*/ -void EmitLeaf (node_t *node) -{ - dleaf_t *leaf_p; - portal_t *p; - int s; - face_t *f; - bspbrush_t *b; - int i; - int brushnum; - leafface_t *pList; - - // emit a leaf - if (numleafs >= MAX_MAP_LEAFS) - Error ("Too many BSP leaves, max = %d", MAX_MAP_LEAFS); - - node->diskId = numleafs; - leaf_p = &dleafs[numleafs]; - numleafs++; - - if( nummodels == 0 ) - { - leaf_p->cluster = node->cluster; - } - else - { - // Submodels don't have clusters. If this isn't set to -1 here, then there - // will be multiple leaves (albeit from different models) that reference - // the same cluster and parts of the code like ivp.cpp's ConvertWaterModelToPhysCollide - // won't work. - leaf_p->cluster = -1; - } - - leaf_p->contents = node->contents; - leaf_p->area = node->area; - - // By default, assume the leaf can see the skybox. - // VRAD will do the actual computation to see if it really can see the skybox - leaf_p->flags = LEAF_FLAGS_SKY; - - // - // write bounding box info - // - VECTOR_COPY (node->mins, leaf_p->mins); - VECTOR_COPY (node->maxs, leaf_p->maxs); - - // - // write the leafbrushes - // - leaf_p->firstleafbrush = numleafbrushes; - for (b=node->brushlist ; b ; b=b->next) - { - if (numleafbrushes >= MAX_MAP_LEAFBRUSHES) - Error ("Too many brushes in one leaf, max = %d", MAX_MAP_LEAFBRUSHES); - - brushnum = b->original - g_MainMap->mapbrushes; - for (i=leaf_p->firstleafbrush ; inumleafbrushes = numleafbrushes - leaf_p->firstleafbrush; - - // - // write the leaffaces - // - if (leaf_p->contents & CONTENTS_SOLID) - return; // no leaffaces in solids - - leaf_p->firstleafface = numleaffaces; - - for (p = node->portals ; p ; p = p->next[s]) - { - s = (p->nodes[1] == node); - f = p->face[s]; - if (!f) - continue; // not a visible portal - - EmitMarkFace (leaf_p, f); - } - - // emit the detail faces - for ( pList = node->leaffacelist; pList; pList = pList->pNext ) - { - EmitMarkFace( leaf_p, pList->pFace ); - } - - - leaf_p->numleaffaces = numleaffaces - leaf_p->firstleafface; -} - -// per face plane - original face "side" list -side_t *pOrigFaceSideList[MAX_MAP_PLANES]; - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int CreateOrigFace( face_t *f ) -{ - int i, j; - dface_t *of; - side_t *side; - int vIndices[128]; - int eIndex[2]; - winding_t *pWinding; - - // not a real face! - if( !f->w ) - return -1; - - // get the original face -- the "side" - side = f->originalface; - - // get the original face winding - if( !side->winding ) - { - return -1; - } - - // - // get the next original face - // - if( numorigfaces >= MAX_MAP_FACES ) - Error( "Too many faces in map, max = %d", MAX_MAP_FACES ); - of = &dorigfaces[numorigfaces]; - numorigfaces++; - - // set original face to -1 -- it is an origianl face! - of->origFace = -1; - - // - // add side to plane list - // - side->next = pOrigFaceSideList[f->planenum]; - pOrigFaceSideList[f->planenum] = side; - side->origIndex = numorigfaces - 1; - - pWinding = CopyWinding( side->winding ); - - // - // plane info - // - of->planenum = side->planenum; - if ( side->contents & CONTENTS_DETAIL ) - of->onNode = 0; - else - of->onNode = 1; - of->side = side->planenum & 1; - - // - // edge info - // - of->firstedge = numsurfedges; - of->numedges = side->winding->numpoints; - - // - // material info - // - of->texinfo = side->texinfo; - of->dispinfo = f->dispinfo; - - // - // save the vertices - // - for( i = 0; i < pWinding->numpoints; i++ ) - { - // - // compare vertices - // - vIndices[i] = GetVertexnum( pWinding->p[i] ); - } - - // - // save off points -- as edges - // - for( i = 0; i < pWinding->numpoints; i++ ) - { - // - // look for matching edges first - // - eIndex[0] = vIndices[i]; - eIndex[1] = vIndices[(i+1)%pWinding->numpoints]; - - for( j = firstmodeledge; j < numedges; j++ ) - { - if( ( eIndex[0] == dedges[j].v[1] ) && - ( eIndex[1] == dedges[j].v[0] ) && - ( edgefaces[j][0]->contents == f->contents ) ) - { - // check for multiple backward edges!! -- shouldn't have - if( edgefaces[j][1] ) - continue; - - // set back edge - edgefaces[j][1] = f; - - // - // get next surface edge - // - if( numsurfedges >= MAX_MAP_SURFEDGES ) - Error( "Too much brush geometry in bsp, numsurfedges == MAX_MAP_SURFEDGES" ); - dsurfedges[numsurfedges] = -j; - numsurfedges++; - break; - } - } - - if( j == numedges ) - { - // - // get next edge - // - AddEdge( eIndex[0], eIndex[1], f ); - - // - // get next surface edge - // - if( numsurfedges >= MAX_MAP_SURFEDGES ) - Error( "Too much brush geometry in bsp, numsurfedges == MAX_MAP_SURFEDGES" ); - dsurfedges[numsurfedges] = ( numedges - 1 ); - numsurfedges++; - } - } - - // return the index - return ( numorigfaces - 1 ); -} - - -//----------------------------------------------------------------------------- -// Purpose: search for a face within the origface list and return the index if -// found -// Input: f - the face to compare -// Output: the index of the face it found, -1 if not found -//----------------------------------------------------------------------------- -int FindOrigFace( face_t *f ) -{ - int i; - static int bClear = 0; - side_t *pSide; - - // - // initially clear the face side lists (per face plane) - // - if( !bClear ) - { - for( i = 0; i < MAX_MAP_PLANES; i++ ) - { - pOrigFaceSideList[i] = NULL; - } - bClear = 1; - } - - // - // compare the sides - // - for( pSide = pOrigFaceSideList[f->planenum]; pSide; pSide = pSide->next ) - { - if( pSide == f->originalface ) - return pSide->origIndex; - } - - // original face not found in list - return -1; -} - - -//----------------------------------------------------------------------------- -// Purpose: to find an the original face within the list of original faces, if -// a match is not found then create a new origFace -- either way pass -// back the index of the origface in the list -// Input: f - face containing the original face information -// Output: the index of the origface in the origface list -//----------------------------------------------------------------------------- -int FindOrCreateOrigFace( face_t *f ) -{ - int index; - - // check for an original face - if( !f->originalface ) - return -1; - - // - // find or create a orig face and return the index - // - index = FindOrigFace( f ); - - if( index == -1 ) - return CreateOrigFace( f ); - else if( index == -2 ) - return -1; - - return index; -} - -/* -================== -EmitFace -================== -*/ -void EmitFace( face_t *f, qboolean onNode ) -{ - dface_t *df; - int i; - int e; - -// void SubdivideFaceBySubdivSize( face_t *f ); // garymcthack -// SubdivideFaceBySubdivSize( f ); - - // set initial output number - f->outputnumber = -1; - - // degenerated - if( f->numpoints < 3 ) - return; - - // not a final face - if( f->merged || f->split[0] || f->split[1] ) - return; - - // don't emit NODRAW faces for runtime - if ( texinfo[f->texinfo].flags & SURF_NODRAW ) - { - // keep NODRAW terrain surfaces though - if ( f->dispinfo == -1 ) - return; - Warning("NODRAW on terrain surface!\n"); - } - - // save output number so leaffaces can use - f->outputnumber = numfaces; - - // - // get the next available .bsp face slot - // - if (numfaces >= MAX_MAP_FACES) - Error( "Too many faces in map, max = %d", MAX_MAP_FACES ); - df = &dfaces[numfaces]; - - // Save the correlation between dfaces and faces -- since dfaces doesnt have worldcraft face id - dfaceids.AddToTail(); - dfaceids[numfaces].hammerfaceid = f->originalface->id; - - numfaces++; - - // - // plane info - planenum is used by qlight, but not quake - // - df->planenum = f->planenum; - df->onNode = onNode; - df->side = f->planenum & 1; - - // - // material info - // - df->texinfo = f->texinfo; - df->dispinfo = f->dispinfo; - df->smoothingGroups = f->smoothingGroups; - - // save the original "side"/face data - df->origFace = FindOrCreateOrigFace( f ); - df->surfaceFogVolumeID = -1; - dfacenodes[numfaces-1] = f->fogVolumeLeaf; - if ( f->fogVolumeLeaf ) - { - Assert( f->fogVolumeLeaf->planenum == PLANENUM_LEAF ); - } - - // - // edge info - // - df->firstedge = numsurfedges; - df->numedges = f->numpoints; - - // UNDONE: Nodraw faces have no winding - revisit to see if this is necessary - if ( f->w ) - { - df->area = WindingArea( f->w ); - } - else - { - df->area = 0; - } - - df->firstPrimID = f->firstPrimID; - df->SetNumPrims( f->numPrims ); - df->SetDynamicShadowsEnabled( f->originalface->m_bDynamicShadowsEnabled ); - - // - // save off points -- as edges - // - for( i = 0; i < f->numpoints; i++ ) - { - //e = GetEdge (f->pts[i], f->pts[(i+1)%f->numpoints], f); - e = GetEdge2 (f->vertexnums[i], f->vertexnums[(i+1)%f->numpoints], f); - - if (numsurfedges >= MAX_MAP_SURFEDGES) - Error( "Too much brush geometry in bsp, numsurfedges == MAX_MAP_SURFEDGES" ); - dsurfedges[numsurfedges] = e; - numsurfedges++; - } - - // Create overlay face lists. - side_t *pSide = f->originalface; - if ( pSide ) - { - int nOverlayCount = pSide->aOverlayIds.Count(); - if ( nOverlayCount > 0 ) - { - Overlay_AddFaceToLists( ( numfaces - 1 ), pSide ); - } - - nOverlayCount = pSide->aWaterOverlayIds.Count(); - if ( nOverlayCount > 0 ) - { - OverlayTransition_AddFaceToLists( ( numfaces - 1 ), pSide ); - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: Emit all of the faces stored at the leaves (faces from detail brushes) -//----------------------------------------------------------------------------- -void EmitLeafFaces( face_t *pLeafFaceList ) -{ - face_t *f = pLeafFaceList; - while ( f ) - { - EmitFace( f, false ); - f = f->next; - } -} - -//----------------------------------------------------------------------------- -// Purpose: Free the list of faces stored at the leaves -//----------------------------------------------------------------------------- -void FreeLeafFaces( face_t *pLeafFaceList ) -{ - int count = 0; - face_t *f, *next; - - f = pLeafFaceList; - - while ( f ) - { - next = f->next; - FreeFace( f ); - f = next; - count++; - } -} - -/* -============ -EmitDrawingNode_r -============ -*/ -int EmitDrawNode_r (node_t *node) -{ - dnode_t *n; - face_t *f; - int i; - - if (node->planenum == PLANENUM_LEAF) - { - EmitLeaf (node); - return -numleafs; - } - - // emit a node - if (numnodes == MAX_MAP_NODES) - Error ("MAX_MAP_NODES"); - node->diskId = numnodes; - - n = &dnodes[numnodes]; - numnodes++; - - VECTOR_COPY (node->mins, n->mins); - VECTOR_COPY (node->maxs, n->maxs); - - if (node->planenum & 1) - Error ("WriteDrawNodes_r: odd planenum"); - n->planenum = node->planenum; - n->firstface = numfaces; - n->area = node->area; - - if (!node->faces) - c_nofaces++; - else - c_facenodes++; - - for (f=node->faces ; f ; f=f->next) - EmitFace (f, true); - - n->numfaces = numfaces - n->firstface; - - - // - // recursively output the other nodes - // - for (i=0 ; i<2 ; i++) - { - if (node->children[i]->planenum == PLANENUM_LEAF) - { - n->children[i] = -(numleafs + 1); - EmitLeaf (node->children[i]); - } - else - { - n->children[i] = numnodes; - EmitDrawNode_r (node->children[i]); - } - } - - return n - dnodes; -} - - -//========================================================= - -// This will generate a scratchpad file with the level's geometry in it and the noshadow faces drawn red. -// #define SCRATCHPAD_NO_SHADOW_FACES -#if defined( SCRATCHPAD_NO_SHADOW_FACES ) - #include "scratchpad_helpers.h" - IScratchPad3D *g_pPad; -#endif - - -void MarkNoShadowFaces() -{ -#if defined( SCRATCHPAD_NO_SHADOW_FACES ) - g_pPad = ScratchPad3D_Create(); - ScratchPad_DrawWorld( g_pPad, false, CSPColor(1,1,1,0.3) ); - - for ( int iFace=0; iFace < numfaces; iFace++ ) - { - dface_t *pFace = &dfaces[iFace]; - - if ( !pFace->AreDynamicShadowsEnabled() ) - { - ScratchPad_DrawFace( g_pPad, pFace, iFace, CSPColor(1,0,0,1), Vector(1,0,0) ); - ScratchPad_DrawFace( g_pPad, pFace, iFace, CSPColor(1,0,0,1), Vector(-1,0,0) ); - ScratchPad_DrawFace( g_pPad, pFace, iFace, CSPColor(1,0,0,1), Vector(0,1,0) ); - } - } - g_pPad->Release(); -#endif -} - -struct texinfomap_t -{ - int refCount; - int outputIndex; -}; -struct texdatamap_t -{ - int refCount; - int outputIndex; -}; - -// Find the best used texinfo to remap this brush side -int FindMatchingBrushSideTexinfo( int sideIndex, const texinfomap_t *pMap ) -{ - dbrushside_t &side = dbrushsides[sideIndex]; - // find one with the same flags & surfaceprops (even if the texture name is different) - int sideTexFlags = texinfo[side.texinfo].flags; - int sideTexData = texinfo[side.texinfo].texdata; - int sideSurfaceProp = g_SurfaceProperties[sideTexData]; - for ( int j = 0; j < texinfo.Count(); j++ ) - { - if ( pMap[j].refCount > 0 && - texinfo[j].flags == sideTexFlags && - g_SurfaceProperties[texinfo[j].texdata] == sideSurfaceProp ) - { - // found one - return j; - } - } - - // can't find a better match - return side.texinfo; -} - -// Remove all unused texinfos and rebuild array -void ComapctTexinfoArray( texinfomap_t *pMap ) -{ - CUtlVector old; - old.CopyArray( texinfo.Base(), texinfo.Count() ); - texinfo.RemoveAll(); - int firstSky = -1; - int first2DSky = -1; - for ( int i = 0; i < old.Count(); i++ ) - { - if ( !pMap[i].refCount ) - { - pMap[i].outputIndex = -1; - continue; - } - // only add one sky texinfo + one 2D sky texinfo - if ( old[i].flags & SURF_SKY2D ) - { - if ( first2DSky < 0 ) - { - first2DSky = texinfo.AddToTail( old[i] ); - } - pMap[i].outputIndex = first2DSky; - continue; - } - if ( old[i].flags & SURF_SKY ) - { - if ( firstSky < 0 ) - { - firstSky = texinfo.AddToTail( old[i] ); - } - pMap[i].outputIndex = firstSky; - continue; - } - pMap[i].outputIndex = texinfo.AddToTail( old[i] ); - } -} - -void CompactTexdataArray( texdatamap_t *pMap ) -{ - CUtlVector oldStringData; - oldStringData.CopyArray( g_TexDataStringData.Base(), g_TexDataStringData.Count() ); - g_TexDataStringData.RemoveAll(); - CUtlVector oldStringTable; - oldStringTable.CopyArray( g_TexDataStringTable.Base(), g_TexDataStringTable.Count() ); - g_TexDataStringTable.RemoveAll(); - CUtlVector oldTexData; - oldTexData.CopyArray( dtexdata, numtexdata ); - // clear current table and rebuild - numtexdata = 0; - for ( int i = 0; i < oldTexData.Count(); i++ ) - { - // unreferenced, note in map and skip - if ( !pMap[i].refCount ) - { - pMap[i].outputIndex = -1; - continue; - } - pMap[i].outputIndex = numtexdata; - - // get old string and re-add to table - const char *pString = &oldStringData[oldStringTable[oldTexData[i].nameStringTableID]]; - int nameIndex = TexDataStringTable_AddOrFindString( pString ); - // copy old texdata and fixup with new name in compacted table - dtexdata[numtexdata] = oldTexData[i]; - dtexdata[numtexdata].nameStringTableID = nameIndex; - numtexdata++; - } -} - -void CompactTexinfos() -{ - Msg("Compacting texture/material tables...\n"); - texinfomap_t *texinfoMap = new texinfomap_t[texinfo.Count()]; - texdatamap_t *texdataMap = new texdatamap_t[numtexdata]; - memset( texinfoMap, 0, sizeof(texinfoMap[0])*texinfo.Count() ); - memset( texdataMap, 0, sizeof(texdataMap[0])*numtexdata ); - int i; - // get texinfos referenced by faces - for ( i = 0; i < numfaces; i++ ) - { - texinfoMap[dfaces[i].texinfo].refCount++; - } - // get texinfos referenced by brush sides - for ( i = 0; i < numbrushsides; i++ ) - { - // not referenced by any visible geometry - Assert( dbrushsides[i].texinfo >= 0 ); - if ( !texinfoMap[dbrushsides[i].texinfo].refCount ) - { - dbrushsides[i].texinfo = FindMatchingBrushSideTexinfo( i, texinfoMap ); - // didn't find anything suitable, go ahead and reference it - if ( !texinfoMap[dbrushsides[i].texinfo].refCount ) - { - texinfoMap[dbrushsides[i].texinfo].refCount++; - } - } - } - // get texinfos referenced by overlays - for ( i = 0; i < g_nOverlayCount; i++ ) - { - texinfoMap[g_Overlays[i].nTexInfo].refCount++; - } - for ( i = 0; i < numleafwaterdata; i++ ) - { - if ( dleafwaterdata[i].surfaceTexInfoID >= 0 ) - { - texinfoMap[dleafwaterdata[i].surfaceTexInfoID].refCount++; - } - } - for ( i = 0; i < *pNumworldlights; i++ ) - { - if ( dworldlights[i].texinfo >= 0 ) - { - texinfoMap[dworldlights[i].texinfo].refCount++; - } - } - for ( i = 0; i < g_nWaterOverlayCount; i++ ) - { - if ( g_WaterOverlays[i].nTexInfo >= 0 ) - { - texinfoMap[g_WaterOverlays[i].nTexInfo].refCount++; - } - } - // reference all used texdatas - for ( i = 0; i < texinfo.Count(); i++ ) - { - if ( texinfoMap[i].refCount > 0 ) - { - texdataMap[texinfo[i].texdata].refCount++; - } - } - - int oldCount = texinfo.Count(); - int oldTexdataCount = numtexdata; - int oldTexdataString = g_TexDataStringData.Count(); - ComapctTexinfoArray( texinfoMap ); - CompactTexdataArray( texdataMap ); - for ( i = 0; i < texinfo.Count(); i++ ) - { - int mapIndex = texdataMap[texinfo[i].texdata].outputIndex; - Assert( mapIndex >= 0 ); - texinfo[i].texdata = mapIndex; - //const char *pName = TexDataStringTable_GetString( dtexdata[texinfo[i].texdata].nameStringTableID ); - } - // remap texinfos on faces - for ( i = 0; i < numfaces; i++ ) - { - Assert( texinfoMap[dfaces[i].texinfo].outputIndex >= 0 ); - dfaces[i].texinfo = texinfoMap[dfaces[i].texinfo].outputIndex; - } - // remap texinfos on brushsides - for ( i = 0; i < numbrushsides; i++ ) - { - Assert( texinfoMap[dbrushsides[i].texinfo].outputIndex >= 0 ); - dbrushsides[i].texinfo = texinfoMap[dbrushsides[i].texinfo].outputIndex; - } - // remap texinfos on overlays - for ( i = 0; i < g_nOverlayCount; i++ ) - { - g_Overlays[i].nTexInfo = texinfoMap[g_Overlays[i].nTexInfo].outputIndex; - } - // remap leaf water data - for ( i = 0; i < numleafwaterdata; i++ ) - { - if ( dleafwaterdata[i].surfaceTexInfoID >= 0 ) - { - dleafwaterdata[i].surfaceTexInfoID = texinfoMap[dleafwaterdata[i].surfaceTexInfoID].outputIndex; - } - } - // remap world lights - for ( i = 0; i < *pNumworldlights; i++ ) - { - if ( dworldlights[i].texinfo >= 0 ) - { - dworldlights[i].texinfo = texinfoMap[dworldlights[i].texinfo].outputIndex; - } - } - // remap water overlays - for ( i = 0; i < g_nWaterOverlayCount; i++ ) - { - if ( g_WaterOverlays[i].nTexInfo >= 0 ) - { - g_WaterOverlays[i].nTexInfo = texinfoMap[g_WaterOverlays[i].nTexInfo].outputIndex; - } - } - - Msg("Reduced %d texinfos to %d\n", oldCount, texinfo.Count() ); - Msg("Reduced %d texdatas to %d (%d bytes to %d)\n", oldTexdataCount, numtexdata, oldTexdataString, g_TexDataStringData.Count() ); - - delete[] texinfoMap; - delete[] texdataMap; -} - -/* -============ -WriteBSP -============ -*/ -void WriteBSP (node_t *headnode, face_t *pLeafFaceList ) -{ - int i; - int oldfaces; - int oldorigfaces; - - c_nofaces = 0; - c_facenodes = 0; - - qprintf ("--- WriteBSP ---\n"); - - oldfaces = numfaces; - oldorigfaces = numorigfaces; - - GetEdge2_InitOptimizedList(); - EmitLeafFaces( pLeafFaceList ); - dmodels[nummodels].headnode = EmitDrawNode_r (headnode); - - // Only emit area portals for the main world. - if( nummodels == 0 ) - { - EmitAreaPortals (headnode); - } - - // - // add all displacement faces for the particular model - // - for( i = 0; i < nummapdispinfo; i++ ) - { - int entityIndex = GetDispInfoEntityNum( &mapdispinfo[i] ); - if( entityIndex == entity_num ) - { - EmitFaceVertexes( NULL, &mapdispinfo[i].face ); - EmitFace( &mapdispinfo[i].face, FALSE ); - } - } - - EmitWaterVolumesForBSP( &dmodels[nummodels], headnode ); - qprintf ("%5i nodes with faces\n", c_facenodes); - qprintf ("%5i nodes without faces\n", c_nofaces); - qprintf ("%5i faces\n", numfaces-oldfaces); - qprintf( "%5i original faces\n", numorigfaces-oldorigfaces ); -} - - - -//=========================================================== - -/* -============ -SetModelNumbers -============ -*/ -void SetModelNumbers (void) -{ - int i; - int models; - char value[10]; - - models = 1; - for (i=1 ; inummapbrushes; - - for (bnum=0 ; bnumnummapbrushes ; bnum++) - { - b = &g_MainMap->mapbrushes[bnum]; - db = &dbrushes[bnum]; - - db->contents = b->contents; - db->firstside = numbrushsides; - db->numsides = b->numsides; - for (j=0 ; jnumsides ; j++) - { - if (numbrushsides == MAX_MAP_BRUSHSIDES) - Error ("MAX_MAP_BRUSHSIDES"); - cp = &dbrushsides[numbrushsides]; - numbrushsides++; - cp->planenum = b->original_sides[j].planenum; - cp->texinfo = b->original_sides[j].texinfo; - if ( cp->texinfo == -1 ) - { - cp->texinfo = g_MainMap->g_ClipTexinfo; - } - cp->bevel = b->original_sides[j].bevel; - } - - // add any axis planes not contained in the brush to bevel off corners - for (x=0 ; x<3 ; x++) - for (s=-1 ; s<=1 ; s+=2) - { - // add the plane - VectorCopy (vec3_origin, normal); - normal[x] = s; - if (s == -1) - dist = -b->mins[x]; - else - dist = b->maxs[x]; - planenum = g_MainMap->FindFloatPlane (normal, dist); - for (i=0 ; inumsides ; i++) - if (b->original_sides[i].planenum == planenum) - break; - if (i == b->numsides) - { - if (numbrushsides >= MAX_MAP_BRUSHSIDES) - Error ("MAX_MAP_BRUSHSIDES"); - - dbrushsides[numbrushsides].planenum = planenum; - dbrushsides[numbrushsides].texinfo = - dbrushsides[numbrushsides-1].texinfo; - numbrushsides++; - db->numsides++; - } - } - } -} - - - -/* -================== -BeginBSPFile -================== -*/ -void BeginBSPFile (void) -{ - // these values may actually be initialized - // if the file existed when loaded, so clear them explicitly - nummodels = 0; - numfaces = 0; - numnodes = 0; - numbrushsides = 0; - numvertexes = 0; - numleaffaces = 0; - numleafbrushes = 0; - numsurfedges = 0; - - // edge 0 is not used, because 0 can't be negated - numedges = 1; - - // leave vertex 0 as an error - numvertexes = 1; - - // leave leaf 0 as an error - numleafs = 1; - dleafs[0].contents = CONTENTS_SOLID; - - // BUGBUG: This doesn't work! -#if 0 - // make a default empty leaf for the tracing code - memset( &dleafs[1], 0, sizeof(dleafs[1]) ); - dleafs[1].contents = CONTENTS_EMPTY; -#endif -} - -// We can't calculate this properly until vvis (since we need vis to do this), so we set -// to zero everywhere by default. -static void ClearDistToClosestWater( void ) -{ - int i; - for( i = 0; i < numleafs; i++ ) - { - g_LeafMinDistToWater[i] = 0; - } -} - - -void DiscoverMacroTextures() -{ - CUtlDict tempDict; - - g_FaceMacroTextureInfos.SetSize( numfaces ); - for ( int iFace=0; iFace < numfaces; iFace++ ) - { - texinfo_t *pTexInfo = &texinfo[dfaces[iFace].texinfo]; - if ( pTexInfo->texdata < 0 ) - continue; - - dtexdata_t *pTexData = &dtexdata[pTexInfo->texdata]; - const char *pMaterialName = &g_TexDataStringData[ g_TexDataStringTable[pTexData->nameStringTableID] ]; - - MaterialSystemMaterial_t hMaterial = FindMaterial( pMaterialName, NULL, false ); - - const char *pMacroTextureName = GetMaterialVar( hMaterial, "$macro_texture" ); - if ( pMacroTextureName ) - { - if ( tempDict.Find( pMacroTextureName ) == tempDict.InvalidIndex() ) - { - Msg( "-- DiscoverMacroTextures: %s\n", pMacroTextureName ); - tempDict.Insert( pMacroTextureName, 0 ); - } - - int stringID = TexDataStringTable_AddOrFindString( pMacroTextureName ); - g_FaceMacroTextureInfos[iFace].m_MacroTextureNameID = (unsigned short)stringID; - } - else - { - g_FaceMacroTextureInfos[iFace].m_MacroTextureNameID = 0xFFFF; - } - } -} - - -// Make sure that we have a water lod control entity if we have water in the map. -void EnsurePresenceOfWaterLODControlEntity( void ) -{ - extern bool g_bHasWater; - if( !g_bHasWater ) - { - // Don't bother if there isn't any water in the map. - return; - } - for( int i=0; i < num_entities; i++ ) - { - entity_t *e = &entities[i]; - - const char *pClassName = ValueForKey( e, "classname" ); - if( !Q_stricmp( pClassName, "water_lod_control" ) ) - { - // Found one!!!! - return; - } - } - - // None found, add one. - Warning( "Water found with no water_lod_control entity, creating a default one.\n" ); - - entity_t *mapent = &entities[num_entities]; - num_entities++; - memset(mapent, 0, sizeof(*mapent)); - mapent->firstbrush = g_MainMap->nummapbrushes; - mapent->numbrushes = 0; - - SetKeyValue( mapent, "classname", "water_lod_control" ); - SetKeyValue( mapent, "cheapwaterstartdistance", "1000" ); - SetKeyValue( mapent, "cheapwaterenddistance", "2000" ); -} - - -/* -============ -EndBSPFile -============ -*/ -void EndBSPFile (void) -{ - // Mark noshadow faces. - MarkNoShadowFaces(); - - EmitBrushes (); - EmitPlanes (); - - // stick flat normals at the verts - SaveVertexNormals(); - - // Figure out lightmap extents for all faces. - UpdateAllFaceLightmapExtents(); - - // Generate geometry and lightmap alpha for displacements. - EmitDispLMAlphaAndNeighbors(); - - // Emit overlay data. - Overlay_EmitOverlayFaces(); - OverlayTransition_EmitOverlayFaces(); - - // phys collision needs dispinfo to operate (needs to generate phys collision for displacement surfs) - EmitPhysCollision(); - - // We can't calculate this properly until vvis (since we need vis to do this), so we set - // to zero everywhere by default. - ClearDistToClosestWater(); - - // Emit static props found in the .vmf file - EmitStaticProps(); - - // Place detail props found in .vmf and based on material properties - EmitDetailObjects(); - - // Compute bounds after creating disp info because we need to reference it - ComputeBoundsNoSkybox(); - - // Make sure that we have a water lod control eneity if we have water in the map. - EnsurePresenceOfWaterLODControlEntity(); - - // Doing this here because stuff about may filter out entities - UnparseEntities (); - - // remove unused texinfos - CompactTexinfos(); - - // Figure out which faces want macro textures. - DiscoverMacroTextures(); - - char targetPath[1024]; - GetPlatformMapPath( source, targetPath, g_nDXLevel, 1024 ); - Msg ("Writing %s\n", targetPath); - WriteBSPFile (targetPath); -} - - -/* -================== -BeginModel -================== -*/ -int firstmodleaf; -void BeginModel (void) -{ - dmodel_t *mod; - int start, end; - mapbrush_t *b; - int j; - entity_t *e; - Vector mins, maxs; - - if (nummodels == MAX_MAP_MODELS) - Error ("Too many brush models in map, max = %d", MAX_MAP_MODELS); - mod = &dmodels[nummodels]; - - mod->firstface = numfaces; - - firstmodleaf = numleafs; - firstmodeledge = numedges; - firstmodelface = numfaces; - - // - // bound the brushes - // - e = &entities[entity_num]; - - start = e->firstbrush; - end = start + e->numbrushes; - ClearBounds (mins, maxs); - - for (j=start ; jmapbrushes[j]; - if (!b->numsides) - continue; // not a real brush (origin brush) - AddPointToBounds (b->mins, mins, maxs); - AddPointToBounds (b->maxs, mins, maxs); - } - - VectorCopy (mins, mod->mins); - VectorCopy (maxs, mod->maxs); -} - - -/* -================== -EndModel -================== -*/ -void EndModel (void) -{ - dmodel_t *mod; - - mod = &dmodels[nummodels]; - - mod->numfaces = numfaces - mod->firstface; - - nummodels++; -} - - - -//----------------------------------------------------------------------------- -// figure out which leaf a point is in -//----------------------------------------------------------------------------- -static int PointLeafnum_r (const Vector& p, int num) -{ - float d; - while (num >= 0) - { - dnode_t* node = dnodes + num; - dplane_t* plane = dplanes + node->planenum; - - if (plane->type < 3) - d = p[plane->type] - plane->dist; - else - d = DotProduct (plane->normal, p) - plane->dist; - if (d < 0) - num = node->children[1]; - else - num = node->children[0]; - } - - return -1 - num; -} - -int PointLeafnum ( dmodel_t* pModel, const Vector& p ) -{ - return PointLeafnum_r (p, pModel->headnode); -} - - -//----------------------------------------------------------------------------- -// Adds a noew to the bounding box -//----------------------------------------------------------------------------- -static void AddNodeToBounds(int node, CUtlVector& skipAreas, Vector& mins, Vector& maxs) -{ - // not a leaf - if (node >= 0) - { - AddNodeToBounds( dnodes[node].children[0], skipAreas, mins, maxs ); - AddNodeToBounds( dnodes[node].children[1], skipAreas, mins, maxs ); - } - else - { - int leaf = - 1 - node; - - // Don't bother with solid leaves - if (dleafs[leaf].contents & CONTENTS_SOLID) - return; - - // Skip 3D skybox - int i; - for ( i = skipAreas.Count(); --i >= 0; ) - { - if (dleafs[leaf].area == skipAreas[i]) - return; - } - - unsigned int firstface = dleafs[leaf].firstleafface; - for ( i = 0; i < dleafs[leaf].numleaffaces; ++i ) - { - unsigned int face = dleaffaces[ firstface + i ]; - - // Skip skyboxes + nodraw - texinfo_t& tex = texinfo[dfaces[face].texinfo]; - if (tex.flags & (SURF_SKY | SURF_NODRAW)) - continue; - - unsigned int firstedge = dfaces[face].firstedge; - Assert( firstedge >= 0 ); - - for (int j = 0; j < dfaces[face].numedges; ++j) - { - Assert( firstedge+j < numsurfedges ); - int edge = abs(dsurfedges[firstedge+j]); - dedge_t* pEdge = &dedges[edge]; - Assert( pEdge->v[0] >= 0 ); - Assert( pEdge->v[1] >= 0 ); - AddPointToBounds (dvertexes[pEdge->v[0]].point, mins, maxs); - AddPointToBounds (dvertexes[pEdge->v[1]].point, mins, maxs); - } - } - } -} - - -//----------------------------------------------------------------------------- -// Check to see if a displacement lives in any leaves that are not -// in the 3d skybox -//----------------------------------------------------------------------------- -bool IsBoxInsideWorld( int node, CUtlVector &skipAreas, const Vector &vecMins, const Vector &vecMaxs ) -{ - while( 1 ) - { - // leaf - if (node < 0) - { - // get the leaf - int leaf = - 1 - node; - - // Don't bother with solid leaves - if (dleafs[leaf].contents & CONTENTS_SOLID) - return false; - - // Skip 3D skybox - int i; - for ( i = skipAreas.Count(); --i >= 0; ) - { - if ( dleafs[leaf].area == skipAreas[i] ) - return false; - } - - return true; - } - - // - // get displacement bounding box position relative to the node plane - // - dnode_t *pNode = &dnodes[ node ]; - dplane_t *pPlane = &dplanes[ pNode->planenum ]; - - int sideResult = BrushBspBoxOnPlaneSide( vecMins, vecMaxs, pPlane ); - - // front side - if( sideResult == 1 ) - { - node = pNode->children[0]; - } - // back side - else if( sideResult == 2 ) - { - node = pNode->children[1]; - } - //split - else - { - if ( IsBoxInsideWorld( pNode->children[0], skipAreas, vecMins, vecMaxs ) ) - return true; - - node = pNode->children[1]; - } - } -} - - -//----------------------------------------------------------------------------- -// Adds the displacement surfaces in the world to the bounds -//----------------------------------------------------------------------------- -void AddDispsToBounds( int nHeadNode, CUtlVector& skipAreas, Vector &vecMins, Vector &vecMaxs ) -{ - Vector vecDispMins, vecDispMaxs; - - // first determine how many displacement surfaces there will be per leaf - int i; - for ( i = 0; i < g_dispinfo.Count(); ++i ) - { - ComputeDispInfoBounds( i, vecDispMins, vecDispMaxs ); - if ( IsBoxInsideWorld( nHeadNode, skipAreas, vecDispMins, vecDispMaxs ) ) - { - AddPointToBounds( vecDispMins, vecMins, vecMaxs ); - AddPointToBounds( vecDispMaxs, vecMins, vecMaxs ); - } - } -} - - -//----------------------------------------------------------------------------- -// Compute the bounding box, excluding 3D skybox + skybox, add it to keyvalues -//----------------------------------------------------------------------------- -void ComputeBoundsNoSkybox( ) -{ - // Iterate over all world leaves, skip those which are part of skybox - Vector mins, maxs; - ClearBounds (mins, maxs); - AddNodeToBounds( dmodels[0].headnode, g_SkyAreas, mins, maxs ); - AddDispsToBounds( dmodels[0].headnode, g_SkyAreas, mins, maxs ); - - // Add the bounds to the worldspawn data - for (int i = 0; i < num_entities; ++i) - { - char* pEntity = ValueForKey(&entities[i], "classname"); - if (!strcmp(pEntity, "worldspawn")) - { - char string[32]; - sprintf (string, "%i %i %i", (int)mins[0], (int)mins[1], (int)mins[2]); - SetKeyValue (&entities[i], "world_mins", string); - sprintf (string, "%i %i %i", (int)maxs[0], (int)maxs[1], (int)maxs[2]); - SetKeyValue (&entities[i], "world_maxs", string); - break; - } - } -} - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" +#include "disp_vbsp.h" +#include "utlvector.h" +#include "faces.h" +#include "builddisp.h" +#include "tier1/strtools.h" +#include "utilmatlib.h" +#include "utldict.h" +#include "map.h" + +int c_nofaces; +int c_facenodes; + +// NOTE: This is a global used to link faces back to the tree node/portals they came from +// it's used when filling water volumes +node_t *dfacenodes[MAX_MAP_FACES]; + + +/* +========================================================= + +ONLY SAVE OUT PLANES THAT ARE ACTUALLY USED AS NODES + +========================================================= +*/ + +void EmitFaceVertexes (face_t **list, face_t *f); +void AssignOccluderAreas(); + +/* +============ +EmitPlanes + +There is no oportunity to discard planes, because all of the original +brushes will be saved in the map. +============ +*/ +void EmitPlanes (void) +{ + int i; + dplane_t *dp; + plane_t *mp; + int planetranslate[MAX_MAP_PLANES]; + + mp = g_MainMap->mapplanes; + for (i=0 ; inummapplanes ; i++, mp++) + { + dp = &dplanes[numplanes]; + planetranslate[i] = numplanes; + VectorCopy ( mp->normal, dp->normal); + dp->dist = mp->dist; + dp->type = mp->type; + numplanes++; + } +} + + +//======================================================== + +void EmitMarkFace (dleaf_t *leaf_p, face_t *f) +{ + int i; + int facenum; + + while (f->merged) + f = f->merged; + + if (f->split[0]) + { + EmitMarkFace (leaf_p, f->split[0]); + EmitMarkFace (leaf_p, f->split[1]); + return; + } + + facenum = f->outputnumber; + if (facenum == -1) + return; // degenerate face + + if (facenum < 0 || facenum >= numfaces) + Error ("Bad leafface"); + for (i=leaf_p->firstleafface ; i= MAX_MAP_LEAFFACES) + Error ("Too many detail brush faces, max = %d\n", MAX_MAP_LEAFFACES); + + dleaffaces[numleaffaces] = facenum; + numleaffaces++; + } + +} + + +/* +================== +EmitLeaf +================== +*/ +void EmitLeaf (node_t *node) +{ + dleaf_t *leaf_p; + portal_t *p; + int s; + face_t *f; + bspbrush_t *b; + int i; + int brushnum; + leafface_t *pList; + + // emit a leaf + if (numleafs >= MAX_MAP_LEAFS) + Error ("Too many BSP leaves, max = %d", MAX_MAP_LEAFS); + + node->diskId = numleafs; + leaf_p = &dleafs[numleafs]; + numleafs++; + + if( nummodels == 0 ) + { + leaf_p->cluster = node->cluster; + } + else + { + // Submodels don't have clusters. If this isn't set to -1 here, then there + // will be multiple leaves (albeit from different models) that reference + // the same cluster and parts of the code like ivp.cpp's ConvertWaterModelToPhysCollide + // won't work. + leaf_p->cluster = -1; + } + + leaf_p->contents = node->contents; + leaf_p->area = node->area; + + // By default, assume the leaf can see the skybox. + // VRAD will do the actual computation to see if it really can see the skybox + leaf_p->flags = LEAF_FLAGS_SKY; + + // + // write bounding box info + // + VECTOR_COPY (node->mins, leaf_p->mins); + VECTOR_COPY (node->maxs, leaf_p->maxs); + + // + // write the leafbrushes + // + leaf_p->firstleafbrush = numleafbrushes; + for (b=node->brushlist ; b ; b=b->next) + { + if (numleafbrushes >= MAX_MAP_LEAFBRUSHES) + Error ("Too many brushes in one leaf, max = %d", MAX_MAP_LEAFBRUSHES); + + brushnum = b->original - g_MainMap->mapbrushes; + for (i=leaf_p->firstleafbrush ; inumleafbrushes = numleafbrushes - leaf_p->firstleafbrush; + + // + // write the leaffaces + // + if (leaf_p->contents & CONTENTS_SOLID) + return; // no leaffaces in solids + + leaf_p->firstleafface = numleaffaces; + + for (p = node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + f = p->face[s]; + if (!f) + continue; // not a visible portal + + EmitMarkFace (leaf_p, f); + } + + // emit the detail faces + for ( pList = node->leaffacelist; pList; pList = pList->pNext ) + { + EmitMarkFace( leaf_p, pList->pFace ); + } + + + leaf_p->numleaffaces = numleaffaces - leaf_p->firstleafface; +} + +// per face plane - original face "side" list +side_t *pOrigFaceSideList[MAX_MAP_PLANES]; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CreateOrigFace( face_t *f ) +{ + int i, j; + dface_t *of; + side_t *side; + int vIndices[128]; + int eIndex[2]; + winding_t *pWinding; + + // not a real face! + if( !f->w ) + return -1; + + // get the original face -- the "side" + side = f->originalface; + + // get the original face winding + if( !side->winding ) + { + return -1; + } + + // + // get the next original face + // + if( numorigfaces >= MAX_MAP_FACES ) + Error( "Too many faces in map, max = %d", MAX_MAP_FACES ); + of = &dorigfaces[numorigfaces]; + numorigfaces++; + + // set original face to -1 -- it is an origianl face! + of->origFace = -1; + + // + // add side to plane list + // + side->next = pOrigFaceSideList[f->planenum]; + pOrigFaceSideList[f->planenum] = side; + side->origIndex = numorigfaces - 1; + + pWinding = CopyWinding( side->winding ); + + // + // plane info + // + of->planenum = side->planenum; + if ( side->contents & CONTENTS_DETAIL ) + of->onNode = 0; + else + of->onNode = 1; + of->side = side->planenum & 1; + + // + // edge info + // + of->firstedge = numsurfedges; + of->numedges = side->winding->numpoints; + + // + // material info + // + of->texinfo = side->texinfo; + of->dispinfo = f->dispinfo; + + // + // save the vertices + // + for( i = 0; i < pWinding->numpoints; i++ ) + { + // + // compare vertices + // + vIndices[i] = GetVertexnum( pWinding->p[i] ); + } + + // + // save off points -- as edges + // + for( i = 0; i < pWinding->numpoints; i++ ) + { + // + // look for matching edges first + // + eIndex[0] = vIndices[i]; + eIndex[1] = vIndices[(i+1)%pWinding->numpoints]; + + for( j = firstmodeledge; j < numedges; j++ ) + { + if( ( eIndex[0] == dedges[j].v[1] ) && + ( eIndex[1] == dedges[j].v[0] ) && + ( edgefaces[j][0]->contents == f->contents ) ) + { + // check for multiple backward edges!! -- shouldn't have + if( edgefaces[j][1] ) + continue; + + // set back edge + edgefaces[j][1] = f; + + // + // get next surface edge + // + if( numsurfedges >= MAX_MAP_SURFEDGES ) + Error( "Too much brush geometry in bsp, numsurfedges == MAX_MAP_SURFEDGES" ); + dsurfedges[numsurfedges] = -j; + numsurfedges++; + break; + } + } + + if( j == numedges ) + { + // + // get next edge + // + AddEdge( eIndex[0], eIndex[1], f ); + + // + // get next surface edge + // + if( numsurfedges >= MAX_MAP_SURFEDGES ) + Error( "Too much brush geometry in bsp, numsurfedges == MAX_MAP_SURFEDGES" ); + dsurfedges[numsurfedges] = ( numedges - 1 ); + numsurfedges++; + } + } + + // return the index + return ( numorigfaces - 1 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: search for a face within the origface list and return the index if +// found +// Input: f - the face to compare +// Output: the index of the face it found, -1 if not found +//----------------------------------------------------------------------------- +int FindOrigFace( face_t *f ) +{ + int i; + static int bClear = 0; + side_t *pSide; + + // + // initially clear the face side lists (per face plane) + // + if( !bClear ) + { + for( i = 0; i < MAX_MAP_PLANES; i++ ) + { + pOrigFaceSideList[i] = NULL; + } + bClear = 1; + } + + // + // compare the sides + // + for( pSide = pOrigFaceSideList[f->planenum]; pSide; pSide = pSide->next ) + { + if( pSide == f->originalface ) + return pSide->origIndex; + } + + // original face not found in list + return -1; +} + + +//----------------------------------------------------------------------------- +// Purpose: to find an the original face within the list of original faces, if +// a match is not found then create a new origFace -- either way pass +// back the index of the origface in the list +// Input: f - face containing the original face information +// Output: the index of the origface in the origface list +//----------------------------------------------------------------------------- +int FindOrCreateOrigFace( face_t *f ) +{ + int index; + + // check for an original face + if( !f->originalface ) + return -1; + + // + // find or create a orig face and return the index + // + index = FindOrigFace( f ); + + if( index == -1 ) + return CreateOrigFace( f ); + else if( index == -2 ) + return -1; + + return index; +} + +/* +================== +EmitFace +================== +*/ +void EmitFace( face_t *f, qboolean onNode ) +{ + dface_t *df; + int i; + int e; + +// void SubdivideFaceBySubdivSize( face_t *f ); // garymcthack +// SubdivideFaceBySubdivSize( f ); + + // set initial output number + f->outputnumber = -1; + + // degenerated + if( f->numpoints < 3 ) + return; + + // not a final face + if( f->merged || f->split[0] || f->split[1] ) + return; + + // don't emit NODRAW faces for runtime + if ( texinfo[f->texinfo].flags & SURF_NODRAW ) + { + // keep NODRAW terrain surfaces though + if ( f->dispinfo == -1 ) + return; + Warning("NODRAW on terrain surface!\n"); + } + + // save output number so leaffaces can use + f->outputnumber = numfaces; + + // + // get the next available .bsp face slot + // + if (numfaces >= MAX_MAP_FACES) + Error( "Too many faces in map, max = %d", MAX_MAP_FACES ); + df = &dfaces[numfaces]; + + // Save the correlation between dfaces and faces -- since dfaces doesnt have worldcraft face id + dfaceids.AddToTail(); + dfaceids[numfaces].hammerfaceid = f->originalface->id; + + numfaces++; + + // + // plane info - planenum is used by qlight, but not quake + // + df->planenum = f->planenum; + df->onNode = onNode; + df->side = f->planenum & 1; + + // + // material info + // + df->texinfo = f->texinfo; + df->dispinfo = f->dispinfo; + df->smoothingGroups = f->smoothingGroups; + + // save the original "side"/face data + df->origFace = FindOrCreateOrigFace( f ); + df->surfaceFogVolumeID = -1; + dfacenodes[numfaces-1] = f->fogVolumeLeaf; + if ( f->fogVolumeLeaf ) + { + Assert( f->fogVolumeLeaf->planenum == PLANENUM_LEAF ); + } + + // + // edge info + // + df->firstedge = numsurfedges; + df->numedges = f->numpoints; + + // UNDONE: Nodraw faces have no winding - revisit to see if this is necessary + if ( f->w ) + { + df->area = WindingArea( f->w ); + } + else + { + df->area = 0; + } + + df->firstPrimID = f->firstPrimID; + df->SetNumPrims( f->numPrims ); + df->SetDynamicShadowsEnabled( f->originalface->m_bDynamicShadowsEnabled ); + + // + // save off points -- as edges + // + for( i = 0; i < f->numpoints; i++ ) + { + //e = GetEdge (f->pts[i], f->pts[(i+1)%f->numpoints], f); + e = GetEdge2 (f->vertexnums[i], f->vertexnums[(i+1)%f->numpoints], f); + + if (numsurfedges >= MAX_MAP_SURFEDGES) + Error( "Too much brush geometry in bsp, numsurfedges == MAX_MAP_SURFEDGES" ); + dsurfedges[numsurfedges] = e; + numsurfedges++; + } + + // Create overlay face lists. + side_t *pSide = f->originalface; + if ( pSide ) + { + int nOverlayCount = pSide->aOverlayIds.Count(); + if ( nOverlayCount > 0 ) + { + Overlay_AddFaceToLists( ( numfaces - 1 ), pSide ); + } + + nOverlayCount = pSide->aWaterOverlayIds.Count(); + if ( nOverlayCount > 0 ) + { + OverlayTransition_AddFaceToLists( ( numfaces - 1 ), pSide ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Emit all of the faces stored at the leaves (faces from detail brushes) +//----------------------------------------------------------------------------- +void EmitLeafFaces( face_t *pLeafFaceList ) +{ + face_t *f = pLeafFaceList; + while ( f ) + { + EmitFace( f, false ); + f = f->next; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Free the list of faces stored at the leaves +//----------------------------------------------------------------------------- +void FreeLeafFaces( face_t *pLeafFaceList ) +{ + int count = 0; + face_t *f, *next; + + f = pLeafFaceList; + + while ( f ) + { + next = f->next; + FreeFace( f ); + f = next; + count++; + } +} + +/* +============ +EmitDrawingNode_r +============ +*/ +int EmitDrawNode_r (node_t *node) +{ + dnode_t *n; + face_t *f; + int i; + + if (node->planenum == PLANENUM_LEAF) + { + EmitLeaf (node); + return -numleafs; + } + + // emit a node + if (numnodes == MAX_MAP_NODES) + Error ("MAX_MAP_NODES"); + node->diskId = numnodes; + + n = &dnodes[numnodes]; + numnodes++; + + VECTOR_COPY (node->mins, n->mins); + VECTOR_COPY (node->maxs, n->maxs); + + if (node->planenum & 1) + Error ("WriteDrawNodes_r: odd planenum"); + n->planenum = node->planenum; + n->firstface = numfaces; + n->area = node->area; + + if (!node->faces) + c_nofaces++; + else + c_facenodes++; + + for (f=node->faces ; f ; f=f->next) + EmitFace (f, true); + + n->numfaces = numfaces - n->firstface; + + + // + // recursively output the other nodes + // + for (i=0 ; i<2 ; i++) + { + if (node->children[i]->planenum == PLANENUM_LEAF) + { + n->children[i] = -(numleafs + 1); + EmitLeaf (node->children[i]); + } + else + { + n->children[i] = numnodes; + EmitDrawNode_r (node->children[i]); + } + } + + return n - dnodes; +} + + +//========================================================= + +// This will generate a scratchpad file with the level's geometry in it and the noshadow faces drawn red. +// #define SCRATCHPAD_NO_SHADOW_FACES +#if defined( SCRATCHPAD_NO_SHADOW_FACES ) + #include "scratchpad_helpers.h" + IScratchPad3D *g_pPad; +#endif + + +void MarkNoShadowFaces() +{ +#if defined( SCRATCHPAD_NO_SHADOW_FACES ) + g_pPad = ScratchPad3D_Create(); + ScratchPad_DrawWorld( g_pPad, false, CSPColor(1,1,1,0.3) ); + + for ( int iFace=0; iFace < numfaces; iFace++ ) + { + dface_t *pFace = &dfaces[iFace]; + + if ( !pFace->AreDynamicShadowsEnabled() ) + { + ScratchPad_DrawFace( g_pPad, pFace, iFace, CSPColor(1,0,0,1), Vector(1,0,0) ); + ScratchPad_DrawFace( g_pPad, pFace, iFace, CSPColor(1,0,0,1), Vector(-1,0,0) ); + ScratchPad_DrawFace( g_pPad, pFace, iFace, CSPColor(1,0,0,1), Vector(0,1,0) ); + } + } + g_pPad->Release(); +#endif +} + +struct texinfomap_t +{ + int refCount; + int outputIndex; +}; +struct texdatamap_t +{ + int refCount; + int outputIndex; +}; + +// Find the best used texinfo to remap this brush side +int FindMatchingBrushSideTexinfo( int sideIndex, const texinfomap_t *pMap ) +{ + dbrushside_t &side = dbrushsides[sideIndex]; + // find one with the same flags & surfaceprops (even if the texture name is different) + int sideTexFlags = texinfo[side.texinfo].flags; + int sideTexData = texinfo[side.texinfo].texdata; + int sideSurfaceProp = g_SurfaceProperties[sideTexData]; + for ( int j = 0; j < texinfo.Count(); j++ ) + { + if ( pMap[j].refCount > 0 && + texinfo[j].flags == sideTexFlags && + g_SurfaceProperties[texinfo[j].texdata] == sideSurfaceProp ) + { + // found one + return j; + } + } + + // can't find a better match + return side.texinfo; +} + +// Remove all unused texinfos and rebuild array +void ComapctTexinfoArray( texinfomap_t *pMap ) +{ + CUtlVector old; + old.CopyArray( texinfo.Base(), texinfo.Count() ); + texinfo.RemoveAll(); + int firstSky = -1; + int first2DSky = -1; + for ( int i = 0; i < old.Count(); i++ ) + { + if ( !pMap[i].refCount ) + { + pMap[i].outputIndex = -1; + continue; + } + // only add one sky texinfo + one 2D sky texinfo + if ( old[i].flags & SURF_SKY2D ) + { + if ( first2DSky < 0 ) + { + first2DSky = texinfo.AddToTail( old[i] ); + } + pMap[i].outputIndex = first2DSky; + continue; + } + if ( old[i].flags & SURF_SKY ) + { + if ( firstSky < 0 ) + { + firstSky = texinfo.AddToTail( old[i] ); + } + pMap[i].outputIndex = firstSky; + continue; + } + pMap[i].outputIndex = texinfo.AddToTail( old[i] ); + } +} + +void CompactTexdataArray( texdatamap_t *pMap ) +{ + CUtlVector oldStringData; + oldStringData.CopyArray( g_TexDataStringData.Base(), g_TexDataStringData.Count() ); + g_TexDataStringData.RemoveAll(); + CUtlVector oldStringTable; + oldStringTable.CopyArray( g_TexDataStringTable.Base(), g_TexDataStringTable.Count() ); + g_TexDataStringTable.RemoveAll(); + CUtlVector oldTexData; + oldTexData.CopyArray( dtexdata, numtexdata ); + // clear current table and rebuild + numtexdata = 0; + for ( int i = 0; i < oldTexData.Count(); i++ ) + { + // unreferenced, note in map and skip + if ( !pMap[i].refCount ) + { + pMap[i].outputIndex = -1; + continue; + } + pMap[i].outputIndex = numtexdata; + + // get old string and re-add to table + const char *pString = &oldStringData[oldStringTable[oldTexData[i].nameStringTableID]]; + int nameIndex = TexDataStringTable_AddOrFindString( pString ); + // copy old texdata and fixup with new name in compacted table + dtexdata[numtexdata] = oldTexData[i]; + dtexdata[numtexdata].nameStringTableID = nameIndex; + numtexdata++; + } +} + +void CompactTexinfos() +{ + Msg("Compacting texture/material tables...\n"); + texinfomap_t *texinfoMap = new texinfomap_t[texinfo.Count()]; + texdatamap_t *texdataMap = new texdatamap_t[numtexdata]; + memset( texinfoMap, 0, sizeof(texinfoMap[0])*texinfo.Count() ); + memset( texdataMap, 0, sizeof(texdataMap[0])*numtexdata ); + int i; + // get texinfos referenced by faces + for ( i = 0; i < numfaces; i++ ) + { + texinfoMap[dfaces[i].texinfo].refCount++; + } + // get texinfos referenced by brush sides + for ( i = 0; i < numbrushsides; i++ ) + { + // not referenced by any visible geometry + Assert( dbrushsides[i].texinfo >= 0 ); + if ( !texinfoMap[dbrushsides[i].texinfo].refCount ) + { + dbrushsides[i].texinfo = FindMatchingBrushSideTexinfo( i, texinfoMap ); + // didn't find anything suitable, go ahead and reference it + if ( !texinfoMap[dbrushsides[i].texinfo].refCount ) + { + texinfoMap[dbrushsides[i].texinfo].refCount++; + } + } + } + // get texinfos referenced by overlays + for ( i = 0; i < g_nOverlayCount; i++ ) + { + texinfoMap[g_Overlays[i].nTexInfo].refCount++; + } + for ( i = 0; i < numleafwaterdata; i++ ) + { + if ( dleafwaterdata[i].surfaceTexInfoID >= 0 ) + { + texinfoMap[dleafwaterdata[i].surfaceTexInfoID].refCount++; + } + } + for ( i = 0; i < *pNumworldlights; i++ ) + { + if ( dworldlights[i].texinfo >= 0 ) + { + texinfoMap[dworldlights[i].texinfo].refCount++; + } + } + for ( i = 0; i < g_nWaterOverlayCount; i++ ) + { + if ( g_WaterOverlays[i].nTexInfo >= 0 ) + { + texinfoMap[g_WaterOverlays[i].nTexInfo].refCount++; + } + } + // reference all used texdatas + for ( i = 0; i < texinfo.Count(); i++ ) + { + if ( texinfoMap[i].refCount > 0 ) + { + texdataMap[texinfo[i].texdata].refCount++; + } + } + + int oldCount = texinfo.Count(); + int oldTexdataCount = numtexdata; + int oldTexdataString = g_TexDataStringData.Count(); + ComapctTexinfoArray( texinfoMap ); + CompactTexdataArray( texdataMap ); + for ( i = 0; i < texinfo.Count(); i++ ) + { + int mapIndex = texdataMap[texinfo[i].texdata].outputIndex; + Assert( mapIndex >= 0 ); + texinfo[i].texdata = mapIndex; + //const char *pName = TexDataStringTable_GetString( dtexdata[texinfo[i].texdata].nameStringTableID ); + } + // remap texinfos on faces + for ( i = 0; i < numfaces; i++ ) + { + Assert( texinfoMap[dfaces[i].texinfo].outputIndex >= 0 ); + dfaces[i].texinfo = texinfoMap[dfaces[i].texinfo].outputIndex; + } + // remap texinfos on brushsides + for ( i = 0; i < numbrushsides; i++ ) + { + Assert( texinfoMap[dbrushsides[i].texinfo].outputIndex >= 0 ); + dbrushsides[i].texinfo = texinfoMap[dbrushsides[i].texinfo].outputIndex; + } + // remap texinfos on overlays + for ( i = 0; i < g_nOverlayCount; i++ ) + { + g_Overlays[i].nTexInfo = texinfoMap[g_Overlays[i].nTexInfo].outputIndex; + } + // remap leaf water data + for ( i = 0; i < numleafwaterdata; i++ ) + { + if ( dleafwaterdata[i].surfaceTexInfoID >= 0 ) + { + dleafwaterdata[i].surfaceTexInfoID = texinfoMap[dleafwaterdata[i].surfaceTexInfoID].outputIndex; + } + } + // remap world lights + for ( i = 0; i < *pNumworldlights; i++ ) + { + if ( dworldlights[i].texinfo >= 0 ) + { + dworldlights[i].texinfo = texinfoMap[dworldlights[i].texinfo].outputIndex; + } + } + // remap water overlays + for ( i = 0; i < g_nWaterOverlayCount; i++ ) + { + if ( g_WaterOverlays[i].nTexInfo >= 0 ) + { + g_WaterOverlays[i].nTexInfo = texinfoMap[g_WaterOverlays[i].nTexInfo].outputIndex; + } + } + + Msg("Reduced %d texinfos to %d\n", oldCount, texinfo.Count() ); + Msg("Reduced %d texdatas to %d (%d bytes to %d)\n", oldTexdataCount, numtexdata, oldTexdataString, g_TexDataStringData.Count() ); + + delete[] texinfoMap; + delete[] texdataMap; +} + +/* +============ +WriteBSP +============ +*/ +void WriteBSP (node_t *headnode, face_t *pLeafFaceList ) +{ + int i; + int oldfaces; + int oldorigfaces; + + c_nofaces = 0; + c_facenodes = 0; + + qprintf ("--- WriteBSP ---\n"); + + oldfaces = numfaces; + oldorigfaces = numorigfaces; + + GetEdge2_InitOptimizedList(); + EmitLeafFaces( pLeafFaceList ); + dmodels[nummodels].headnode = EmitDrawNode_r (headnode); + + // Only emit area portals for the main world. + if( nummodels == 0 ) + { + EmitAreaPortals (headnode); + } + + // + // add all displacement faces for the particular model + // + for( i = 0; i < nummapdispinfo; i++ ) + { + int entityIndex = GetDispInfoEntityNum( &mapdispinfo[i] ); + if( entityIndex == entity_num ) + { + EmitFaceVertexes( NULL, &mapdispinfo[i].face ); + EmitFace( &mapdispinfo[i].face, FALSE ); + } + } + + EmitWaterVolumesForBSP( &dmodels[nummodels], headnode ); + qprintf ("%5i nodes with faces\n", c_facenodes); + qprintf ("%5i nodes without faces\n", c_nofaces); + qprintf ("%5i faces\n", numfaces-oldfaces); + qprintf( "%5i original faces\n", numorigfaces-oldorigfaces ); +} + + + +//=========================================================== + +/* +============ +SetModelNumbers +============ +*/ +void SetModelNumbers (void) +{ + int i; + int models; + char value[10]; + + models = 1; + for (i=1 ; inummapbrushes; + + for (bnum=0 ; bnumnummapbrushes ; bnum++) + { + b = &g_MainMap->mapbrushes[bnum]; + db = &dbrushes[bnum]; + + db->contents = b->contents; + db->firstside = numbrushsides; + db->numsides = b->numsides; + for (j=0 ; jnumsides ; j++) + { + if (numbrushsides == MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + cp = &dbrushsides[numbrushsides]; + numbrushsides++; + cp->planenum = b->original_sides[j].planenum; + cp->texinfo = b->original_sides[j].texinfo; + if ( cp->texinfo == -1 ) + { + cp->texinfo = g_MainMap->g_ClipTexinfo; + } + cp->bevel = b->original_sides[j].bevel; + } + + // add any axis planes not contained in the brush to bevel off corners + for (x=0 ; x<3 ; x++) + for (s=-1 ; s<=1 ; s+=2) + { + // add the plane + VectorCopy (vec3_origin, normal); + normal[x] = s; + if (s == -1) + dist = -b->mins[x]; + else + dist = b->maxs[x]; + planenum = g_MainMap->FindFloatPlane (normal, dist); + for (i=0 ; inumsides ; i++) + if (b->original_sides[i].planenum == planenum) + break; + if (i == b->numsides) + { + if (numbrushsides >= MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + + dbrushsides[numbrushsides].planenum = planenum; + dbrushsides[numbrushsides].texinfo = + dbrushsides[numbrushsides-1].texinfo; + numbrushsides++; + db->numsides++; + } + } + } +} + + + +/* +================== +BeginBSPFile +================== +*/ +void BeginBSPFile (void) +{ + // these values may actually be initialized + // if the file existed when loaded, so clear them explicitly + nummodels = 0; + numfaces = 0; + numnodes = 0; + numbrushsides = 0; + numvertexes = 0; + numleaffaces = 0; + numleafbrushes = 0; + numsurfedges = 0; + + // edge 0 is not used, because 0 can't be negated + numedges = 1; + + // leave vertex 0 as an error + numvertexes = 1; + + // leave leaf 0 as an error + numleafs = 1; + dleafs[0].contents = CONTENTS_SOLID; + + // BUGBUG: This doesn't work! +#if 0 + // make a default empty leaf for the tracing code + memset( &dleafs[1], 0, sizeof(dleafs[1]) ); + dleafs[1].contents = CONTENTS_EMPTY; +#endif +} + +// We can't calculate this properly until vvis (since we need vis to do this), so we set +// to zero everywhere by default. +static void ClearDistToClosestWater( void ) +{ + int i; + for( i = 0; i < numleafs; i++ ) + { + g_LeafMinDistToWater[i] = 0; + } +} + + +void DiscoverMacroTextures() +{ + CUtlDict tempDict; + + g_FaceMacroTextureInfos.SetSize( numfaces ); + for ( int iFace=0; iFace < numfaces; iFace++ ) + { + texinfo_t *pTexInfo = &texinfo[dfaces[iFace].texinfo]; + if ( pTexInfo->texdata < 0 ) + continue; + + dtexdata_t *pTexData = &dtexdata[pTexInfo->texdata]; + const char *pMaterialName = &g_TexDataStringData[ g_TexDataStringTable[pTexData->nameStringTableID] ]; + + MaterialSystemMaterial_t hMaterial = FindMaterial( pMaterialName, NULL, false ); + + const char *pMacroTextureName = GetMaterialVar( hMaterial, "$macro_texture" ); + if ( pMacroTextureName ) + { + if ( tempDict.Find( pMacroTextureName ) == tempDict.InvalidIndex() ) + { + Msg( "-- DiscoverMacroTextures: %s\n", pMacroTextureName ); + tempDict.Insert( pMacroTextureName, 0 ); + } + + int stringID = TexDataStringTable_AddOrFindString( pMacroTextureName ); + g_FaceMacroTextureInfos[iFace].m_MacroTextureNameID = (unsigned short)stringID; + } + else + { + g_FaceMacroTextureInfos[iFace].m_MacroTextureNameID = 0xFFFF; + } + } +} + + +// Make sure that we have a water lod control entity if we have water in the map. +void EnsurePresenceOfWaterLODControlEntity( void ) +{ + extern bool g_bHasWater; + if( !g_bHasWater ) + { + // Don't bother if there isn't any water in the map. + return; + } + for( int i=0; i < num_entities; i++ ) + { + entity_t *e = &entities[i]; + + const char *pClassName = ValueForKey( e, "classname" ); + if( !Q_stricmp( pClassName, "water_lod_control" ) ) + { + // Found one!!!! + return; + } + } + + // None found, add one. + Warning( "Water found with no water_lod_control entity, creating a default one.\n" ); + + entity_t *mapent = &entities[num_entities]; + num_entities++; + memset(mapent, 0, sizeof(*mapent)); + mapent->firstbrush = g_MainMap->nummapbrushes; + mapent->numbrushes = 0; + + SetKeyValue( mapent, "classname", "water_lod_control" ); + SetKeyValue( mapent, "cheapwaterstartdistance", "1000" ); + SetKeyValue( mapent, "cheapwaterenddistance", "2000" ); +} + + +/* +============ +EndBSPFile +============ +*/ +void EndBSPFile (void) +{ + // Mark noshadow faces. + MarkNoShadowFaces(); + + EmitBrushes (); + EmitPlanes (); + + // stick flat normals at the verts + SaveVertexNormals(); + + // Figure out lightmap extents for all faces. + UpdateAllFaceLightmapExtents(); + + // Generate geometry and lightmap alpha for displacements. + EmitDispLMAlphaAndNeighbors(); + + // Emit overlay data. + Overlay_EmitOverlayFaces(); + OverlayTransition_EmitOverlayFaces(); + + // phys collision needs dispinfo to operate (needs to generate phys collision for displacement surfs) + EmitPhysCollision(); + + // We can't calculate this properly until vvis (since we need vis to do this), so we set + // to zero everywhere by default. + ClearDistToClosestWater(); + + // Emit static props found in the .vmf file + EmitStaticProps(); + + // Place detail props found in .vmf and based on material properties + EmitDetailObjects(); + + // Compute bounds after creating disp info because we need to reference it + ComputeBoundsNoSkybox(); + + // Make sure that we have a water lod control eneity if we have water in the map. + EnsurePresenceOfWaterLODControlEntity(); + + // Doing this here because stuff about may filter out entities + UnparseEntities (); + + // remove unused texinfos + CompactTexinfos(); + + // Figure out which faces want macro textures. + DiscoverMacroTextures(); + + char targetPath[1024]; + GetPlatformMapPath( source, targetPath, g_nDXLevel, 1024 ); + Msg ("Writing %s\n", targetPath); + WriteBSPFile (targetPath); +} + + +/* +================== +BeginModel +================== +*/ +int firstmodleaf; +void BeginModel (void) +{ + dmodel_t *mod; + int start, end; + mapbrush_t *b; + int j; + entity_t *e; + Vector mins, maxs; + + if (nummodels == MAX_MAP_MODELS) + Error ("Too many brush models in map, max = %d", MAX_MAP_MODELS); + mod = &dmodels[nummodels]; + + mod->firstface = numfaces; + + firstmodleaf = numleafs; + firstmodeledge = numedges; + firstmodelface = numfaces; + + // + // bound the brushes + // + e = &entities[entity_num]; + + start = e->firstbrush; + end = start + e->numbrushes; + ClearBounds (mins, maxs); + + for (j=start ; jmapbrushes[j]; + if (!b->numsides) + continue; // not a real brush (origin brush) + AddPointToBounds (b->mins, mins, maxs); + AddPointToBounds (b->maxs, mins, maxs); + } + + VectorCopy (mins, mod->mins); + VectorCopy (maxs, mod->maxs); +} + + +/* +================== +EndModel +================== +*/ +void EndModel (void) +{ + dmodel_t *mod; + + mod = &dmodels[nummodels]; + + mod->numfaces = numfaces - mod->firstface; + + nummodels++; +} + + + +//----------------------------------------------------------------------------- +// figure out which leaf a point is in +//----------------------------------------------------------------------------- +static int PointLeafnum_r (const Vector& p, int num) +{ + float d; + while (num >= 0) + { + dnode_t* node = dnodes + num; + dplane_t* plane = dplanes + node->planenum; + + if (plane->type < 3) + d = p[plane->type] - plane->dist; + else + d = DotProduct (plane->normal, p) - plane->dist; + if (d < 0) + num = node->children[1]; + else + num = node->children[0]; + } + + return -1 - num; +} + +int PointLeafnum ( dmodel_t* pModel, const Vector& p ) +{ + return PointLeafnum_r (p, pModel->headnode); +} + + +//----------------------------------------------------------------------------- +// Adds a noew to the bounding box +//----------------------------------------------------------------------------- +static void AddNodeToBounds(int node, CUtlVector& skipAreas, Vector& mins, Vector& maxs) +{ + // not a leaf + if (node >= 0) + { + AddNodeToBounds( dnodes[node].children[0], skipAreas, mins, maxs ); + AddNodeToBounds( dnodes[node].children[1], skipAreas, mins, maxs ); + } + else + { + int leaf = - 1 - node; + + // Don't bother with solid leaves + if (dleafs[leaf].contents & CONTENTS_SOLID) + return; + + // Skip 3D skybox + int i; + for ( i = skipAreas.Count(); --i >= 0; ) + { + if (dleafs[leaf].area == skipAreas[i]) + return; + } + + unsigned int firstface = dleafs[leaf].firstleafface; + for ( i = 0; i < dleafs[leaf].numleaffaces; ++i ) + { + unsigned int face = dleaffaces[ firstface + i ]; + + // Skip skyboxes + nodraw + texinfo_t& tex = texinfo[dfaces[face].texinfo]; + if (tex.flags & (SURF_SKY | SURF_NODRAW)) + continue; + + unsigned int firstedge = dfaces[face].firstedge; + Assert( firstedge >= 0 ); + + for (int j = 0; j < dfaces[face].numedges; ++j) + { + Assert( firstedge+j < numsurfedges ); + int edge = abs(dsurfedges[firstedge+j]); + dedge_t* pEdge = &dedges[edge]; + Assert( pEdge->v[0] >= 0 ); + Assert( pEdge->v[1] >= 0 ); + AddPointToBounds (dvertexes[pEdge->v[0]].point, mins, maxs); + AddPointToBounds (dvertexes[pEdge->v[1]].point, mins, maxs); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Check to see if a displacement lives in any leaves that are not +// in the 3d skybox +//----------------------------------------------------------------------------- +bool IsBoxInsideWorld( int node, CUtlVector &skipAreas, const Vector &vecMins, const Vector &vecMaxs ) +{ + while( 1 ) + { + // leaf + if (node < 0) + { + // get the leaf + int leaf = - 1 - node; + + // Don't bother with solid leaves + if (dleafs[leaf].contents & CONTENTS_SOLID) + return false; + + // Skip 3D skybox + int i; + for ( i = skipAreas.Count(); --i >= 0; ) + { + if ( dleafs[leaf].area == skipAreas[i] ) + return false; + } + + return true; + } + + // + // get displacement bounding box position relative to the node plane + // + dnode_t *pNode = &dnodes[ node ]; + dplane_t *pPlane = &dplanes[ pNode->planenum ]; + + int sideResult = BrushBspBoxOnPlaneSide( vecMins, vecMaxs, pPlane ); + + // front side + if( sideResult == 1 ) + { + node = pNode->children[0]; + } + // back side + else if( sideResult == 2 ) + { + node = pNode->children[1]; + } + //split + else + { + if ( IsBoxInsideWorld( pNode->children[0], skipAreas, vecMins, vecMaxs ) ) + return true; + + node = pNode->children[1]; + } + } +} + + +//----------------------------------------------------------------------------- +// Adds the displacement surfaces in the world to the bounds +//----------------------------------------------------------------------------- +void AddDispsToBounds( int nHeadNode, CUtlVector& skipAreas, Vector &vecMins, Vector &vecMaxs ) +{ + Vector vecDispMins, vecDispMaxs; + + // first determine how many displacement surfaces there will be per leaf + int i; + for ( i = 0; i < g_dispinfo.Count(); ++i ) + { + ComputeDispInfoBounds( i, vecDispMins, vecDispMaxs ); + if ( IsBoxInsideWorld( nHeadNode, skipAreas, vecDispMins, vecDispMaxs ) ) + { + AddPointToBounds( vecDispMins, vecMins, vecMaxs ); + AddPointToBounds( vecDispMaxs, vecMins, vecMaxs ); + } + } +} + + +//----------------------------------------------------------------------------- +// Compute the bounding box, excluding 3D skybox + skybox, add it to keyvalues +//----------------------------------------------------------------------------- +void ComputeBoundsNoSkybox( ) +{ + // Iterate over all world leaves, skip those which are part of skybox + Vector mins, maxs; + ClearBounds (mins, maxs); + AddNodeToBounds( dmodels[0].headnode, g_SkyAreas, mins, maxs ); + AddDispsToBounds( dmodels[0].headnode, g_SkyAreas, mins, maxs ); + + // Add the bounds to the worldspawn data + for (int i = 0; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!strcmp(pEntity, "worldspawn")) + { + char string[32]; + sprintf (string, "%i %i %i", (int)mins[0], (int)mins[1], (int)mins[2]); + SetKeyValue (&entities[i], "world_mins", string); + sprintf (string, "%i %i %i", (int)maxs[0], (int)maxs[1], (int)maxs[2]); + SetKeyValue (&entities[i], "world_maxs", string); + break; + } + } +} + + diff --git a/mp/src/utils/vbsp/writebsp.h b/mp/src/utils/vbsp/writebsp.h index e1ad084e..5dfce099 100644 --- a/mp/src/utils/vbsp/writebsp.h +++ b/mp/src/utils/vbsp/writebsp.h @@ -1,34 +1,34 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef WRITEBSP_H -#define WRITEBSP_H -#ifdef _WIN32 -#pragma once -#endif - -#include "bspfile.h" -#include "utlmap.h" - -struct node_t; - -//----------------------------------------------------------------------------- -// Emits occluder faces -//----------------------------------------------------------------------------- -void EmitOccluderFaces (node_t *node); - - -//----------------------------------------------------------------------------- -// Purpose: Free the list of faces stored at the leaves -//----------------------------------------------------------------------------- -void FreeLeafFaces( face_t *pLeafFaceList ); - -//----------------------------------------------------------------------------- -// Purpose: Make sure that we have a water lod control entity if we have water in the map. -//----------------------------------------------------------------------------- -void EnsurePresenceOfWaterLODControlEntity( void ); - -#endif // WRITEBSP_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef WRITEBSP_H +#define WRITEBSP_H +#ifdef _WIN32 +#pragma once +#endif + +#include "bspfile.h" +#include "utlmap.h" + +struct node_t; + +//----------------------------------------------------------------------------- +// Emits occluder faces +//----------------------------------------------------------------------------- +void EmitOccluderFaces (node_t *node); + + +//----------------------------------------------------------------------------- +// Purpose: Free the list of faces stored at the leaves +//----------------------------------------------------------------------------- +void FreeLeafFaces( face_t *pLeafFaceList ); + +//----------------------------------------------------------------------------- +// Purpose: Make sure that we have a water lod control entity if we have water in the map. +//----------------------------------------------------------------------------- +void EnsurePresenceOfWaterLODControlEntity( void ); + +#endif // WRITEBSP_H diff --git a/mp/src/utils/vice/vice.cpp b/mp/src/utils/vice/vice.cpp index 0ed9c32e..56c6d069 100644 --- a/mp/src/utils/vice/vice.cpp +++ b/mp/src/utils/vice/vice.cpp @@ -1,277 +1,277 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// vice.cpp : Defines the entry point for the console application. -// - -#include -#include -#include -#include -#include "tier1/strtools.h" -#include -#include "conio.h" -#include -#include -#include "UtlBuffer.h" -#include "tier0/dbg.h" -#include "cmdlib.h" -#include "tier0/icommandline.h" -#include "windows.h" - -#include "mathlib/IceKey.h" -#include - -#define FF_TRYAGAIN 1 -#define FF_DONTPROCESS 2 - -#undef GetCurrentDirectory - -static bool g_NoPause = false; -static bool g_Quiet = false; -static bool g_Encrypt = false; -static bool g_Decrypt = false; -static char g_ICEKey[16]; -static char g_Extension[16]; - -static void Pause( void ) -{ - if( !g_NoPause ) - { - printf( "Hit a key to continue\n" ); - getch(); - } -} - -static void Exit(const char *msg) -{ - fprintf( stderr, msg ); - Pause(); - exit( -1 ); -} - -static void Usage( void ) -{ - fprintf( stderr, "Usage: vice [-quiet] [-nopause] [-encrypt key] [-decrypt key] [-newext name] file file2 . . .\n" ); - fprintf( stderr, "-quiet : don't print anything out, don't pause for input\n" ); - fprintf( stderr, "-nopause : don't pause for input\n" ); - fprintf( stderr, "-encrypt : encrypt files with given key\n" ); - fprintf( stderr, "-decrypt : decypt files with given key\n" ); - fprintf( stderr, "-newext : new output file extension\n" ); - Pause(); - exit( -1 ); -} - - -bool Process_File( char *pInputBaseName, int maxlen ) -{ - Q_FixSlashes( pInputBaseName, '/' ); - // Q_StripExtension( pInputBaseName, pInputBaseName, maxlen ); - - if( !g_Quiet ) - { - printf( "input file: %s\n", pInputBaseName ); - } - - FileHandle_t f = g_pFullFileSystem->Open(pInputBaseName, "rb", "vice" ); - - if (!f) - Error("Could not open input file"); - - int fileSize = g_pFullFileSystem->Size(f); - - unsigned char *buffer = (unsigned char*)_alloca(fileSize); - - g_pFullFileSystem->Read(buffer, fileSize, f); // read into local buffer - g_pFullFileSystem->Close( f ); // close file after reading - - IceKey ice( 0 ); // level 0 = 64bit key - ice.set( (unsigned char*) g_ICEKey ); // set key - - int blockSize = ice.blockSize(); - - unsigned char *temp = (unsigned char *)_alloca( fileSize ); - unsigned char *p1 = buffer; - unsigned char *p2 = temp; - - // encrypt data in 8 byte blocks - int bytesLeft = fileSize; - while ( bytesLeft >= blockSize ) - { - if ( g_Encrypt ) - { - ice.encrypt( p1, p2 ); - } - else if ( g_Decrypt ) - { - ice.decrypt( p1, p2 ); - } - else - { - memcpy( p2, p1, blockSize ); - } - - bytesLeft -= blockSize; - p1+=blockSize; - p2+=blockSize; - } - - memcpy( p2, p1, bytesLeft ); - - Q_SetExtension( pInputBaseName, g_Extension, maxlen ); - - if( !g_Quiet ) - { - printf( "output file: %s\n", pInputBaseName ); - } - - f = g_pFullFileSystem->Open(pInputBaseName, "wb", "vice" ); - - if (!f) - Exit("Could not open output file"); - - g_pFullFileSystem->Write( temp, fileSize, f ); // read into local buffer - g_pFullFileSystem->Close( f ); // close file after reading - - return TRUE; -} - -int main(int argc, char* argv[]) -{ - CommandLine()->CreateCmdLine( argc, argv ); - - - if( argc < 2 ) - { - Usage(); - } - char *pInputBaseName = NULL; - int i = 1; - strcpy( g_Extension, ".dat" ); - while( i < argc ) - { - if( stricmp( argv[i], "-quiet" ) == 0 ) - { - i++; - g_Quiet = true; - g_NoPause = true; // no point in pausing if we aren't going to print anything out. - } - if( stricmp( argv[i], "-nopause" ) == 0 ) - { - i++; - g_NoPause = true; - } - if( stricmp( argv[i], "-encrypt" ) == 0 ) - { - g_Encrypt = true; - i++; - - if ( strlen( argv[i] ) != 8 ) - { - Exit("Error - ICE key must be a 8 char text.\n"); - } - - Q_strncpy( g_ICEKey, argv[i], sizeof(g_ICEKey) ); - i++; - - } - if( stricmp( argv[i], "-decrypt" ) == 0 ) - { - g_Decrypt = true; - i++; - - if ( strlen( argv[i] ) != 8 ) - { - Exit("Error - ICE key must be a 8 char text.\n"); - } - - Q_strncpy( g_ICEKey, argv[i], sizeof(g_ICEKey) ); - i++; - - } - if( stricmp( argv[i], "-newext" ) == 0 ) - { - i++; - - if ( strlen( argv[i] ) > 5 ) - { - Exit("Error - extension must be smaller than 4 chars.\n"); - } - - Q_strncpy( g_Extension, argv[i], sizeof(g_Extension) ); - i++; - - } - else - { - break; - } - } - - if ( i >= argc ) - { - Exit("Error - missing files in commandline.\n"); - } - - CmdLib_InitFileSystem( argv[i] ); - - g_pFullFileSystem->GetCurrentDirectory( gamedir, sizeof(gamedir) ); - g_pFullFileSystem->AddSearchPath( gamedir, "vice" ); - - - Q_FixSlashes( gamedir, '/' ); - - for( ; i < argc; i++ ) - { - pInputBaseName = argv[i]; - int maxlen = Q_strlen( pInputBaseName ) + 1; - - - if ( strstr( pInputBaseName, "*.") ) - { - char search[ MAX_PATH ]; - char fname[ MAX_PATH ]; - char ext[_MAX_EXT]; - - _splitpath( pInputBaseName, NULL, NULL, fname, ext ); //find extension wanted - fname[strlen(fname)-1] = 0; // remove * - - sprintf( search, "%s\\*%s", gamedir, ext ); - - Q_FixSlashes( search, '/' ); - - WIN32_FIND_DATA wfd; - HANDLE hResult; - memset(&wfd, 0, sizeof(WIN32_FIND_DATA)); - - hResult = FindFirstFile( search, &wfd ); - - while ( hResult != INVALID_HANDLE_VALUE ) - { - if ( !strnicmp( fname, wfd.cFileName, strlen(fname) ) ) - { - if ( !Process_File( wfd.cFileName, sizeof( wfd.cFileName ) ) ) - break; - } - - if ( !FindNextFile( hResult, &wfd) ) - break; - - } - - FindClose( hResult ); - } - else - { - Process_File( pInputBaseName, maxlen ); - } - } - - Pause(); - return 0; -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vice.cpp : Defines the entry point for the console application. +// + +#include +#include +#include +#include +#include "tier1/strtools.h" +#include +#include "conio.h" +#include +#include +#include "UtlBuffer.h" +#include "tier0/dbg.h" +#include "cmdlib.h" +#include "tier0/icommandline.h" +#include "windows.h" + +#include "mathlib/IceKey.h" +#include + +#define FF_TRYAGAIN 1 +#define FF_DONTPROCESS 2 + +#undef GetCurrentDirectory + +static bool g_NoPause = false; +static bool g_Quiet = false; +static bool g_Encrypt = false; +static bool g_Decrypt = false; +static char g_ICEKey[16]; +static char g_Extension[16]; + +static void Pause( void ) +{ + if( !g_NoPause ) + { + printf( "Hit a key to continue\n" ); + getch(); + } +} + +static void Exit(const char *msg) +{ + fprintf( stderr, msg ); + Pause(); + exit( -1 ); +} + +static void Usage( void ) +{ + fprintf( stderr, "Usage: vice [-quiet] [-nopause] [-encrypt key] [-decrypt key] [-newext name] file file2 . . .\n" ); + fprintf( stderr, "-quiet : don't print anything out, don't pause for input\n" ); + fprintf( stderr, "-nopause : don't pause for input\n" ); + fprintf( stderr, "-encrypt : encrypt files with given key\n" ); + fprintf( stderr, "-decrypt : decypt files with given key\n" ); + fprintf( stderr, "-newext : new output file extension\n" ); + Pause(); + exit( -1 ); +} + + +bool Process_File( char *pInputBaseName, int maxlen ) +{ + Q_FixSlashes( pInputBaseName, '/' ); + // Q_StripExtension( pInputBaseName, pInputBaseName, maxlen ); + + if( !g_Quiet ) + { + printf( "input file: %s\n", pInputBaseName ); + } + + FileHandle_t f = g_pFullFileSystem->Open(pInputBaseName, "rb", "vice" ); + + if (!f) + Error("Could not open input file"); + + int fileSize = g_pFullFileSystem->Size(f); + + unsigned char *buffer = (unsigned char*)_alloca(fileSize); + + g_pFullFileSystem->Read(buffer, fileSize, f); // read into local buffer + g_pFullFileSystem->Close( f ); // close file after reading + + IceKey ice( 0 ); // level 0 = 64bit key + ice.set( (unsigned char*) g_ICEKey ); // set key + + int blockSize = ice.blockSize(); + + unsigned char *temp = (unsigned char *)_alloca( fileSize ); + unsigned char *p1 = buffer; + unsigned char *p2 = temp; + + // encrypt data in 8 byte blocks + int bytesLeft = fileSize; + while ( bytesLeft >= blockSize ) + { + if ( g_Encrypt ) + { + ice.encrypt( p1, p2 ); + } + else if ( g_Decrypt ) + { + ice.decrypt( p1, p2 ); + } + else + { + memcpy( p2, p1, blockSize ); + } + + bytesLeft -= blockSize; + p1+=blockSize; + p2+=blockSize; + } + + memcpy( p2, p1, bytesLeft ); + + Q_SetExtension( pInputBaseName, g_Extension, maxlen ); + + if( !g_Quiet ) + { + printf( "output file: %s\n", pInputBaseName ); + } + + f = g_pFullFileSystem->Open(pInputBaseName, "wb", "vice" ); + + if (!f) + Exit("Could not open output file"); + + g_pFullFileSystem->Write( temp, fileSize, f ); // read into local buffer + g_pFullFileSystem->Close( f ); // close file after reading + + return TRUE; +} + +int main(int argc, char* argv[]) +{ + CommandLine()->CreateCmdLine( argc, argv ); + + + if( argc < 2 ) + { + Usage(); + } + char *pInputBaseName = NULL; + int i = 1; + strcpy( g_Extension, ".dat" ); + while( i < argc ) + { + if( stricmp( argv[i], "-quiet" ) == 0 ) + { + i++; + g_Quiet = true; + g_NoPause = true; // no point in pausing if we aren't going to print anything out. + } + if( stricmp( argv[i], "-nopause" ) == 0 ) + { + i++; + g_NoPause = true; + } + if( stricmp( argv[i], "-encrypt" ) == 0 ) + { + g_Encrypt = true; + i++; + + if ( strlen( argv[i] ) != 8 ) + { + Exit("Error - ICE key must be a 8 char text.\n"); + } + + Q_strncpy( g_ICEKey, argv[i], sizeof(g_ICEKey) ); + i++; + + } + if( stricmp( argv[i], "-decrypt" ) == 0 ) + { + g_Decrypt = true; + i++; + + if ( strlen( argv[i] ) != 8 ) + { + Exit("Error - ICE key must be a 8 char text.\n"); + } + + Q_strncpy( g_ICEKey, argv[i], sizeof(g_ICEKey) ); + i++; + + } + if( stricmp( argv[i], "-newext" ) == 0 ) + { + i++; + + if ( strlen( argv[i] ) > 5 ) + { + Exit("Error - extension must be smaller than 4 chars.\n"); + } + + Q_strncpy( g_Extension, argv[i], sizeof(g_Extension) ); + i++; + + } + else + { + break; + } + } + + if ( i >= argc ) + { + Exit("Error - missing files in commandline.\n"); + } + + CmdLib_InitFileSystem( argv[i] ); + + g_pFullFileSystem->GetCurrentDirectory( gamedir, sizeof(gamedir) ); + g_pFullFileSystem->AddSearchPath( gamedir, "vice" ); + + + Q_FixSlashes( gamedir, '/' ); + + for( ; i < argc; i++ ) + { + pInputBaseName = argv[i]; + int maxlen = Q_strlen( pInputBaseName ) + 1; + + + if ( strstr( pInputBaseName, "*.") ) + { + char search[ MAX_PATH ]; + char fname[ MAX_PATH ]; + char ext[_MAX_EXT]; + + _splitpath( pInputBaseName, NULL, NULL, fname, ext ); //find extension wanted + fname[strlen(fname)-1] = 0; // remove * + + sprintf( search, "%s\\*%s", gamedir, ext ); + + Q_FixSlashes( search, '/' ); + + WIN32_FIND_DATA wfd; + HANDLE hResult; + memset(&wfd, 0, sizeof(WIN32_FIND_DATA)); + + hResult = FindFirstFile( search, &wfd ); + + while ( hResult != INVALID_HANDLE_VALUE ) + { + if ( !strnicmp( fname, wfd.cFileName, strlen(fname) ) ) + { + if ( !Process_File( wfd.cFileName, sizeof( wfd.cFileName ) ) ) + break; + } + + if ( !FindNextFile( hResult, &wfd) ) + break; + + } + + FindClose( hResult ); + } + else + { + Process_File( pInputBaseName, maxlen ); + } + } + + Pause(); + return 0; +} + diff --git a/mp/src/utils/vice/vice.vpc b/mp/src/utils/vice/vice.vpc index 8a87d0bb..3e8532a7 100644 --- a/mp/src/utils/vice/vice.vpc +++ b/mp/src/utils/vice/vice.vpc @@ -1,41 +1,41 @@ -//----------------------------------------------------------------------------- -// VICE.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" - -$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" - -$Configuration -{ - $Compiler - { - $AdditionalIncludeDirectories "$BASE,..\common" - } -} - -$Project "Vice" -{ - $Folder "Source Files" - { - $File "..\common\cmdlib.cpp" - $File "$SRCDIR\public\filesystem_helpers.cpp" - $File "$SRCDIR\public\filesystem_init.cpp" - $File "..\common\filesystem_tools.cpp" - $File "vice.cpp" - } - - $Folder "Header Files" - { - $File "$SRCDIR\public\mathlib\IceKey.H" - } - - $Folder "Link Libraries" - { - $Lib tier2 - $Lib mathlib - } -} +//----------------------------------------------------------------------------- +// VICE.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\common" + } +} + +$Project "Vice" +{ + $Folder "Source Files" + { + $File "..\common\cmdlib.cpp" + $File "$SRCDIR\public\filesystem_helpers.cpp" + $File "$SRCDIR\public\filesystem_init.cpp" + $File "..\common\filesystem_tools.cpp" + $File "vice.cpp" + } + + $Folder "Header Files" + { + $File "$SRCDIR\public\mathlib\IceKey.H" + } + + $Folder "Link Libraries" + { + $Lib tier2 + $Lib mathlib + } +} diff --git a/mp/src/utils/vmpi/ichannel.h b/mp/src/utils/vmpi/ichannel.h index 42466cfc..a26f034c 100644 --- a/mp/src/utils/vmpi/ichannel.h +++ b/mp/src/utils/vmpi/ichannel.h @@ -1,49 +1,49 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef ICHANNEL_H -#define ICHANNEL_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "tier1/utlvector.h" - - -class IChannel -{ -public: - // Note: this also releases any channels contained inside. So if you make a reliable - // channel that contains an unreliable channel and release the reliable one, - // it will automatically release the unreliable one it contains. - virtual void Release() = 0; - - // Send data to the destination. - virtual bool Send( const void *pData, int len ) = 0; - - // This version puts all the chunks into one packet and ships it off. - virtual bool SendChunks( void const * const *pChunks, const int *pChunkLengths, int nChunks ) = 0; - - // Check for any packets coming in from the destination. - // Returns false if no packet was received. - // - // flTimeout can be used to make it wait for data. - // - // Note: this is most efficient if you keep the buffer around between calls so it only - // reallocates it when it needs more space. - virtual bool Recv( CUtlVector &data, double flTimeout=0 ) = 0; - - // Returns false if the connection has been broken. - virtual bool IsConnected() = 0; - - // If IsConnected returns false, you can call this to find out why the socket got disconnected. - virtual void GetDisconnectReason( CUtlVector &reason ) = 0; -}; - - -#endif // ICHANNEL_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ICHANNEL_H +#define ICHANNEL_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tier1/utlvector.h" + + +class IChannel +{ +public: + // Note: this also releases any channels contained inside. So if you make a reliable + // channel that contains an unreliable channel and release the reliable one, + // it will automatically release the unreliable one it contains. + virtual void Release() = 0; + + // Send data to the destination. + virtual bool Send( const void *pData, int len ) = 0; + + // This version puts all the chunks into one packet and ships it off. + virtual bool SendChunks( void const * const *pChunks, const int *pChunkLengths, int nChunks ) = 0; + + // Check for any packets coming in from the destination. + // Returns false if no packet was received. + // + // flTimeout can be used to make it wait for data. + // + // Note: this is most efficient if you keep the buffer around between calls so it only + // reallocates it when it needs more space. + virtual bool Recv( CUtlVector &data, double flTimeout=0 ) = 0; + + // Returns false if the connection has been broken. + virtual bool IsConnected() = 0; + + // If IsConnected returns false, you can call this to find out why the socket got disconnected. + virtual void GetDisconnectReason( CUtlVector &reason ) = 0; +}; + + +#endif // ICHANNEL_H diff --git a/mp/src/utils/vmpi/imysqlwrapper.h b/mp/src/utils/vmpi/imysqlwrapper.h index f5a8dfa3..26041288 100644 --- a/mp/src/utils/vmpi/imysqlwrapper.h +++ b/mp/src/utils/vmpi/imysqlwrapper.h @@ -1,115 +1,115 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef MYSQL_WRAPPER_H -#define MYSQL_WRAPPER_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "utlvector.h" -#include "interface.h" - - -class IMySQLRowSet; - - -class CColumnValue -{ -public: - - CColumnValue( IMySQLRowSet *pSQL, int iColumn ); - - const char* String(); - long Int32(); - -private: - IMySQLRowSet *m_pSQL; - int m_iColumn; -}; - - - -class IMySQLRowSet -{ -public: - virtual void Release() = 0; - - // Get the number of columns in the data returned from the last query (if it was a select statement). - virtual int NumFields() = 0; - - // Get the name of each column returned by the last query. - virtual const char* GetFieldName( int iColumn ) = 0; - - // Call this in a loop until it returns false to iterate over all rows the query returned. - virtual bool NextRow() = 0; - - // You can call this to start iterating over the result set from the start again. - // Note: after calling this, you have to call NextRow() to actually get the first row's value ready. - virtual bool SeekToFirstRow() = 0; - - virtual CColumnValue GetColumnValue( int iColumn ) = 0; - virtual CColumnValue GetColumnValue( const char *pColumnName ) = 0; - - virtual const char* GetColumnValue_String( int iColumn ) = 0; - virtual long GetColumnValue_Int( int iColumn ) = 0; - - // You can call this to get the index of a column for faster lookups with GetColumnValue( int ). - // Returns -1 if the column can't be found. - virtual int GetColumnIndex( const char *pColumnName ) = 0; -}; - - -class IMySQL : public IMySQLRowSet -{ -public: - virtual bool InitMySQL( const char *pDBName, const char *pHostName="", const char *pUserName="", const char *pPassword="" ) = 0; - virtual void Release() = 0; - - // These execute SQL commands. They return 0 if the query was successful. - virtual int Execute( const char *pString ) = 0; - - // This reads in all of the data in the last row set you queried with Execute and builds a separate - // copy. This is useful in some of the VMPI tools to have a thread repeatedly execute a slow query, then - // store off the results for the main thread to parse. - virtual IMySQLRowSet* DuplicateRowSet() = 0; - - // If you just inserted rows into a table with an AUTO_INCREMENT column, - // then this returns the (unique) value of that column. - virtual unsigned long InsertID() = 0; - - // Returns the last error message, if an error took place - virtual const char * GetLastError() = 0; -}; - - -#define MYSQL_WRAPPER_VERSION_NAME "MySQLWrapper001" - - -// ------------------------------------------------------------------------------------------------ // -// Inlines. -// ------------------------------------------------------------------------------------------------ // - -inline CColumnValue::CColumnValue( IMySQLRowSet *pSQL, int iColumn ) -{ - m_pSQL = pSQL; - m_iColumn = iColumn; -} - -inline const char* CColumnValue::String() -{ - return m_pSQL->GetColumnValue_String( m_iColumn ); -} - -inline long CColumnValue::Int32() -{ - return m_pSQL->GetColumnValue_Int( m_iColumn ); -} - - -#endif // MYSQL_WRAPPER_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MYSQL_WRAPPER_H +#define MYSQL_WRAPPER_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "utlvector.h" +#include "interface.h" + + +class IMySQLRowSet; + + +class CColumnValue +{ +public: + + CColumnValue( IMySQLRowSet *pSQL, int iColumn ); + + const char* String(); + long Int32(); + +private: + IMySQLRowSet *m_pSQL; + int m_iColumn; +}; + + + +class IMySQLRowSet +{ +public: + virtual void Release() = 0; + + // Get the number of columns in the data returned from the last query (if it was a select statement). + virtual int NumFields() = 0; + + // Get the name of each column returned by the last query. + virtual const char* GetFieldName( int iColumn ) = 0; + + // Call this in a loop until it returns false to iterate over all rows the query returned. + virtual bool NextRow() = 0; + + // You can call this to start iterating over the result set from the start again. + // Note: after calling this, you have to call NextRow() to actually get the first row's value ready. + virtual bool SeekToFirstRow() = 0; + + virtual CColumnValue GetColumnValue( int iColumn ) = 0; + virtual CColumnValue GetColumnValue( const char *pColumnName ) = 0; + + virtual const char* GetColumnValue_String( int iColumn ) = 0; + virtual long GetColumnValue_Int( int iColumn ) = 0; + + // You can call this to get the index of a column for faster lookups with GetColumnValue( int ). + // Returns -1 if the column can't be found. + virtual int GetColumnIndex( const char *pColumnName ) = 0; +}; + + +class IMySQL : public IMySQLRowSet +{ +public: + virtual bool InitMySQL( const char *pDBName, const char *pHostName="", const char *pUserName="", const char *pPassword="" ) = 0; + virtual void Release() = 0; + + // These execute SQL commands. They return 0 if the query was successful. + virtual int Execute( const char *pString ) = 0; + + // This reads in all of the data in the last row set you queried with Execute and builds a separate + // copy. This is useful in some of the VMPI tools to have a thread repeatedly execute a slow query, then + // store off the results for the main thread to parse. + virtual IMySQLRowSet* DuplicateRowSet() = 0; + + // If you just inserted rows into a table with an AUTO_INCREMENT column, + // then this returns the (unique) value of that column. + virtual unsigned long InsertID() = 0; + + // Returns the last error message, if an error took place + virtual const char * GetLastError() = 0; +}; + + +#define MYSQL_WRAPPER_VERSION_NAME "MySQLWrapper001" + + +// ------------------------------------------------------------------------------------------------ // +// Inlines. +// ------------------------------------------------------------------------------------------------ // + +inline CColumnValue::CColumnValue( IMySQLRowSet *pSQL, int iColumn ) +{ + m_pSQL = pSQL; + m_iColumn = iColumn; +} + +inline const char* CColumnValue::String() +{ + return m_pSQL->GetColumnValue_String( m_iColumn ); +} + +inline long CColumnValue::Int32() +{ + return m_pSQL->GetColumnValue_Int( m_iColumn ); +} + + +#endif // MYSQL_WRAPPER_H diff --git a/mp/src/utils/vmpi/iphelpers.h b/mp/src/utils/vmpi/iphelpers.h index 570a0c74..acd356a9 100644 --- a/mp/src/utils/vmpi/iphelpers.h +++ b/mp/src/utils/vmpi/iphelpers.h @@ -1,162 +1,162 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#ifndef IPHELPERS_H -#define IPHELPERS_H - - -#include "ichannel.h" - - -// Loops that poll sockets should Sleep for this amount of time between iterations -// so they don't hog all the CPU. -#define LOOP_POLL_INTERVAL 5 - - -// Useful for putting the arguments into a printf statement. -#define EXPAND_ADDR( x ) (x).ip[0], (x).ip[1], (x).ip[2], (x).ip[3], (x).port - - -// This is a simple wrapper layer for UDP sockets. -class CIPAddr -{ -public: - CIPAddr(); - CIPAddr( const int inputIP[4], const int inputPort ); - CIPAddr( int ip0, int ip1, int ip2, int ip3, int ipPort ); - - void Init( int ip0, int ip1, int ip2, int ip3, int ipPort ); - bool operator==( const CIPAddr &o ) const; - bool operator!=( const CIPAddr &o ) const; - - // Setup to send to the local machine on the specified port. - void SetupLocal( int inPort ); - -public: - - unsigned char ip[4]; - unsigned short port; -}; - - - -// The chunk walker provides an easy way to copy data out of the chunks as though it were a -// single contiguous chunk of memory.s -class CChunkWalker -{ -public: - CChunkWalker( void const * const *pChunks, const int *pChunkLengths, int nChunks ); - - int GetTotalLength() const; - void CopyTo( void *pOut, int nBytes ); - -private: - - void const * const *m_pChunks; - const int *m_pChunkLengths; - int m_nChunks; - - int m_iCurChunk; - int m_iCurChunkPos; - - int m_TotalLength; -}; - - -// This class makes loop that wait on something look nicer. ALL loops using this class -// should follow this pattern, or they can wind up with unforeseen delays that add a whole -// lot of lag. -// -// CWaitTimer waitTimer( 5.0 ); -// while ( 1 ) -// { -// do your thing here like Recv() from a socket. -// -// if ( waitTimer.ShouldKeepWaiting() ) -// Sleep() for some time interval like 5ms so you don't hog the CPU -// else -// BREAK HERE -// } -class CWaitTimer -{ -public: - CWaitTimer( double flSeconds ); - - bool ShouldKeepWaiting(); - -private: - unsigned long m_StartTime; - unsigned long m_WaitMS; -}; - - -// Helper function to get time in milliseconds. -unsigned long SampleMilliseconds(); - - -class ISocket -{ -public: - - // Call this when you're done. - virtual void Release() = 0; - - - // Bind the socket so you can send and receive with it. - // If you bind to port 0, then the system will select the port for you. - virtual bool Bind( const CIPAddr *pAddr ) = 0; - virtual bool BindToAny( const unsigned short port ) = 0; - - - // Broadcast some data. - virtual bool Broadcast( const void *pData, const int len, const unsigned short port ) = 0; - - // Send a packet. - virtual bool SendTo( const CIPAddr *pAddr, const void *pData, const int len ) = 0; - virtual bool SendChunksTo( const CIPAddr *pAddr, void const * const *pChunks, const int *pChunkLengths, int nChunks ) = 0; - - // Receive a packet. Returns the length received or -1 if no data came in. - // If pFrom is set, then it is filled in with the sender's IP address. - virtual int RecvFrom( void *pData, int maxDataLen, CIPAddr *pFrom ) = 0; - - // How long has it been since we successfully received a packet? - virtual double GetRecvTimeout() = 0; -}; - -// Create a connectionless socket that you can send packets out of. -ISocket* CreateIPSocket(); - -// This sets up the socket to receive multicast data on the specified group. -// By default, localInterface is INADDR_ANY, but if you want to specify a specific interface -// the data should come in through, you can. -ISocket* CreateMulticastListenSocket( - const CIPAddr &addr, - const CIPAddr &localInterface = CIPAddr() - ); - - -// Setup a CIPAddr from the string. The string can be a dotted IP address or -// a hostname, and it can be followed by a colon and a port number like "1.2.3.4:3443" -// or "myhostname.valvesoftware.com:2342". -// -// Note: if the string does not contain a port, then pOut->port will be left alone. -bool ConvertStringToIPAddr( const char *pStr, CIPAddr *pOut ); - -// Do a DNS lookup on the IP. -// You can optionally get a service name back too. -bool ConvertIPAddrToString( const CIPAddr *pIn, char *pOut, int outLen ); - - -void IP_GetLastErrorString( char *pStr, int maxLen ); - -void SockAddrToIPAddr( const struct sockaddr_in *pIn, CIPAddr *pOut ); -void IPAddrToSockAddr( const CIPAddr *pIn, struct sockaddr_in *pOut ); - - -#endif - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#ifndef IPHELPERS_H +#define IPHELPERS_H + + +#include "ichannel.h" + + +// Loops that poll sockets should Sleep for this amount of time between iterations +// so they don't hog all the CPU. +#define LOOP_POLL_INTERVAL 5 + + +// Useful for putting the arguments into a printf statement. +#define EXPAND_ADDR( x ) (x).ip[0], (x).ip[1], (x).ip[2], (x).ip[3], (x).port + + +// This is a simple wrapper layer for UDP sockets. +class CIPAddr +{ +public: + CIPAddr(); + CIPAddr( const int inputIP[4], const int inputPort ); + CIPAddr( int ip0, int ip1, int ip2, int ip3, int ipPort ); + + void Init( int ip0, int ip1, int ip2, int ip3, int ipPort ); + bool operator==( const CIPAddr &o ) const; + bool operator!=( const CIPAddr &o ) const; + + // Setup to send to the local machine on the specified port. + void SetupLocal( int inPort ); + +public: + + unsigned char ip[4]; + unsigned short port; +}; + + + +// The chunk walker provides an easy way to copy data out of the chunks as though it were a +// single contiguous chunk of memory.s +class CChunkWalker +{ +public: + CChunkWalker( void const * const *pChunks, const int *pChunkLengths, int nChunks ); + + int GetTotalLength() const; + void CopyTo( void *pOut, int nBytes ); + +private: + + void const * const *m_pChunks; + const int *m_pChunkLengths; + int m_nChunks; + + int m_iCurChunk; + int m_iCurChunkPos; + + int m_TotalLength; +}; + + +// This class makes loop that wait on something look nicer. ALL loops using this class +// should follow this pattern, or they can wind up with unforeseen delays that add a whole +// lot of lag. +// +// CWaitTimer waitTimer( 5.0 ); +// while ( 1 ) +// { +// do your thing here like Recv() from a socket. +// +// if ( waitTimer.ShouldKeepWaiting() ) +// Sleep() for some time interval like 5ms so you don't hog the CPU +// else +// BREAK HERE +// } +class CWaitTimer +{ +public: + CWaitTimer( double flSeconds ); + + bool ShouldKeepWaiting(); + +private: + unsigned long m_StartTime; + unsigned long m_WaitMS; +}; + + +// Helper function to get time in milliseconds. +unsigned long SampleMilliseconds(); + + +class ISocket +{ +public: + + // Call this when you're done. + virtual void Release() = 0; + + + // Bind the socket so you can send and receive with it. + // If you bind to port 0, then the system will select the port for you. + virtual bool Bind( const CIPAddr *pAddr ) = 0; + virtual bool BindToAny( const unsigned short port ) = 0; + + + // Broadcast some data. + virtual bool Broadcast( const void *pData, const int len, const unsigned short port ) = 0; + + // Send a packet. + virtual bool SendTo( const CIPAddr *pAddr, const void *pData, const int len ) = 0; + virtual bool SendChunksTo( const CIPAddr *pAddr, void const * const *pChunks, const int *pChunkLengths, int nChunks ) = 0; + + // Receive a packet. Returns the length received or -1 if no data came in. + // If pFrom is set, then it is filled in with the sender's IP address. + virtual int RecvFrom( void *pData, int maxDataLen, CIPAddr *pFrom ) = 0; + + // How long has it been since we successfully received a packet? + virtual double GetRecvTimeout() = 0; +}; + +// Create a connectionless socket that you can send packets out of. +ISocket* CreateIPSocket(); + +// This sets up the socket to receive multicast data on the specified group. +// By default, localInterface is INADDR_ANY, but if you want to specify a specific interface +// the data should come in through, you can. +ISocket* CreateMulticastListenSocket( + const CIPAddr &addr, + const CIPAddr &localInterface = CIPAddr() + ); + + +// Setup a CIPAddr from the string. The string can be a dotted IP address or +// a hostname, and it can be followed by a colon and a port number like "1.2.3.4:3443" +// or "myhostname.valvesoftware.com:2342". +// +// Note: if the string does not contain a port, then pOut->port will be left alone. +bool ConvertStringToIPAddr( const char *pStr, CIPAddr *pOut ); + +// Do a DNS lookup on the IP. +// You can optionally get a service name back too. +bool ConvertIPAddrToString( const CIPAddr *pIn, char *pOut, int outLen ); + + +void IP_GetLastErrorString( char *pStr, int maxLen ); + +void SockAddrToIPAddr( const struct sockaddr_in *pIn, CIPAddr *pOut ); +void IPAddrToSockAddr( const CIPAddr *pIn, struct sockaddr_in *pOut ); + + +#endif + diff --git a/mp/src/utils/vmpi/messbuf.h b/mp/src/utils/vmpi/messbuf.h index 2f09e884..976ffa2f 100644 --- a/mp/src/utils/vmpi/messbuf.h +++ b/mp/src/utils/vmpi/messbuf.h @@ -1,52 +1,52 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// -// MessageBuffer - handy for packing and upacking -// structures to be sent as messages -// -#ifndef _MESSAGEBUFFER -#define _MESSAGEBUFFER - -#include -#define DEFAULT_MESSAGE_BUFFER_SIZE 2048 - -class MessageBuffer { - public: - char * data; - - MessageBuffer(); - MessageBuffer(int size); - ~MessageBuffer(); - - int getSize(); - int getLen(); - int setLen(int len); - int getOffset(); - int setOffset(int offset); - - int write(void const * p, int bytes); - int update(int loc, void const * p, int bytes); - int extract(int loc, void * p, int bytes); - int read(void * p, int bytes); - - int WriteString( const char *pString ); - int ReadString( char *pOut, int bufferLength ); - - void clear(); - void clear(int minsize); - void reset(int minsize); - void print(FILE * ofile, int num); - - private: - void resize(int minsize); - int size; - int offset; - int len; -}; - -#endif +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// +// MessageBuffer - handy for packing and upacking +// structures to be sent as messages +// +#ifndef _MESSAGEBUFFER +#define _MESSAGEBUFFER + +#include +#define DEFAULT_MESSAGE_BUFFER_SIZE 2048 + +class MessageBuffer { + public: + char * data; + + MessageBuffer(); + MessageBuffer(int size); + ~MessageBuffer(); + + int getSize(); + int getLen(); + int setLen(int len); + int getOffset(); + int setOffset(int offset); + + int write(void const * p, int bytes); + int update(int loc, void const * p, int bytes); + int extract(int loc, void * p, int bytes); + int read(void * p, int bytes); + + int WriteString( const char *pString ); + int ReadString( char *pOut, int bufferLength ); + + void clear(); + void clear(int minsize); + void reset(int minsize); + void print(FILE * ofile, int num); + + private: + void resize(int minsize); + int size; + int offset; + int len; +}; + +#endif diff --git a/mp/src/utils/vmpi/threadhelpers.h b/mp/src/utils/vmpi/threadhelpers.h index 1e955a5e..bfba74f3 100644 --- a/mp/src/utils/vmpi/threadhelpers.h +++ b/mp/src/utils/vmpi/threadhelpers.h @@ -1,110 +1,110 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef THREADHELPERS_H -#define THREADHELPERS_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "tier1/utllinkedlist.h" - - -#define SIZEOF_CS 24 // sizeof( CRITICAL_SECTION ) - - -class CCriticalSection -{ -public: - CCriticalSection(); - ~CCriticalSection(); - - -protected: - - friend class CCriticalSectionLock; - - void Lock(); - void Unlock(); - - -public: - char m_CS[SIZEOF_CS]; - - // Used to protect against deadlock in debug mode. -//#if defined( _DEBUG ) - CUtlLinkedList m_Locks; - char m_DeadlockProtect[SIZEOF_CS]; -//#endif -}; - - -// Use this to lock a critical section. -class CCriticalSectionLock -{ -public: - CCriticalSectionLock( CCriticalSection *pCS ); - ~CCriticalSectionLock(); - void Lock(); - void Unlock(); - -private: - CCriticalSection *m_pCS; - bool m_bLocked; -}; - - -template< class T > -class CCriticalSectionData : private CCriticalSection -{ -public: - // You only have access to the data between Lock() and Unlock(). - T* Lock() - { - CCriticalSection::Lock(); - return &m_Data; - } - - void Unlock() - { - CCriticalSection::Unlock(); - } - -private: - T m_Data; -}; - - - -// ------------------------------------------------------------------------------------------------ // -// CEvent. -// ------------------------------------------------------------------------------------------------ // -class CEvent -{ -public: - CEvent(); - ~CEvent(); - - bool Init( bool bManualReset, bool bInitialState ); - void Term(); - - void* GetEventHandle() const; - - // Signal the event. - bool SetEvent(); - - // Unset the event's signalled status. - bool ResetEvent(); - - -private: - void *m_hEvent; -}; - - -#endif // THREADHELPERS_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef THREADHELPERS_H +#define THREADHELPERS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tier1/utllinkedlist.h" + + +#define SIZEOF_CS 24 // sizeof( CRITICAL_SECTION ) + + +class CCriticalSection +{ +public: + CCriticalSection(); + ~CCriticalSection(); + + +protected: + + friend class CCriticalSectionLock; + + void Lock(); + void Unlock(); + + +public: + char m_CS[SIZEOF_CS]; + + // Used to protect against deadlock in debug mode. +//#if defined( _DEBUG ) + CUtlLinkedList m_Locks; + char m_DeadlockProtect[SIZEOF_CS]; +//#endif +}; + + +// Use this to lock a critical section. +class CCriticalSectionLock +{ +public: + CCriticalSectionLock( CCriticalSection *pCS ); + ~CCriticalSectionLock(); + void Lock(); + void Unlock(); + +private: + CCriticalSection *m_pCS; + bool m_bLocked; +}; + + +template< class T > +class CCriticalSectionData : private CCriticalSection +{ +public: + // You only have access to the data between Lock() and Unlock(). + T* Lock() + { + CCriticalSection::Lock(); + return &m_Data; + } + + void Unlock() + { + CCriticalSection::Unlock(); + } + +private: + T m_Data; +}; + + + +// ------------------------------------------------------------------------------------------------ // +// CEvent. +// ------------------------------------------------------------------------------------------------ // +class CEvent +{ +public: + CEvent(); + ~CEvent(); + + bool Init( bool bManualReset, bool bInitialState ); + void Term(); + + void* GetEventHandle() const; + + // Signal the event. + bool SetEvent(); + + // Unset the event's signalled status. + bool ResetEvent(); + + +private: + void *m_hEvent; +}; + + +#endif // THREADHELPERS_H diff --git a/mp/src/utils/vmpi/vmpi.h b/mp/src/utils/vmpi/vmpi.h index b2fe9641..cb7316f5 100644 --- a/mp/src/utils/vmpi/vmpi.h +++ b/mp/src/utils/vmpi/vmpi.h @@ -1,217 +1,217 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef VMPI_H -#define VMPI_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "vmpi_defs.h" -#include "messbuf.h" -#include "iphelpers.h" - - -// These are called to handle incoming messages. -// Return true if you handled the message and false otherwise. -// Note: the first byte in each message is the packet ID. -typedef bool (*VMPIDispatchFn)( MessageBuffer *pBuf, int iSource, int iPacketID ); - -typedef void (*VMPI_Disconnect_Handler)( int procID, const char *pReason ); - - -// Which machine is the master. -#define VMPI_MASTER_ID 0 - -#define VMPI_SEND_TO_ALL -2 -#define VMPI_PERSISTENT -3 // If this is set as the destination for a packet, it is sent to all - // workers, and also to new workers that connect. - -#define MAX_VMPI_PACKET_IDS 32 - - -#define VMPI_TIMEOUT_INFINITE 0xFFFFFFFF - - -// Instantiate one of these to register a dispatch. -class CDispatchReg -{ -public: - CDispatchReg( int iPacketID, VMPIDispatchFn fn ); -}; - - -// Enums for all the command line parameters. -#define VMPI_PARAM_SDK_HIDDEN 0x0001 // Hidden in SDK mode. - -#define VMPI_PARAM( paramName, paramFlags, helpText ) paramName, -enum EVMPICmdLineParam -{ - k_eVMPICmdLineParam_FirstParam=0, - k_eVMPICmdLineParam_VMPIParam, - #include "vmpi_parameters.h" - k_eVMPICmdLineParam_LastParam -}; -#undef VMPI_PARAM - - -// Shared by all the tools. -extern bool g_bUseMPI; -extern bool g_bMPIMaster; // Set to true if we're the master in a VMPI session. -extern int g_iVMPIVerboseLevel; // Higher numbers make it spit out more data. - -extern bool g_bMPI_Stats; // Send stats to the MySQL database? -extern bool g_bMPI_StatsTextOutput; // Send text output in the stats? - -// These can be watched or modified to check bandwidth statistics. -extern int g_nBytesSent; -extern int g_nMessagesSent; -extern int g_nBytesReceived; -extern int g_nMessagesReceived; - -extern int g_nMulticastBytesSent; -extern int g_nMulticastBytesReceived; - -extern int g_nMaxWorkerCount; - - -enum VMPIRunMode -{ - VMPI_RUN_NETWORKED, - VMPI_RUN_LOCAL // Just make a local process and have it do the work. -}; - - -enum VMPIFileSystemMode -{ - VMPI_FILESYSTEM_MULTICAST, // Multicast out, find workers, have them do work. - VMPI_FILESYSTEM_BROADCAST, // Broadcast out, find workers, have them do work. - VMPI_FILESYSTEM_TCP // TCP filesystem. -}; - - -// If this precedes the dependency filename, then it will transfer all the files in the specified directory. -#define VMPI_DEPENDENCY_DIRECTORY_TOKEN '*' - - -// It's good to specify a disconnect handler here immediately. If you don't have a handler -// and the master disconnects, you'll lockup forever inside a dispatch loop because you -// never handled the master disconnecting. -// -// Note: runMode is only relevant for the VMPI master. The worker always connects to the master -// the same way. -bool VMPI_Init( - int &argc, - char **&argv, - const char *pDependencyFilename, - VMPI_Disconnect_Handler handler = NULL, - VMPIRunMode runMode = VMPI_RUN_NETWORKED, // Networked or local?, - bool bConnectingAsService = false - ); - -// Used when hosting a patch. -void VMPI_Init_PatchMaster( int argc, char **argv ); - -void VMPI_Finalize(); - -VMPIRunMode VMPI_GetRunMode(); -VMPIFileSystemMode VMPI_GetFileSystemMode(); - -// Note: this number can change on the master. -int VMPI_GetCurrentNumberOfConnections(); - - -// Dispatch messages until it gets one with the specified packet ID. -// If subPacketID is not set to -1, then the second byte must match that as well. -// -// Note: this WILL dispatch packets with matching packet IDs and give them a chance to handle packets first. -// -// If bWait is true, then this function either succeeds or Error() is called. If it's false, then if the first available message -// is handled by a dispatch, this function returns false. -bool VMPI_DispatchUntil( MessageBuffer *pBuf, int *pSource, int packetID, int subPacketID = -1, bool bWait = true ); - -// This waits for the next message and dispatches it. -// You can specify a timeout in milliseconds. If the timeout expires, the function returns false. -bool VMPI_DispatchNextMessage( unsigned long timeout=VMPI_TIMEOUT_INFINITE ); - -// This should be called periodically in modal loops that don't call other VMPI functions. This will -// check for disconnected sockets and call disconnect handlers so the app can error out if -// it loses all of its connections. -// -// This can be used in place of a Sleep() call by specifying a timeout value. -void VMPI_HandleSocketErrors( unsigned long timeout=0 ); - - - -enum VMPISendFlags -{ - k_eVMPISendFlags_GroupPackets = 0x0001 -}; - -// Use these to send data to one of the machines. -// If iDest is VMPI_SEND_TO_ALL, then the message goes to all the machines. -// Flags is a combination of the VMPISendFlags enums. -bool VMPI_SendData( void *pData, int nBytes, int iDest, int fVMPISendFlags=0 ); -bool VMPI_SendChunks( void const * const *pChunks, const int *pChunkLengths, int nChunks, int iDest, int fVMPISendFlags=0 ); -bool VMPI_Send2Chunks( const void *pChunk1, int chunk1Len, const void *pChunk2, int chunk2Len, int iDest, int fVMPISendFlags=0 ); // for convenience.. -bool VMPI_Send3Chunks( const void *pChunk1, int chunk1Len, const void *pChunk2, int chunk2Len, const void *pChunk3, int chunk3Len, int iDest, int fVMPISendFlags=0 ); - -// Flush any groups that were queued with k_eVMPISendFlags_GroupPackets. -// If msInterval is > 0, then it will check a timer and only flush that often (so you can call this a lot, and have it check). -void VMPI_FlushGroupedPackets( unsigned long msInterval=0 ); - -// This registers a function that gets called when a connection is terminated ungracefully. -void VMPI_AddDisconnectHandler( VMPI_Disconnect_Handler handler ); - -// Returns false if the process has disconnected ungracefully (disconnect handlers -// would have been called for it too). -bool VMPI_IsProcConnected( int procID ); - -// Returns true if the process is just a service (in which case it should only get file IO traffic). -bool VMPI_IsProcAService( int procID ); - -// Simple wrapper for Sleep() so people can avoid including windows.h -void VMPI_Sleep( unsigned long ms ); - -// VMPI sends machine names around first thing. -const char* VMPI_GetLocalMachineName(); -const char* VMPI_GetMachineName( int iProc ); -bool VMPI_HasMachineNameBeenSet( int iProc ); - -// Returns 0xFFFFFFFF if the ID hasn't been set. -unsigned long VMPI_GetJobWorkerID( int iProc ); -void VMPI_SetJobWorkerID( int iProc, unsigned long jobWorkerID ); - -// Search a command line to find arguments. Looks for pName, and if it finds it, returns the -// argument following it. If pName is the last argument, it returns pDefault. If it doesn't -// find pName, returns NULL. -const char* VMPI_FindArg( int argc, char **argv, const char *pName, const char *pDefault = "" ); - -// (Threadsafe) get and set the current stage. This info winds up in the VMPI database. -void VMPI_GetCurrentStage( char *pOut, int strLen ); -void VMPI_SetCurrentStage( const char *pCurStage ); - -// VMPI is always broadcasting this job in the background. -// This changes the password to 'debugworker' and allows more workers in. -// This can be used if workers are dying on certain work units. Then a programmer -// can run vmpi_service with -superdebug and debug the whole thing. -void VMPI_InviteDebugWorkers(); - -bool VMPI_IsSDKMode(); - -// Lookup a command line parameter string. -const char* VMPI_GetParamString( EVMPICmdLineParam eParam ); -int VMPI_GetParamFlags( EVMPICmdLineParam eParam ); -const char* VMPI_GetParamHelpString( EVMPICmdLineParam eParam ); -bool VMPI_IsParamUsed( EVMPICmdLineParam eParam ); // Returns true if the specified parameter is on the command line. - -// Can be called from error handlers and if -mpi_Restart is used, it'll automatically restart the process. -bool VMPI_HandleAutoRestart(); - - -#endif // VMPI_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VMPI_H +#define VMPI_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "vmpi_defs.h" +#include "messbuf.h" +#include "iphelpers.h" + + +// These are called to handle incoming messages. +// Return true if you handled the message and false otherwise. +// Note: the first byte in each message is the packet ID. +typedef bool (*VMPIDispatchFn)( MessageBuffer *pBuf, int iSource, int iPacketID ); + +typedef void (*VMPI_Disconnect_Handler)( int procID, const char *pReason ); + + +// Which machine is the master. +#define VMPI_MASTER_ID 0 + +#define VMPI_SEND_TO_ALL -2 +#define VMPI_PERSISTENT -3 // If this is set as the destination for a packet, it is sent to all + // workers, and also to new workers that connect. + +#define MAX_VMPI_PACKET_IDS 32 + + +#define VMPI_TIMEOUT_INFINITE 0xFFFFFFFF + + +// Instantiate one of these to register a dispatch. +class CDispatchReg +{ +public: + CDispatchReg( int iPacketID, VMPIDispatchFn fn ); +}; + + +// Enums for all the command line parameters. +#define VMPI_PARAM_SDK_HIDDEN 0x0001 // Hidden in SDK mode. + +#define VMPI_PARAM( paramName, paramFlags, helpText ) paramName, +enum EVMPICmdLineParam +{ + k_eVMPICmdLineParam_FirstParam=0, + k_eVMPICmdLineParam_VMPIParam, + #include "vmpi_parameters.h" + k_eVMPICmdLineParam_LastParam +}; +#undef VMPI_PARAM + + +// Shared by all the tools. +extern bool g_bUseMPI; +extern bool g_bMPIMaster; // Set to true if we're the master in a VMPI session. +extern int g_iVMPIVerboseLevel; // Higher numbers make it spit out more data. + +extern bool g_bMPI_Stats; // Send stats to the MySQL database? +extern bool g_bMPI_StatsTextOutput; // Send text output in the stats? + +// These can be watched or modified to check bandwidth statistics. +extern int g_nBytesSent; +extern int g_nMessagesSent; +extern int g_nBytesReceived; +extern int g_nMessagesReceived; + +extern int g_nMulticastBytesSent; +extern int g_nMulticastBytesReceived; + +extern int g_nMaxWorkerCount; + + +enum VMPIRunMode +{ + VMPI_RUN_NETWORKED, + VMPI_RUN_LOCAL // Just make a local process and have it do the work. +}; + + +enum VMPIFileSystemMode +{ + VMPI_FILESYSTEM_MULTICAST, // Multicast out, find workers, have them do work. + VMPI_FILESYSTEM_BROADCAST, // Broadcast out, find workers, have them do work. + VMPI_FILESYSTEM_TCP // TCP filesystem. +}; + + +// If this precedes the dependency filename, then it will transfer all the files in the specified directory. +#define VMPI_DEPENDENCY_DIRECTORY_TOKEN '*' + + +// It's good to specify a disconnect handler here immediately. If you don't have a handler +// and the master disconnects, you'll lockup forever inside a dispatch loop because you +// never handled the master disconnecting. +// +// Note: runMode is only relevant for the VMPI master. The worker always connects to the master +// the same way. +bool VMPI_Init( + int &argc, + char **&argv, + const char *pDependencyFilename, + VMPI_Disconnect_Handler handler = NULL, + VMPIRunMode runMode = VMPI_RUN_NETWORKED, // Networked or local?, + bool bConnectingAsService = false + ); + +// Used when hosting a patch. +void VMPI_Init_PatchMaster( int argc, char **argv ); + +void VMPI_Finalize(); + +VMPIRunMode VMPI_GetRunMode(); +VMPIFileSystemMode VMPI_GetFileSystemMode(); + +// Note: this number can change on the master. +int VMPI_GetCurrentNumberOfConnections(); + + +// Dispatch messages until it gets one with the specified packet ID. +// If subPacketID is not set to -1, then the second byte must match that as well. +// +// Note: this WILL dispatch packets with matching packet IDs and give them a chance to handle packets first. +// +// If bWait is true, then this function either succeeds or Error() is called. If it's false, then if the first available message +// is handled by a dispatch, this function returns false. +bool VMPI_DispatchUntil( MessageBuffer *pBuf, int *pSource, int packetID, int subPacketID = -1, bool bWait = true ); + +// This waits for the next message and dispatches it. +// You can specify a timeout in milliseconds. If the timeout expires, the function returns false. +bool VMPI_DispatchNextMessage( unsigned long timeout=VMPI_TIMEOUT_INFINITE ); + +// This should be called periodically in modal loops that don't call other VMPI functions. This will +// check for disconnected sockets and call disconnect handlers so the app can error out if +// it loses all of its connections. +// +// This can be used in place of a Sleep() call by specifying a timeout value. +void VMPI_HandleSocketErrors( unsigned long timeout=0 ); + + + +enum VMPISendFlags +{ + k_eVMPISendFlags_GroupPackets = 0x0001 +}; + +// Use these to send data to one of the machines. +// If iDest is VMPI_SEND_TO_ALL, then the message goes to all the machines. +// Flags is a combination of the VMPISendFlags enums. +bool VMPI_SendData( void *pData, int nBytes, int iDest, int fVMPISendFlags=0 ); +bool VMPI_SendChunks( void const * const *pChunks, const int *pChunkLengths, int nChunks, int iDest, int fVMPISendFlags=0 ); +bool VMPI_Send2Chunks( const void *pChunk1, int chunk1Len, const void *pChunk2, int chunk2Len, int iDest, int fVMPISendFlags=0 ); // for convenience.. +bool VMPI_Send3Chunks( const void *pChunk1, int chunk1Len, const void *pChunk2, int chunk2Len, const void *pChunk3, int chunk3Len, int iDest, int fVMPISendFlags=0 ); + +// Flush any groups that were queued with k_eVMPISendFlags_GroupPackets. +// If msInterval is > 0, then it will check a timer and only flush that often (so you can call this a lot, and have it check). +void VMPI_FlushGroupedPackets( unsigned long msInterval=0 ); + +// This registers a function that gets called when a connection is terminated ungracefully. +void VMPI_AddDisconnectHandler( VMPI_Disconnect_Handler handler ); + +// Returns false if the process has disconnected ungracefully (disconnect handlers +// would have been called for it too). +bool VMPI_IsProcConnected( int procID ); + +// Returns true if the process is just a service (in which case it should only get file IO traffic). +bool VMPI_IsProcAService( int procID ); + +// Simple wrapper for Sleep() so people can avoid including windows.h +void VMPI_Sleep( unsigned long ms ); + +// VMPI sends machine names around first thing. +const char* VMPI_GetLocalMachineName(); +const char* VMPI_GetMachineName( int iProc ); +bool VMPI_HasMachineNameBeenSet( int iProc ); + +// Returns 0xFFFFFFFF if the ID hasn't been set. +unsigned long VMPI_GetJobWorkerID( int iProc ); +void VMPI_SetJobWorkerID( int iProc, unsigned long jobWorkerID ); + +// Search a command line to find arguments. Looks for pName, and if it finds it, returns the +// argument following it. If pName is the last argument, it returns pDefault. If it doesn't +// find pName, returns NULL. +const char* VMPI_FindArg( int argc, char **argv, const char *pName, const char *pDefault = "" ); + +// (Threadsafe) get and set the current stage. This info winds up in the VMPI database. +void VMPI_GetCurrentStage( char *pOut, int strLen ); +void VMPI_SetCurrentStage( const char *pCurStage ); + +// VMPI is always broadcasting this job in the background. +// This changes the password to 'debugworker' and allows more workers in. +// This can be used if workers are dying on certain work units. Then a programmer +// can run vmpi_service with -superdebug and debug the whole thing. +void VMPI_InviteDebugWorkers(); + +bool VMPI_IsSDKMode(); + +// Lookup a command line parameter string. +const char* VMPI_GetParamString( EVMPICmdLineParam eParam ); +int VMPI_GetParamFlags( EVMPICmdLineParam eParam ); +const char* VMPI_GetParamHelpString( EVMPICmdLineParam eParam ); +bool VMPI_IsParamUsed( EVMPICmdLineParam eParam ); // Returns true if the specified parameter is on the command line. + +// Can be called from error handlers and if -mpi_Restart is used, it'll automatically restart the process. +bool VMPI_HandleAutoRestart(); + + +#endif // VMPI_H diff --git a/mp/src/utils/vmpi/vmpi_defs.h b/mp/src/utils/vmpi/vmpi_defs.h index 7845d9f9..7d655084 100644 --- a/mp/src/utils/vmpi/vmpi_defs.h +++ b/mp/src/utils/vmpi/vmpi_defs.h @@ -1,147 +1,147 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef VMPI_DEFS_H -#define VMPI_DEFS_H -#ifdef _WIN32 -#pragma once -#endif - - -// This goes in front of all packets. -#define VMPI_PROTOCOL_VERSION 5 - -// This represents the protocol between the service and its UI. -#define VMPI_SERVICE_UI_PROTOCOL_VERSION 1 - -// NOTE: the service version (embedded in vmpi_service.exe as a resource) is the version -// that is used to apply patches. -#define VMPI_SERVICE_IDS_VERSION_STRING 102 // This matches IDS_VERSION_STRING in vmpi_service.exe. - -// Known packet IDs in various systems. -#define VMPI_PACKETID_FILESYSTEM 0 // The file system reserves this packet ID. - // All application traffic must set its first byte to something other - // than this value. -#define VMPI_SHARED_PACKET_ID 10 - - -// Turn this on, and the various service apps will log stuff. -//#define VMPI_SERVICE_LOGS - - -// This value is put in the RunningTimeMS until the job is finished. This is how -// the job_search app knows if a job never finished. -#define RUNNINGTIME_MS_SENTINEL 0xFEDCBAFD - - - -#define VMPI_SERVICE_NAME_INTERNAL "VMPI" -#define VMPI_SERVICE_NAME "Valve MPI Service" - -// Stuff in the registry goes under here (in HKEY_LOCAL_MACHINE). -#define VMPI_SERVICE_KEY "Software\\Valve\\VMPI" -#define SERVICE_INSTALL_LOCATION_KEY "InstallLocation" - -// The VMPI service listens on one of these ports to talk to the UI. -#define VMPI_SERVICE_FIRST_UI_PORT 23300 -#define VMPI_SERVICE_LAST_UI_PORT 23310 - -// Port numbers that the master will use to broadcast unless -mpi_port is used. -#define VMPI_MASTER_FIRST_PORT 23311 -#define VMPI_MASTER_LAST_PORT 23330 - - -// Packet IDs for vmpi_service to talk to UI clients. -#define VMPI_SERVICE_TO_UI_CONSOLE_TEXT 0 // Print some text to the UI's console. -#define VMPI_SERVICE_TO_UI_STATE 1 // Updates state reflecting whether it's idle, busy, etc. -#define VMPI_SERVICE_TO_UI_PATCHING 2 // Updates state reflecting whether it's idle, busy, etc. -#define VMPI_SERVICE_TO_UI_EXIT 3 // Updates state reflecting whether it's idle, busy, etc. - - // Application state.. these are communicated between the service and the UI. - enum - { - VMPI_SERVICE_STATE_IDLE=0, - VMPI_SERVICE_STATE_BUSY, - VMPI_SERVICE_STATE_DISABLED - }; -#define VMPI_SERVICE_DISABLE 2 // Stop waiting for jobs.. -#define VMPI_SERVICE_ENABLE 3 -#define VMPI_SERVICE_UPDATE_PASSWORD 4 // New password. -#define VMPI_SERVICE_EXIT 5 // User chose "exit" from the menu. Kill the service. -#define VMPI_SERVICE_SKIP_CSX_JOBS 6 -#define VMPI_SERVICE_SCREENSAVER_MODE 7 - - -// The worker service waits on this range of ports. -#define VMPI_SERVICE_PORT 23397 -#define VMPI_LAST_SERVICE_PORT (VMPI_SERVICE_PORT + 15) - - -#define VMPI_WORKER_PORT_FIRST 22340 -#define VMPI_WORKER_PORT_LAST 22350 - -// The VMPI service downloader is still a worker but it uses this port range so the -// master knows it's just downloading the exes. -#define VMPI_SERVICE_DOWNLOADER_PORT_FIRST 22351 -#define VMPI_SERVICE_DOWNLOADER_PORT_LAST 22360 - -// Give it a small range so they can have multiple masters running. -#define VMPI_MASTER_PORT_FIRST 21140 -#define VMPI_MASTER_PORT_LAST 21145 -#define VMPI_MASTER_FILESYSTEM_BROADCAST_PORT 21146 - - - - -// Protocol. - -// The message format is: -// - VMPI_PROTOCOL_VERSION -// - null-terminated password string (or VMPI_PASSWORD_OVERRIDE followed by a zero to process it regardless of pw). -// - packet ID -// - payload - - -#define VMPI_PASSWORD_OVERRIDE -111 - - -#define VMPI_MESSAGE_BASE 71 - - -// This is the broadcast message from the main (rank 0) process looking for workers. -#define VMPI_LOOKING_FOR_WORKERS (VMPI_MESSAGE_BASE+0) - -// This is so an app can find out what machines are running the service. -#define VMPI_PING_REQUEST (VMPI_MESSAGE_BASE+2) -#define VMPI_PING_RESPONSE (VMPI_MESSAGE_BASE+3) - -// This tells the service to quit. -#define VMPI_STOP_SERVICE (VMPI_MESSAGE_BASE+6) - -// This tells the service to kill any process it has running. -#define VMPI_KILL_PROCESS (VMPI_MESSAGE_BASE+7) - -// This tells the service to patch itself. -#define VMPI_SERVICE_PATCH (VMPI_MESSAGE_BASE+8) - -// Sent back to the master via UDP to tell it if its job has started and ended. -#define VMPI_NOTIFY_START_STATUS (VMPI_MESSAGE_BASE+9) -#define VMPI_NOTIFY_END_STATUS (VMPI_MESSAGE_BASE+10) - -#define VMPI_FORCE_PASSWORD_CHANGE (VMPI_MESSAGE_BASE+11) - - -// These states are sent from the service to the services browser. -#define VMPI_STATE_IDLE 0 -#define VMPI_STATE_BUSY 1 -#define VMPI_STATE_PATCHING 2 -#define VMPI_STATE_DISABLED 3 -#define VMPI_STATE_SCREENSAVER_DISABLED 4 -#define VMPI_STATE_DOWNLOADING 5 - - -#endif // VMPI_DEFS_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VMPI_DEFS_H +#define VMPI_DEFS_H +#ifdef _WIN32 +#pragma once +#endif + + +// This goes in front of all packets. +#define VMPI_PROTOCOL_VERSION 5 + +// This represents the protocol between the service and its UI. +#define VMPI_SERVICE_UI_PROTOCOL_VERSION 1 + +// NOTE: the service version (embedded in vmpi_service.exe as a resource) is the version +// that is used to apply patches. +#define VMPI_SERVICE_IDS_VERSION_STRING 102 // This matches IDS_VERSION_STRING in vmpi_service.exe. + +// Known packet IDs in various systems. +#define VMPI_PACKETID_FILESYSTEM 0 // The file system reserves this packet ID. + // All application traffic must set its first byte to something other + // than this value. +#define VMPI_SHARED_PACKET_ID 10 + + +// Turn this on, and the various service apps will log stuff. +//#define VMPI_SERVICE_LOGS + + +// This value is put in the RunningTimeMS until the job is finished. This is how +// the job_search app knows if a job never finished. +#define RUNNINGTIME_MS_SENTINEL 0xFEDCBAFD + + + +#define VMPI_SERVICE_NAME_INTERNAL "VMPI" +#define VMPI_SERVICE_NAME "Valve MPI Service" + +// Stuff in the registry goes under here (in HKEY_LOCAL_MACHINE). +#define VMPI_SERVICE_KEY "Software\\Valve\\VMPI" +#define SERVICE_INSTALL_LOCATION_KEY "InstallLocation" + +// The VMPI service listens on one of these ports to talk to the UI. +#define VMPI_SERVICE_FIRST_UI_PORT 23300 +#define VMPI_SERVICE_LAST_UI_PORT 23310 + +// Port numbers that the master will use to broadcast unless -mpi_port is used. +#define VMPI_MASTER_FIRST_PORT 23311 +#define VMPI_MASTER_LAST_PORT 23330 + + +// Packet IDs for vmpi_service to talk to UI clients. +#define VMPI_SERVICE_TO_UI_CONSOLE_TEXT 0 // Print some text to the UI's console. +#define VMPI_SERVICE_TO_UI_STATE 1 // Updates state reflecting whether it's idle, busy, etc. +#define VMPI_SERVICE_TO_UI_PATCHING 2 // Updates state reflecting whether it's idle, busy, etc. +#define VMPI_SERVICE_TO_UI_EXIT 3 // Updates state reflecting whether it's idle, busy, etc. + + // Application state.. these are communicated between the service and the UI. + enum + { + VMPI_SERVICE_STATE_IDLE=0, + VMPI_SERVICE_STATE_BUSY, + VMPI_SERVICE_STATE_DISABLED + }; +#define VMPI_SERVICE_DISABLE 2 // Stop waiting for jobs.. +#define VMPI_SERVICE_ENABLE 3 +#define VMPI_SERVICE_UPDATE_PASSWORD 4 // New password. +#define VMPI_SERVICE_EXIT 5 // User chose "exit" from the menu. Kill the service. +#define VMPI_SERVICE_SKIP_CSX_JOBS 6 +#define VMPI_SERVICE_SCREENSAVER_MODE 7 + + +// The worker service waits on this range of ports. +#define VMPI_SERVICE_PORT 23397 +#define VMPI_LAST_SERVICE_PORT (VMPI_SERVICE_PORT + 15) + + +#define VMPI_WORKER_PORT_FIRST 22340 +#define VMPI_WORKER_PORT_LAST 22350 + +// The VMPI service downloader is still a worker but it uses this port range so the +// master knows it's just downloading the exes. +#define VMPI_SERVICE_DOWNLOADER_PORT_FIRST 22351 +#define VMPI_SERVICE_DOWNLOADER_PORT_LAST 22360 + +// Give it a small range so they can have multiple masters running. +#define VMPI_MASTER_PORT_FIRST 21140 +#define VMPI_MASTER_PORT_LAST 21145 +#define VMPI_MASTER_FILESYSTEM_BROADCAST_PORT 21146 + + + + +// Protocol. + +// The message format is: +// - VMPI_PROTOCOL_VERSION +// - null-terminated password string (or VMPI_PASSWORD_OVERRIDE followed by a zero to process it regardless of pw). +// - packet ID +// - payload + + +#define VMPI_PASSWORD_OVERRIDE -111 + + +#define VMPI_MESSAGE_BASE 71 + + +// This is the broadcast message from the main (rank 0) process looking for workers. +#define VMPI_LOOKING_FOR_WORKERS (VMPI_MESSAGE_BASE+0) + +// This is so an app can find out what machines are running the service. +#define VMPI_PING_REQUEST (VMPI_MESSAGE_BASE+2) +#define VMPI_PING_RESPONSE (VMPI_MESSAGE_BASE+3) + +// This tells the service to quit. +#define VMPI_STOP_SERVICE (VMPI_MESSAGE_BASE+6) + +// This tells the service to kill any process it has running. +#define VMPI_KILL_PROCESS (VMPI_MESSAGE_BASE+7) + +// This tells the service to patch itself. +#define VMPI_SERVICE_PATCH (VMPI_MESSAGE_BASE+8) + +// Sent back to the master via UDP to tell it if its job has started and ended. +#define VMPI_NOTIFY_START_STATUS (VMPI_MESSAGE_BASE+9) +#define VMPI_NOTIFY_END_STATUS (VMPI_MESSAGE_BASE+10) + +#define VMPI_FORCE_PASSWORD_CHANGE (VMPI_MESSAGE_BASE+11) + + +// These states are sent from the service to the services browser. +#define VMPI_STATE_IDLE 0 +#define VMPI_STATE_BUSY 1 +#define VMPI_STATE_PATCHING 2 +#define VMPI_STATE_DISABLED 3 +#define VMPI_STATE_SCREENSAVER_DISABLED 4 +#define VMPI_STATE_DOWNLOADING 5 + + +#endif // VMPI_DEFS_H diff --git a/mp/src/utils/vmpi/vmpi_dispatch.h b/mp/src/utils/vmpi/vmpi_dispatch.h index d91c5b1d..981d4a27 100644 --- a/mp/src/utils/vmpi/vmpi_dispatch.h +++ b/mp/src/utils/vmpi/vmpi_dispatch.h @@ -1,15 +1,15 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef VMPI_DISPATCH_H -#define VMPI_DISPATCH_H -#ifdef _WIN32 -#pragma once -#endif - - -#endif // VMPI_DISPATCH_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VMPI_DISPATCH_H +#define VMPI_DISPATCH_H +#ifdef _WIN32 +#pragma once +#endif + + +#endif // VMPI_DISPATCH_H diff --git a/mp/src/utils/vmpi/vmpi_distribute_work.h b/mp/src/utils/vmpi/vmpi_distribute_work.h index cfd37d8b..04354608 100644 --- a/mp/src/utils/vmpi/vmpi_distribute_work.h +++ b/mp/src/utils/vmpi/vmpi_distribute_work.h @@ -1,89 +1,89 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef VMPI_DISTRIBUTE_WORK_H -#define VMPI_DISTRIBUTE_WORK_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "messbuf.h" -#include "utlvector.h" - - -class IWorkUnitDistributorCallbacks -{ -public: - // Called every 200ms or so as it does the work. - // Return true to stop distributing work. - virtual bool Update() { return false; } - - // Called when a subsequent number of work units is completed. - // e.g. results received in the following order will trigger - // the following calls to OnWorkUnitsCompleted: - // Work unit numbers: wu2 wu4 wu5 wu1 wu0 wu6 wu3 - // Calling OnWorkUnitsCompleted with arg: - - - - 3 - 7 - // because when wu0 is received we already have { wu0, wu1, wu2 } so we signal - // that 3 subsequent work units completed, like wise by the time when wu3 is - // received we already have a full set { wu0, wu1, wu2, wu3, wu4, wu5, wu6 } - // and signal that 7 work units completed. - virtual void OnWorkUnitsCompleted( uint64 numWorkUnits ) { return; } -}; - - -enum EWorkUnitDistributor -{ - k_eWorkUnitDistributor_Default, - k_eWorkUnitDistributor_SDK -}; - -// Tells which work unit distributor is going to be used. -EWorkUnitDistributor VMPI_GetActiveWorkUnitDistributor(); - - -// Before calling DistributeWork, you can set this and it'll call your virtual functions. -extern IWorkUnitDistributorCallbacks *g_pDistributeWorkCallbacks; - - -// You must append data to pBuf with the work unit results. -// Note: pBuf will be NULL if this is a local thread doing work on the master. -typedef void (*ProcessWorkUnitFn)( int iThread, uint64 iWorkUnit, MessageBuffer *pBuf ); - -// pBuf is ready to read the results written to the buffer in ProcessWorkUnitFn. -typedef void (*ReceiveWorkUnitFn)( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ); - - -// Use a CDispatchReg to register this function with whatever packet ID you give to DistributeWork. -bool DistributeWorkDispatch( MessageBuffer *pBuf, int iSource, int iPacketID ); - - - -// This is the function vrad and vvis use to divide the work units and send them out. -// It maintains a sliding window of work units so it can always keep the clients busy. -// -// The workers implement processFn to do the job work in a work unit. -// This function must send back a packet formatted with: -// cPacketID (char), cSubPacketID (char), iWorkUnit (int), (app-specific data for the results) -// -// The masters implement receiveFn to receive a work unit's results. -// -// Returns time it took to finish the work. -double DistributeWork( - uint64 nWorkUnits, // how many work units to dole out - char cPacketID, // This packet ID must be reserved for DistributeWork and DistributeWorkDispatch - // must be registered with it. - ProcessWorkUnitFn processFn, // workers implement this to process a work unit and send results back - ReceiveWorkUnitFn receiveFn // the master implements this to receive a work unit - ); - - -// VMPI calls this before shutting down because any threads that DistributeWork has running must stop, -// otherwise it can crash if a thread tries to send data in the middle of shutting down. -void DistributeWork_Cancel(); - - -#endif // VMPI_DISTRIBUTE_WORK_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef VMPI_DISTRIBUTE_WORK_H +#define VMPI_DISTRIBUTE_WORK_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "messbuf.h" +#include "utlvector.h" + + +class IWorkUnitDistributorCallbacks +{ +public: + // Called every 200ms or so as it does the work. + // Return true to stop distributing work. + virtual bool Update() { return false; } + + // Called when a subsequent number of work units is completed. + // e.g. results received in the following order will trigger + // the following calls to OnWorkUnitsCompleted: + // Work unit numbers: wu2 wu4 wu5 wu1 wu0 wu6 wu3 + // Calling OnWorkUnitsCompleted with arg: - - - - 3 - 7 + // because when wu0 is received we already have { wu0, wu1, wu2 } so we signal + // that 3 subsequent work units completed, like wise by the time when wu3 is + // received we already have a full set { wu0, wu1, wu2, wu3, wu4, wu5, wu6 } + // and signal that 7 work units completed. + virtual void OnWorkUnitsCompleted( uint64 numWorkUnits ) { return; } +}; + + +enum EWorkUnitDistributor +{ + k_eWorkUnitDistributor_Default, + k_eWorkUnitDistributor_SDK +}; + +// Tells which work unit distributor is going to be used. +EWorkUnitDistributor VMPI_GetActiveWorkUnitDistributor(); + + +// Before calling DistributeWork, you can set this and it'll call your virtual functions. +extern IWorkUnitDistributorCallbacks *g_pDistributeWorkCallbacks; + + +// You must append data to pBuf with the work unit results. +// Note: pBuf will be NULL if this is a local thread doing work on the master. +typedef void (*ProcessWorkUnitFn)( int iThread, uint64 iWorkUnit, MessageBuffer *pBuf ); + +// pBuf is ready to read the results written to the buffer in ProcessWorkUnitFn. +typedef void (*ReceiveWorkUnitFn)( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ); + + +// Use a CDispatchReg to register this function with whatever packet ID you give to DistributeWork. +bool DistributeWorkDispatch( MessageBuffer *pBuf, int iSource, int iPacketID ); + + + +// This is the function vrad and vvis use to divide the work units and send them out. +// It maintains a sliding window of work units so it can always keep the clients busy. +// +// The workers implement processFn to do the job work in a work unit. +// This function must send back a packet formatted with: +// cPacketID (char), cSubPacketID (char), iWorkUnit (int), (app-specific data for the results) +// +// The masters implement receiveFn to receive a work unit's results. +// +// Returns time it took to finish the work. +double DistributeWork( + uint64 nWorkUnits, // how many work units to dole out + char cPacketID, // This packet ID must be reserved for DistributeWork and DistributeWorkDispatch + // must be registered with it. + ProcessWorkUnitFn processFn, // workers implement this to process a work unit and send results back + ReceiveWorkUnitFn receiveFn // the master implements this to receive a work unit + ); + + +// VMPI calls this before shutting down because any threads that DistributeWork has running must stop, +// otherwise it can crash if a thread tries to send data in the middle of shutting down. +void DistributeWork_Cancel(); + + +#endif // VMPI_DISTRIBUTE_WORK_H diff --git a/mp/src/utils/vmpi/vmpi_filesystem.h b/mp/src/utils/vmpi/vmpi_filesystem.h index 889d8abc..f3c3c8a7 100644 --- a/mp/src/utils/vmpi/vmpi_filesystem.h +++ b/mp/src/utils/vmpi/vmpi_filesystem.h @@ -1,53 +1,53 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef VMPI_FILESYSTEM_H -#define VMPI_FILESYSTEM_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "interface.h" - - -class IFileSystem; -class MessageBuffer; - - -// Use this to read virtual files. -#define VMPI_VIRTUAL_FILES_PATH_ID "VMPI_VIRTUAL_FILES_PATH_ID" - - -// When you hook the file system with VMPI and are a worker, it blocks on file reads -// and uses MPI to communicate with the master to transfer files it needs over. -// -// The filesystem, by default (and it maxFileSystemMemoryUsage is left at zero), -// keeps the contents of the files that get opened in memory. You can pass in a -// value here to put a cap on it, in which case it'll unload the least-recently-used -// files when it hits the limit. -IFileSystem* VMPI_FileSystem_Init( int maxFileSystemMemoryUsage, IFileSystem *pPassThru ); - -// On the master machine, this really should be called before the app shuts down and -// global destructors are called. If it isn't, it might lock up waiting for a thread to exit. -// -// This returns the original filesystem you passed into VMPI_FileSystem_Init so you can uninitialize it. -IFileSystem* VMPI_FileSystem_Term(); - -// Causes it to error out on any Open() calls. -void VMPI_FileSystem_DisableFileAccess(); - -// Returns a factory that will hand out BASEFILESYSTEM_INTERFACE_VERSION when asked for it. -CreateInterfaceFn VMPI_FileSystem_GetFactory(); - -// This function creates a virtual file that workers can then open and read out of. -// NOTE: when reading from the file, you must use VMPI_VIRTUAL_FILES_PATH_ID as the path ID -// or else it won't find the file. -void VMPI_FileSystem_CreateVirtualFile( const char *pFilename, const void *pData, unsigned long fileLength ); - - -#endif // VMPI_FILESYSTEM_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VMPI_FILESYSTEM_H +#define VMPI_FILESYSTEM_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "interface.h" + + +class IFileSystem; +class MessageBuffer; + + +// Use this to read virtual files. +#define VMPI_VIRTUAL_FILES_PATH_ID "VMPI_VIRTUAL_FILES_PATH_ID" + + +// When you hook the file system with VMPI and are a worker, it blocks on file reads +// and uses MPI to communicate with the master to transfer files it needs over. +// +// The filesystem, by default (and it maxFileSystemMemoryUsage is left at zero), +// keeps the contents of the files that get opened in memory. You can pass in a +// value here to put a cap on it, in which case it'll unload the least-recently-used +// files when it hits the limit. +IFileSystem* VMPI_FileSystem_Init( int maxFileSystemMemoryUsage, IFileSystem *pPassThru ); + +// On the master machine, this really should be called before the app shuts down and +// global destructors are called. If it isn't, it might lock up waiting for a thread to exit. +// +// This returns the original filesystem you passed into VMPI_FileSystem_Init so you can uninitialize it. +IFileSystem* VMPI_FileSystem_Term(); + +// Causes it to error out on any Open() calls. +void VMPI_FileSystem_DisableFileAccess(); + +// Returns a factory that will hand out BASEFILESYSTEM_INTERFACE_VERSION when asked for it. +CreateInterfaceFn VMPI_FileSystem_GetFactory(); + +// This function creates a virtual file that workers can then open and read out of. +// NOTE: when reading from the file, you must use VMPI_VIRTUAL_FILES_PATH_ID as the path ID +// or else it won't find the file. +void VMPI_FileSystem_CreateVirtualFile( const char *pFilename, const void *pData, unsigned long fileLength ); + + +#endif // VMPI_FILESYSTEM_H diff --git a/mp/src/utils/vmpi/vmpi_parameters.h b/mp/src/utils/vmpi/vmpi_parameters.h index db467cd0..e6f0abc9 100644 --- a/mp/src/utils/vmpi/vmpi_parameters.h +++ b/mp/src/utils/vmpi/vmpi_parameters.h @@ -1,31 +1,31 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//============================================================================= - -VMPI_PARAM( mpi_Worker, 0, "Workers use this to connect to a VMPI job. Specify the IP address of the master. Example: -mpi_worker 1.2.3.4 or -mpi_worker 1.2.3.4:242" ) -VMPI_PARAM( mpi_Port, 0, "Use this on the master to force it to bind to a specified port. Otherwise it binds to 23311 (and ascending port numbers if 23311 doesn't work)." ) -VMPI_PARAM( mpi_Graphics, 0, "Show a graphical representation of work units [grey=work unit not sent yet, red=sent, green=completed, blue=in-process]" ) -VMPI_PARAM( mpi_Retry, 0, "Use this on the worker to have it retry connecting to the master forever. Otherwise it will exit if it can't connect to the master immediately." ) -VMPI_PARAM( mpi_AutoRestart, 0, "Use this on the worker to have it restart with the same command line parameters after completing a job. Useful in conjunction with -mpi_Retry to have an always-on worker ready to do work." ) -VMPI_PARAM( mpi_TrackEvents, 0, "Enables a debug menu during jobs (press D to access). Note: -mpi_Graphics automatically enables -mpi_TrackEvents." ) -VMPI_PARAM( mpi_ShowDistributeWorkStats, 0, "After finishing a stage in the work unit processing, shows statistics." ) -VMPI_PARAM( mpi_TimingWait, 0, "Causes the master to wait for a keypress to start so workers can connect before it starts. Used for performance measurements." ) -VMPI_PARAM( mpi_WorkerCount, 0, "Set the maximum number of workers allowed in the job." ) -VMPI_PARAM( mpi_AutoLocalWorker, 0, "Used on the master's machine. Automatically spawn a worker on the local machine. Used for testing." ) -VMPI_PARAM( mpi_FileTransmitRate, 0, "VMPI file transmission rate in kB/sec." ) -VMPI_PARAM( mpi_Verbose, 0, "Set to 0, 1, or 2 to control verbosity of debug output." ) -VMPI_PARAM( mpi_NoMasterWorkerThreads, 0, "Don't process work units locally (in the master). Only used by the SDK work unit distributor." ) -VMPI_PARAM( mpi_SDKMode, VMPI_PARAM_SDK_HIDDEN, "Force VMPI to run in SDK mode." ) -VMPI_PARAM( mpi_UseSDKDistributor, VMPI_PARAM_SDK_HIDDEN, "Use the SDK work unit distributor. Optimized for low numbers of workers and higher latency. Note that this will automatically be used in SDK distributions." ) -VMPI_PARAM( mpi_UseDefaultDistributor, VMPI_PARAM_SDK_HIDDEN, "Use the default work unit distributor. Optimized for high numbers of workers, higher numbers of work units, and lower latency. Note that this will automatically be used in non-SDK distributions." ) -VMPI_PARAM( mpi_NoTimeout, VMPI_PARAM_SDK_HIDDEN, "Don't timeout VMPI sockets. Used for testing." ) -VMPI_PARAM( mpi_DontSetThreadPriorities, VMPI_PARAM_SDK_HIDDEN, "Don't set worker thread priorities to idle." ) -VMPI_PARAM( mpi_GroupPackets, VMPI_PARAM_SDK_HIDDEN, "Delay and group some of the worker packets instead of sending immediately." ) -VMPI_PARAM( mpi_Stats, VMPI_PARAM_SDK_HIDDEN, "Enables the use of a database to store compile statistics." ) -VMPI_PARAM( mpi_Stats_TextOutput, VMPI_PARAM_SDK_HIDDEN, "Enables the workers storing all of their text output into the stats database." ) -VMPI_PARAM( mpi_pw, VMPI_PARAM_SDK_HIDDEN, "Non-SDK only. Sets a password on the VMPI job. Workers must also use the same -mpi_pw [password] argument or else the master will ignore their requests to join the job." ) -VMPI_PARAM( mpi_CalcShuffleCRC, VMPI_PARAM_SDK_HIDDEN, "Calculate a CRC for shuffled work unit arrays in the SDK work unit distributor." ) -VMPI_PARAM( mpi_Job_Watch, VMPI_PARAM_SDK_HIDDEN, "Automatically launches vmpi_job_watch.exe on the job." ) +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +VMPI_PARAM( mpi_Worker, 0, "Workers use this to connect to a VMPI job. Specify the IP address of the master. Example: -mpi_worker 1.2.3.4 or -mpi_worker 1.2.3.4:242" ) +VMPI_PARAM( mpi_Port, 0, "Use this on the master to force it to bind to a specified port. Otherwise it binds to 23311 (and ascending port numbers if 23311 doesn't work)." ) +VMPI_PARAM( mpi_Graphics, 0, "Show a graphical representation of work units [grey=work unit not sent yet, red=sent, green=completed, blue=in-process]" ) +VMPI_PARAM( mpi_Retry, 0, "Use this on the worker to have it retry connecting to the master forever. Otherwise it will exit if it can't connect to the master immediately." ) +VMPI_PARAM( mpi_AutoRestart, 0, "Use this on the worker to have it restart with the same command line parameters after completing a job. Useful in conjunction with -mpi_Retry to have an always-on worker ready to do work." ) +VMPI_PARAM( mpi_TrackEvents, 0, "Enables a debug menu during jobs (press D to access). Note: -mpi_Graphics automatically enables -mpi_TrackEvents." ) +VMPI_PARAM( mpi_ShowDistributeWorkStats, 0, "After finishing a stage in the work unit processing, shows statistics." ) +VMPI_PARAM( mpi_TimingWait, 0, "Causes the master to wait for a keypress to start so workers can connect before it starts. Used for performance measurements." ) +VMPI_PARAM( mpi_WorkerCount, 0, "Set the maximum number of workers allowed in the job." ) +VMPI_PARAM( mpi_AutoLocalWorker, 0, "Used on the master's machine. Automatically spawn a worker on the local machine. Used for testing." ) +VMPI_PARAM( mpi_FileTransmitRate, 0, "VMPI file transmission rate in kB/sec." ) +VMPI_PARAM( mpi_Verbose, 0, "Set to 0, 1, or 2 to control verbosity of debug output." ) +VMPI_PARAM( mpi_NoMasterWorkerThreads, 0, "Don't process work units locally (in the master). Only used by the SDK work unit distributor." ) +VMPI_PARAM( mpi_SDKMode, VMPI_PARAM_SDK_HIDDEN, "Force VMPI to run in SDK mode." ) +VMPI_PARAM( mpi_UseSDKDistributor, VMPI_PARAM_SDK_HIDDEN, "Use the SDK work unit distributor. Optimized for low numbers of workers and higher latency. Note that this will automatically be used in SDK distributions." ) +VMPI_PARAM( mpi_UseDefaultDistributor, VMPI_PARAM_SDK_HIDDEN, "Use the default work unit distributor. Optimized for high numbers of workers, higher numbers of work units, and lower latency. Note that this will automatically be used in non-SDK distributions." ) +VMPI_PARAM( mpi_NoTimeout, VMPI_PARAM_SDK_HIDDEN, "Don't timeout VMPI sockets. Used for testing." ) +VMPI_PARAM( mpi_DontSetThreadPriorities, VMPI_PARAM_SDK_HIDDEN, "Don't set worker thread priorities to idle." ) +VMPI_PARAM( mpi_GroupPackets, VMPI_PARAM_SDK_HIDDEN, "Delay and group some of the worker packets instead of sending immediately." ) +VMPI_PARAM( mpi_Stats, VMPI_PARAM_SDK_HIDDEN, "Enables the use of a database to store compile statistics." ) +VMPI_PARAM( mpi_Stats_TextOutput, VMPI_PARAM_SDK_HIDDEN, "Enables the workers storing all of their text output into the stats database." ) +VMPI_PARAM( mpi_pw, VMPI_PARAM_SDK_HIDDEN, "Non-SDK only. Sets a password on the VMPI job. Workers must also use the same -mpi_pw [password] argument or else the master will ignore their requests to join the job." ) +VMPI_PARAM( mpi_CalcShuffleCRC, VMPI_PARAM_SDK_HIDDEN, "Calculate a CRC for shuffled work unit arrays in the SDK work unit distributor." ) +VMPI_PARAM( mpi_Job_Watch, VMPI_PARAM_SDK_HIDDEN, "Automatically launches vmpi_job_watch.exe on the job." ) VMPI_PARAM( mpi_Local, VMPI_PARAM_SDK_HIDDEN, "Similar to -mpi_AutoLocalWorker, but the automatically-spawned worker's console window is hidden." ) \ No newline at end of file diff --git a/mp/src/utils/vrad/disp_vrad.cpp b/mp/src/utils/vrad/disp_vrad.cpp index e1ef3a2e..b254b9d7 100644 --- a/mp/src/utils/vrad/disp_vrad.cpp +++ b/mp/src/utils/vrad/disp_vrad.cpp @@ -1,332 +1,332 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "disp_vrad.h" -#include "utllinkedlist.h" -#include "utlvector.h" -#include "iscratchpad3d.h" -#include "scratchpadutils.h" - - -//#define USE_SCRATCHPAD -#if defined( USE_SCRATCHPAD ) - static IScratchPad3D *g_pPad = 0; -#endif - - -int FindNeighborCornerVert( CCoreDispInfo *pDisp, const Vector &vTest ) -{ - CDispUtilsHelper *pDispHelper = pDisp; - - int iClosest = 0; - float flClosest = 1e24; - for ( int iCorner=0; iCorner < 4; iCorner++ ) - { - // Has it been touched? - CVertIndex cornerVert = pDispHelper->GetPowerInfo()->GetCornerPointIndex( iCorner ); - int iCornerVert = pDispHelper->VertIndexToInt( cornerVert ); - const Vector &vCornerVert = pDisp->GetVert( iCornerVert ); - - float flDist = vCornerVert.DistTo( vTest ); - if ( flDist < flClosest ) - { - iClosest = iCorner; - flClosest = flDist; - } - } - - if ( flClosest <= 0.1f ) - return iClosest; - else - return -1; -} - - -int GetAllNeighbors( const CCoreDispInfo *pDisp, int iNeighbors[512] ) -{ - int nNeighbors = 0; - - // Check corner neighbors. - for ( int iCorner=0; iCorner < 4; iCorner++ ) - { - const CDispCornerNeighbors *pCorner = pDisp->GetCornerNeighbors( iCorner ); - - for ( int i=0; i < pCorner->m_nNeighbors; i++ ) - { - if ( nNeighbors < _ARRAYSIZE( iNeighbors ) ) - iNeighbors[nNeighbors++] = pCorner->m_Neighbors[i]; - } - } - - for ( int iEdge=0; iEdge < 4; iEdge++ ) - { - const CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge ); - - for ( int i=0; i < 2; i++ ) - { - if ( pEdge->m_SubNeighbors[i].IsValid() ) - if ( nNeighbors < 512 ) - iNeighbors[nNeighbors++] = pEdge->m_SubNeighbors[i].GetNeighborIndex(); - } - } - - return nNeighbors; -} - - -void BlendCorners( CCoreDispInfo **ppListBase, int listSize ) -{ - CUtlVector nbCornerVerts; - - for ( int iDisp=0; iDisp < listSize; iDisp++ ) - { - CCoreDispInfo *pDisp = ppListBase[iDisp]; - - int iNeighbors[512]; - int nNeighbors = GetAllNeighbors( pDisp, iNeighbors ); - - // Make sure we have room for all the neighbors. - nbCornerVerts.RemoveAll(); - nbCornerVerts.EnsureCapacity( nNeighbors ); - nbCornerVerts.AddMultipleToTail( nNeighbors ); - - // For each corner. - for ( int iCorner=0; iCorner < 4; iCorner++ ) - { - // Has it been touched? - CVertIndex cornerVert = pDisp->GetCornerPointIndex( iCorner ); - int iCornerVert = pDisp->VertIndexToInt( cornerVert ); - const Vector &vCornerVert = pDisp->GetVert( iCornerVert ); - - // For each displacement sharing this corner.. - Vector vAverage = pDisp->GetNormal( iCornerVert ); - - for ( int iNeighbor=0; iNeighbor < nNeighbors; iNeighbor++ ) - { - int iNBListIndex = iNeighbors[iNeighbor]; - CCoreDispInfo *pNeighbor = ppListBase[iNBListIndex]; - - // Find out which vert it is on the neighbor. - int iNBCorner = FindNeighborCornerVert( pNeighbor, vCornerVert ); - if ( iNBCorner == -1 ) - { - nbCornerVerts[iNeighbor] = -1; // remove this neighbor from the list. - } - else - { - CVertIndex viNBCornerVert = pNeighbor->GetCornerPointIndex( iNBCorner ); - int iNBVert = pNeighbor->VertIndexToInt( viNBCornerVert ); - nbCornerVerts[iNeighbor] = iNBVert; - vAverage += pNeighbor->GetNormal( iNBVert ); - } - } - - - // Blend all the neighbor normals with this one. - VectorNormalize( vAverage ); - pDisp->SetNormal( iCornerVert, vAverage ); - -#if defined( USE_SCRATCHPAD ) - ScratchPad_DrawArrowSimple( - g_pPad, - pDisp->GetVert( iCornerVert ), - pDisp->GetNormal( iCornerVert ), - Vector( 0, 0, 1 ), - 25 ); -#endif - - for ( int iNeighbor=0; iNeighbor < nNeighbors; iNeighbor++ ) - { - int iNBListIndex = iNeighbors[iNeighbor]; - if ( nbCornerVerts[iNeighbor] == -1 ) - continue; - - CCoreDispInfo *pNeighbor = ppListBase[iNBListIndex]; - pNeighbor->SetNormal( nbCornerVerts[iNeighbor], vAverage ); - } - } - } -} - - -void BlendTJuncs( CCoreDispInfo **ppListBase, int listSize ) -{ - for ( int iDisp=0; iDisp < listSize; iDisp++ ) - { - CCoreDispInfo *pDisp = ppListBase[iDisp]; - - for ( int iEdge=0; iEdge < 4; iEdge++ ) - { - CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge ); - - CVertIndex viMidPoint = pDisp->GetEdgeMidPoint( iEdge ); - int iMidPoint = pDisp->VertIndexToInt( viMidPoint ); - - if ( pEdge->m_SubNeighbors[0].IsValid() && pEdge->m_SubNeighbors[1].IsValid() ) - { - const Vector &vMidPoint = pDisp->GetVert( iMidPoint ); - - CCoreDispInfo *pNeighbor1 = ppListBase[pEdge->m_SubNeighbors[0].GetNeighborIndex()]; - CCoreDispInfo *pNeighbor2 = ppListBase[pEdge->m_SubNeighbors[1].GetNeighborIndex()]; - - int iNBCorners[2]; - iNBCorners[0] = FindNeighborCornerVert( pNeighbor1, vMidPoint ); - iNBCorners[1] = FindNeighborCornerVert( pNeighbor2, vMidPoint ); - - if ( iNBCorners[0] != -1 && iNBCorners[1] != -1 ) - { - CVertIndex viNBCorners[2] = - { - pNeighbor1->GetCornerPointIndex( iNBCorners[0] ), - pNeighbor2->GetCornerPointIndex( iNBCorners[1] ) - }; - - Vector vAverage = pDisp->GetNormal( iMidPoint ); - vAverage += pNeighbor1->GetNormal( viNBCorners[0] ); - vAverage += pNeighbor2->GetNormal( viNBCorners[1] ); - - VectorNormalize( vAverage ); - pDisp->SetNormal( iMidPoint, vAverage ); - pNeighbor1->SetNormal( viNBCorners[0], vAverage ); - pNeighbor2->SetNormal( viNBCorners[1], vAverage ); - -#if defined( USE_SCRATCHPAD ) - ScratchPad_DrawArrowSimple( g_pPad, pDisp->GetVert( iMidPoint ), pDisp->GetNormal( iMidPoint ), Vector( 0, 1, 1 ), 25 ); -#endif - } - } - } - } -} - - -void BlendEdges( CCoreDispInfo **ppListBase, int listSize ) -{ - for ( int iDisp=0; iDisp < listSize; iDisp++ ) - { - CCoreDispInfo *pDisp = ppListBase[iDisp]; - - for ( int iEdge=0; iEdge < 4; iEdge++ ) - { - CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge ); - - for ( int iSub=0; iSub < 2; iSub++ ) - { - CDispSubNeighbor *pSub = &pEdge->m_SubNeighbors[iSub]; - if ( !pSub->IsValid() ) - continue; - - CCoreDispInfo *pNeighbor = ppListBase[ pSub->GetNeighborIndex() ]; - - int iEdgeDim = g_EdgeDims[iEdge]; - - CDispSubEdgeIterator it; - it.Start( pDisp, iEdge, iSub, true ); - - // Get setup on the first corner vert. - it.Next(); - CVertIndex viPrevPos = it.GetVertIndex(); - - while ( it.Next() ) - { - // Blend the two. - if ( !it.IsLastVert() ) - { - Vector vAverage = pDisp->GetNormal( it.GetVertIndex() ) + pNeighbor->GetNormal( it.GetNBVertIndex() ); - VectorNormalize( vAverage ); - - pDisp->SetNormal( it.GetVertIndex(), vAverage ); - pNeighbor->SetNormal( it.GetNBVertIndex(), vAverage ); - -#if defined( USE_SCRATCHPAD ) - ScratchPad_DrawArrowSimple( g_pPad, pDisp->GetVert( it.GetVertIndex() ), pDisp->GetNormal( it.GetVertIndex() ), Vector( 1, 0, 0 ), 25 ); -#endif - } - - // Now blend the in-between verts (if this edge is high-res). - int iPrevPos = viPrevPos[ !iEdgeDim ]; - int iCurPos = it.GetVertIndex()[ !iEdgeDim ]; - - for ( int iTween = iPrevPos+1; iTween < iCurPos; iTween++ ) - { - float flPercent = RemapVal( iTween, iPrevPos, iCurPos, 0, 1 ); - Vector vNormal; - VectorLerp( pDisp->GetNormal( viPrevPos ), pDisp->GetNormal( it.GetVertIndex() ), flPercent, vNormal ); - VectorNormalize( vNormal ); - - CVertIndex viTween; - viTween[iEdgeDim] = it.GetVertIndex()[ iEdgeDim ]; - viTween[!iEdgeDim] = iTween; - pDisp->SetNormal( viTween, vNormal ); - -#if defined( USE_SCRATCHPAD ) - ScratchPad_DrawArrowSimple( g_pPad, pDisp->GetVert( viTween ), pDisp->GetNormal( viTween ), Vector( 1, 0.5, 0 ), 25 ); -#endif - } - - viPrevPos = it.GetVertIndex(); - } - } - } - } -} - - -#if defined( USE_SCRATCHPAD ) - void ScratchPad_DrawOriginalNormals( const CCoreDispInfo *pListBase, int listSize ) - { - for ( int i=0; i < listSize; i++ ) - { - const CCoreDispInfo *pDisp = &pListBase[i]; - const CPowerInfo *pPowerInfo = pDisp->GetPowerInfo(); - - // Draw the triangles. - for ( int iTri=0; iTri < pPowerInfo->GetNumTriInfos(); iTri++ ) - { - const CTriInfo *pTriInfo = pPowerInfo->GetTriInfo( iTri ); - - for ( int iLine=0; iLine < 3; iLine++ ) - { - const Vector &v1 = pDisp->GetVert( pTriInfo->m_Indices[iLine] ); - const Vector &v2 = pDisp->GetVert( pTriInfo->m_Indices[(iLine+1)%3] ); - - g_pPad->DrawLine( CSPVert( v1 ), CSPVert( v2 ) ); - } - } - - // Draw the normals. - CDispCircumferenceIterator it( pPowerInfo->GetSideLength() ); - while ( it.Next() ) - { - ScratchPad_DrawArrowSimple( - g_pPad, - pDisp->GetVert( it.GetVertIndex() ), - pDisp->GetNormal( it.GetVertIndex() ), - Vector( 0, 1, 0 ), - 15 ); - } - } - } -#endif - - -void SmoothNeighboringDispSurfNormals( CCoreDispInfo **ppListBase, int listSize ) -{ -//#if defined( USE_SCRATCHPAD ) -// g_pPad = ScratchPad3D_Create(); -// ScratchPad_DrawOriginalNormals( pListBase, listSize ); -//#endif - - BlendTJuncs( ppListBase, listSize ); - - BlendCorners( ppListBase, listSize ); - - BlendEdges( ppListBase, listSize ); -} - - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "disp_vrad.h" +#include "utllinkedlist.h" +#include "utlvector.h" +#include "iscratchpad3d.h" +#include "scratchpadutils.h" + + +//#define USE_SCRATCHPAD +#if defined( USE_SCRATCHPAD ) + static IScratchPad3D *g_pPad = 0; +#endif + + +int FindNeighborCornerVert( CCoreDispInfo *pDisp, const Vector &vTest ) +{ + CDispUtilsHelper *pDispHelper = pDisp; + + int iClosest = 0; + float flClosest = 1e24; + for ( int iCorner=0; iCorner < 4; iCorner++ ) + { + // Has it been touched? + CVertIndex cornerVert = pDispHelper->GetPowerInfo()->GetCornerPointIndex( iCorner ); + int iCornerVert = pDispHelper->VertIndexToInt( cornerVert ); + const Vector &vCornerVert = pDisp->GetVert( iCornerVert ); + + float flDist = vCornerVert.DistTo( vTest ); + if ( flDist < flClosest ) + { + iClosest = iCorner; + flClosest = flDist; + } + } + + if ( flClosest <= 0.1f ) + return iClosest; + else + return -1; +} + + +int GetAllNeighbors( const CCoreDispInfo *pDisp, int iNeighbors[512] ) +{ + int nNeighbors = 0; + + // Check corner neighbors. + for ( int iCorner=0; iCorner < 4; iCorner++ ) + { + const CDispCornerNeighbors *pCorner = pDisp->GetCornerNeighbors( iCorner ); + + for ( int i=0; i < pCorner->m_nNeighbors; i++ ) + { + if ( nNeighbors < _ARRAYSIZE( iNeighbors ) ) + iNeighbors[nNeighbors++] = pCorner->m_Neighbors[i]; + } + } + + for ( int iEdge=0; iEdge < 4; iEdge++ ) + { + const CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge ); + + for ( int i=0; i < 2; i++ ) + { + if ( pEdge->m_SubNeighbors[i].IsValid() ) + if ( nNeighbors < 512 ) + iNeighbors[nNeighbors++] = pEdge->m_SubNeighbors[i].GetNeighborIndex(); + } + } + + return nNeighbors; +} + + +void BlendCorners( CCoreDispInfo **ppListBase, int listSize ) +{ + CUtlVector nbCornerVerts; + + for ( int iDisp=0; iDisp < listSize; iDisp++ ) + { + CCoreDispInfo *pDisp = ppListBase[iDisp]; + + int iNeighbors[512]; + int nNeighbors = GetAllNeighbors( pDisp, iNeighbors ); + + // Make sure we have room for all the neighbors. + nbCornerVerts.RemoveAll(); + nbCornerVerts.EnsureCapacity( nNeighbors ); + nbCornerVerts.AddMultipleToTail( nNeighbors ); + + // For each corner. + for ( int iCorner=0; iCorner < 4; iCorner++ ) + { + // Has it been touched? + CVertIndex cornerVert = pDisp->GetCornerPointIndex( iCorner ); + int iCornerVert = pDisp->VertIndexToInt( cornerVert ); + const Vector &vCornerVert = pDisp->GetVert( iCornerVert ); + + // For each displacement sharing this corner.. + Vector vAverage = pDisp->GetNormal( iCornerVert ); + + for ( int iNeighbor=0; iNeighbor < nNeighbors; iNeighbor++ ) + { + int iNBListIndex = iNeighbors[iNeighbor]; + CCoreDispInfo *pNeighbor = ppListBase[iNBListIndex]; + + // Find out which vert it is on the neighbor. + int iNBCorner = FindNeighborCornerVert( pNeighbor, vCornerVert ); + if ( iNBCorner == -1 ) + { + nbCornerVerts[iNeighbor] = -1; // remove this neighbor from the list. + } + else + { + CVertIndex viNBCornerVert = pNeighbor->GetCornerPointIndex( iNBCorner ); + int iNBVert = pNeighbor->VertIndexToInt( viNBCornerVert ); + nbCornerVerts[iNeighbor] = iNBVert; + vAverage += pNeighbor->GetNormal( iNBVert ); + } + } + + + // Blend all the neighbor normals with this one. + VectorNormalize( vAverage ); + pDisp->SetNormal( iCornerVert, vAverage ); + +#if defined( USE_SCRATCHPAD ) + ScratchPad_DrawArrowSimple( + g_pPad, + pDisp->GetVert( iCornerVert ), + pDisp->GetNormal( iCornerVert ), + Vector( 0, 0, 1 ), + 25 ); +#endif + + for ( int iNeighbor=0; iNeighbor < nNeighbors; iNeighbor++ ) + { + int iNBListIndex = iNeighbors[iNeighbor]; + if ( nbCornerVerts[iNeighbor] == -1 ) + continue; + + CCoreDispInfo *pNeighbor = ppListBase[iNBListIndex]; + pNeighbor->SetNormal( nbCornerVerts[iNeighbor], vAverage ); + } + } + } +} + + +void BlendTJuncs( CCoreDispInfo **ppListBase, int listSize ) +{ + for ( int iDisp=0; iDisp < listSize; iDisp++ ) + { + CCoreDispInfo *pDisp = ppListBase[iDisp]; + + for ( int iEdge=0; iEdge < 4; iEdge++ ) + { + CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge ); + + CVertIndex viMidPoint = pDisp->GetEdgeMidPoint( iEdge ); + int iMidPoint = pDisp->VertIndexToInt( viMidPoint ); + + if ( pEdge->m_SubNeighbors[0].IsValid() && pEdge->m_SubNeighbors[1].IsValid() ) + { + const Vector &vMidPoint = pDisp->GetVert( iMidPoint ); + + CCoreDispInfo *pNeighbor1 = ppListBase[pEdge->m_SubNeighbors[0].GetNeighborIndex()]; + CCoreDispInfo *pNeighbor2 = ppListBase[pEdge->m_SubNeighbors[1].GetNeighborIndex()]; + + int iNBCorners[2]; + iNBCorners[0] = FindNeighborCornerVert( pNeighbor1, vMidPoint ); + iNBCorners[1] = FindNeighborCornerVert( pNeighbor2, vMidPoint ); + + if ( iNBCorners[0] != -1 && iNBCorners[1] != -1 ) + { + CVertIndex viNBCorners[2] = + { + pNeighbor1->GetCornerPointIndex( iNBCorners[0] ), + pNeighbor2->GetCornerPointIndex( iNBCorners[1] ) + }; + + Vector vAverage = pDisp->GetNormal( iMidPoint ); + vAverage += pNeighbor1->GetNormal( viNBCorners[0] ); + vAverage += pNeighbor2->GetNormal( viNBCorners[1] ); + + VectorNormalize( vAverage ); + pDisp->SetNormal( iMidPoint, vAverage ); + pNeighbor1->SetNormal( viNBCorners[0], vAverage ); + pNeighbor2->SetNormal( viNBCorners[1], vAverage ); + +#if defined( USE_SCRATCHPAD ) + ScratchPad_DrawArrowSimple( g_pPad, pDisp->GetVert( iMidPoint ), pDisp->GetNormal( iMidPoint ), Vector( 0, 1, 1 ), 25 ); +#endif + } + } + } + } +} + + +void BlendEdges( CCoreDispInfo **ppListBase, int listSize ) +{ + for ( int iDisp=0; iDisp < listSize; iDisp++ ) + { + CCoreDispInfo *pDisp = ppListBase[iDisp]; + + for ( int iEdge=0; iEdge < 4; iEdge++ ) + { + CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge ); + + for ( int iSub=0; iSub < 2; iSub++ ) + { + CDispSubNeighbor *pSub = &pEdge->m_SubNeighbors[iSub]; + if ( !pSub->IsValid() ) + continue; + + CCoreDispInfo *pNeighbor = ppListBase[ pSub->GetNeighborIndex() ]; + + int iEdgeDim = g_EdgeDims[iEdge]; + + CDispSubEdgeIterator it; + it.Start( pDisp, iEdge, iSub, true ); + + // Get setup on the first corner vert. + it.Next(); + CVertIndex viPrevPos = it.GetVertIndex(); + + while ( it.Next() ) + { + // Blend the two. + if ( !it.IsLastVert() ) + { + Vector vAverage = pDisp->GetNormal( it.GetVertIndex() ) + pNeighbor->GetNormal( it.GetNBVertIndex() ); + VectorNormalize( vAverage ); + + pDisp->SetNormal( it.GetVertIndex(), vAverage ); + pNeighbor->SetNormal( it.GetNBVertIndex(), vAverage ); + +#if defined( USE_SCRATCHPAD ) + ScratchPad_DrawArrowSimple( g_pPad, pDisp->GetVert( it.GetVertIndex() ), pDisp->GetNormal( it.GetVertIndex() ), Vector( 1, 0, 0 ), 25 ); +#endif + } + + // Now blend the in-between verts (if this edge is high-res). + int iPrevPos = viPrevPos[ !iEdgeDim ]; + int iCurPos = it.GetVertIndex()[ !iEdgeDim ]; + + for ( int iTween = iPrevPos+1; iTween < iCurPos; iTween++ ) + { + float flPercent = RemapVal( iTween, iPrevPos, iCurPos, 0, 1 ); + Vector vNormal; + VectorLerp( pDisp->GetNormal( viPrevPos ), pDisp->GetNormal( it.GetVertIndex() ), flPercent, vNormal ); + VectorNormalize( vNormal ); + + CVertIndex viTween; + viTween[iEdgeDim] = it.GetVertIndex()[ iEdgeDim ]; + viTween[!iEdgeDim] = iTween; + pDisp->SetNormal( viTween, vNormal ); + +#if defined( USE_SCRATCHPAD ) + ScratchPad_DrawArrowSimple( g_pPad, pDisp->GetVert( viTween ), pDisp->GetNormal( viTween ), Vector( 1, 0.5, 0 ), 25 ); +#endif + } + + viPrevPos = it.GetVertIndex(); + } + } + } + } +} + + +#if defined( USE_SCRATCHPAD ) + void ScratchPad_DrawOriginalNormals( const CCoreDispInfo *pListBase, int listSize ) + { + for ( int i=0; i < listSize; i++ ) + { + const CCoreDispInfo *pDisp = &pListBase[i]; + const CPowerInfo *pPowerInfo = pDisp->GetPowerInfo(); + + // Draw the triangles. + for ( int iTri=0; iTri < pPowerInfo->GetNumTriInfos(); iTri++ ) + { + const CTriInfo *pTriInfo = pPowerInfo->GetTriInfo( iTri ); + + for ( int iLine=0; iLine < 3; iLine++ ) + { + const Vector &v1 = pDisp->GetVert( pTriInfo->m_Indices[iLine] ); + const Vector &v2 = pDisp->GetVert( pTriInfo->m_Indices[(iLine+1)%3] ); + + g_pPad->DrawLine( CSPVert( v1 ), CSPVert( v2 ) ); + } + } + + // Draw the normals. + CDispCircumferenceIterator it( pPowerInfo->GetSideLength() ); + while ( it.Next() ) + { + ScratchPad_DrawArrowSimple( + g_pPad, + pDisp->GetVert( it.GetVertIndex() ), + pDisp->GetNormal( it.GetVertIndex() ), + Vector( 0, 1, 0 ), + 15 ); + } + } + } +#endif + + +void SmoothNeighboringDispSurfNormals( CCoreDispInfo **ppListBase, int listSize ) +{ +//#if defined( USE_SCRATCHPAD ) +// g_pPad = ScratchPad3D_Create(); +// ScratchPad_DrawOriginalNormals( pListBase, listSize ); +//#endif + + BlendTJuncs( ppListBase, listSize ); + + BlendCorners( ppListBase, listSize ); + + BlendEdges( ppListBase, listSize ); +} + + + diff --git a/mp/src/utils/vrad/disp_vrad.h b/mp/src/utils/vrad/disp_vrad.h index ec6bb838..0976c574 100644 --- a/mp/src/utils/vrad/disp_vrad.h +++ b/mp/src/utils/vrad/disp_vrad.h @@ -1,22 +1,22 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef DISP_VRAD_H -#define DISP_VRAD_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "builddisp.h" - - -// Blend the normals of neighboring displacement surfaces so they match at edges and corners. -void SmoothNeighboringDispSurfNormals( CCoreDispInfo **ppListBase, int listSize ); - - -#endif // DISP_VRAD_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef DISP_VRAD_H +#define DISP_VRAD_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "builddisp.h" + + +// Blend the normals of neighboring displacement surfaces so they match at edges and corners. +void SmoothNeighboringDispSurfNormals( CCoreDispInfo **ppListBase, int listSize ); + + +#endif // DISP_VRAD_H diff --git a/mp/src/utils/vrad/iincremental.h b/mp/src/utils/vrad/iincremental.h index fbde8c86..c80854f4 100644 --- a/mp/src/utils/vrad/iincremental.h +++ b/mp/src/utils/vrad/iincremental.h @@ -1,71 +1,71 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef IINCREMENTAL_H -#define IINCREMENTAL_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "mathlib/vector.h" -#include "utlvector.h" - - -typedef unsigned short IncrementalLightID; - - -// Incremental lighting manager. -class IIncremental -{ -// IIncremental overrides. -public: - - virtual ~IIncremental() {} - - // Sets up for incremental mode. The BSP file (in bsplib) should be loaded - // already so it can detect if the incremental file is up to date. - virtual bool Init( char const *pBSPFilename, char const *pIncrementalFilename ) = 0; - - // Prepare to light. You must call Init once, but then you can - // do as many Prepare/AddLight/Finalize phases as you want. - virtual bool PrepareForLighting() = 0; - - // Called every time light is added to a face. - // NOTE: This is the ONLY threadsafe function in IIncremental. - virtual void AddLightToFace( - IncrementalLightID lightID, - int iFace, - int iSample, - int lmSize, - float dot, - int iThread ) = 0; - - // Called when it's done applying light from the specified light to the specified face. - virtual void FinishFace ( - IncrementalLightID lightID, - int iFace, - int iThread ) = 0; - - // For each face that was changed during the lighting process, save out - // new data for it in the incremental file. - // Returns false if the incremental lighting isn't active. - virtual bool Finalize() = 0; - - // Grows touched to a size of 'numfaces' and sets each byte to 0 or 1 telling - // if the face's lightmap was updated in Finalize. - virtual void GetFacesTouched( CUtlVector &touched ) = 0; - - // This saves the .r0 file and updates the lighting in the BSP file. - virtual bool Serialize() = 0; -}; - - -extern IIncremental* GetIncremental(); - - -#endif // IINCREMENTAL_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef IINCREMENTAL_H +#define IINCREMENTAL_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "mathlib/vector.h" +#include "utlvector.h" + + +typedef unsigned short IncrementalLightID; + + +// Incremental lighting manager. +class IIncremental +{ +// IIncremental overrides. +public: + + virtual ~IIncremental() {} + + // Sets up for incremental mode. The BSP file (in bsplib) should be loaded + // already so it can detect if the incremental file is up to date. + virtual bool Init( char const *pBSPFilename, char const *pIncrementalFilename ) = 0; + + // Prepare to light. You must call Init once, but then you can + // do as many Prepare/AddLight/Finalize phases as you want. + virtual bool PrepareForLighting() = 0; + + // Called every time light is added to a face. + // NOTE: This is the ONLY threadsafe function in IIncremental. + virtual void AddLightToFace( + IncrementalLightID lightID, + int iFace, + int iSample, + int lmSize, + float dot, + int iThread ) = 0; + + // Called when it's done applying light from the specified light to the specified face. + virtual void FinishFace ( + IncrementalLightID lightID, + int iFace, + int iThread ) = 0; + + // For each face that was changed during the lighting process, save out + // new data for it in the incremental file. + // Returns false if the incremental lighting isn't active. + virtual bool Finalize() = 0; + + // Grows touched to a size of 'numfaces' and sets each byte to 0 or 1 telling + // if the face's lightmap was updated in Finalize. + virtual void GetFacesTouched( CUtlVector &touched ) = 0; + + // This saves the .r0 file and updates the lighting in the BSP file. + virtual bool Serialize() = 0; +}; + + +extern IIncremental* GetIncremental(); + + +#endif // IINCREMENTAL_H diff --git a/mp/src/utils/vrad/imagepacker.cpp b/mp/src/utils/vrad/imagepacker.cpp index 612b0d57..f1cb5ed6 100644 --- a/mp/src/utils/vrad/imagepacker.cpp +++ b/mp/src/utils/vrad/imagepacker.cpp @@ -1,141 +1,141 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// The copyright to the contents herein is the property of Valve, L.L.C. -// The contents may be used and/or copied only with the written permission of -// Valve, L.L.C., or in accordance with the terms and conditions stipulated in -// the agreement/contract under which the contents have been supplied. -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//============================================================================= - -#include "vrad.h" -#include "imagepacker.h" - - -bool CImagePacker::Reset( int maxLightmapWidth, int maxLightmapHeight ) -{ - int i; - - Assert( maxLightmapWidth <= MAX_MAX_LIGHTMAP_WIDTH ); - - m_MaxLightmapWidth = maxLightmapWidth; - m_MaxLightmapHeight = maxLightmapHeight; - - m_MaxBlockWidth = maxLightmapWidth + 1; - m_MaxBlockHeight = maxLightmapHeight + 1; - - m_AreaUsed = 0; - m_MinimumHeight = -1; - for( i = 0; i < m_MaxLightmapWidth; i++ ) - { - m_pLightmapWavefront[i] = -1; - } - return true; -} - - -inline int CImagePacker::GetMaxYIndex( int firstX, int width ) -{ - int maxY = -1; - int maxYIndex = 0; - for( int x = firstX; x < firstX + width; ++x ) - { - // NOTE: Want the equals here since we'll never be able to fit - // in between the multiple instances of maxY - if( m_pLightmapWavefront[x] >= maxY ) - { - maxY = m_pLightmapWavefront[x]; - maxYIndex = x; - } - } - return maxYIndex; -} - - -bool CImagePacker::AddBlock( int width, int height, int *returnX, int *returnY ) -{ - // If we've already determined that a block this big couldn't fit - // then blow off checking again... - if ( ( width >= m_MaxBlockWidth ) && ( height >= m_MaxBlockHeight ) ) - return false; - - int bestX = -1; - int maxYIdx; - int outerX = 0; - int outerMinY = m_MaxLightmapHeight; - int lastX = m_MaxLightmapWidth - width; - int lastMaxYVal = -2; - while (outerX <= lastX) - { - // Skip all tiles that have the last Y value, these - // aren't going to change our min Y value - if (m_pLightmapWavefront[outerX] == lastMaxYVal) - { - ++outerX; - continue; - } - - maxYIdx = GetMaxYIndex( outerX, width ); - lastMaxYVal = m_pLightmapWavefront[maxYIdx]; - if (outerMinY > lastMaxYVal) - { - outerMinY = lastMaxYVal; - bestX = outerX; - } - outerX = maxYIdx + 1; - } - - if( bestX == -1 ) - { - // If we failed to add it, remember the block size that failed - // *only if both dimensions are smaller*!! - // Just because a 1x10 block failed, doesn't mean a 10x1 block will fail - if ( ( width <= m_MaxBlockWidth ) && ( height <= m_MaxBlockHeight ) ) - { - m_MaxBlockWidth = width; - m_MaxBlockHeight = height; - } - - return false; - } - - // Set the return positions for the block. - *returnX = bestX; - *returnY = outerMinY + 1; - - // Check if it actually fit height-wise. - // hack - // if( *returnY + height > maxLightmapHeight ) - if( *returnY + height >= m_MaxLightmapHeight - 1 ) - { - if ( ( width <= m_MaxBlockWidth ) && ( height <= m_MaxBlockHeight ) ) - { - m_MaxBlockWidth = width; - m_MaxBlockHeight = height; - } - - return false; - } - - // It fit! - // Keep up with the smallest possible size for the image so far. - if( *returnY + height > m_MinimumHeight ) - m_MinimumHeight = *returnY + height; - - // Update the wavefront info. - int x; - for( x = bestX; x < bestX + width; x++ ) - { - m_pLightmapWavefront[x] = outerMinY + height; - } - - // AddBlockToLightmapImage( *returnX, *returnY, width, height ); - m_AreaUsed += width * height; - - return true; -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//============================================================================= + +#include "vrad.h" +#include "imagepacker.h" + + +bool CImagePacker::Reset( int maxLightmapWidth, int maxLightmapHeight ) +{ + int i; + + Assert( maxLightmapWidth <= MAX_MAX_LIGHTMAP_WIDTH ); + + m_MaxLightmapWidth = maxLightmapWidth; + m_MaxLightmapHeight = maxLightmapHeight; + + m_MaxBlockWidth = maxLightmapWidth + 1; + m_MaxBlockHeight = maxLightmapHeight + 1; + + m_AreaUsed = 0; + m_MinimumHeight = -1; + for( i = 0; i < m_MaxLightmapWidth; i++ ) + { + m_pLightmapWavefront[i] = -1; + } + return true; +} + + +inline int CImagePacker::GetMaxYIndex( int firstX, int width ) +{ + int maxY = -1; + int maxYIndex = 0; + for( int x = firstX; x < firstX + width; ++x ) + { + // NOTE: Want the equals here since we'll never be able to fit + // in between the multiple instances of maxY + if( m_pLightmapWavefront[x] >= maxY ) + { + maxY = m_pLightmapWavefront[x]; + maxYIndex = x; + } + } + return maxYIndex; +} + + +bool CImagePacker::AddBlock( int width, int height, int *returnX, int *returnY ) +{ + // If we've already determined that a block this big couldn't fit + // then blow off checking again... + if ( ( width >= m_MaxBlockWidth ) && ( height >= m_MaxBlockHeight ) ) + return false; + + int bestX = -1; + int maxYIdx; + int outerX = 0; + int outerMinY = m_MaxLightmapHeight; + int lastX = m_MaxLightmapWidth - width; + int lastMaxYVal = -2; + while (outerX <= lastX) + { + // Skip all tiles that have the last Y value, these + // aren't going to change our min Y value + if (m_pLightmapWavefront[outerX] == lastMaxYVal) + { + ++outerX; + continue; + } + + maxYIdx = GetMaxYIndex( outerX, width ); + lastMaxYVal = m_pLightmapWavefront[maxYIdx]; + if (outerMinY > lastMaxYVal) + { + outerMinY = lastMaxYVal; + bestX = outerX; + } + outerX = maxYIdx + 1; + } + + if( bestX == -1 ) + { + // If we failed to add it, remember the block size that failed + // *only if both dimensions are smaller*!! + // Just because a 1x10 block failed, doesn't mean a 10x1 block will fail + if ( ( width <= m_MaxBlockWidth ) && ( height <= m_MaxBlockHeight ) ) + { + m_MaxBlockWidth = width; + m_MaxBlockHeight = height; + } + + return false; + } + + // Set the return positions for the block. + *returnX = bestX; + *returnY = outerMinY + 1; + + // Check if it actually fit height-wise. + // hack + // if( *returnY + height > maxLightmapHeight ) + if( *returnY + height >= m_MaxLightmapHeight - 1 ) + { + if ( ( width <= m_MaxBlockWidth ) && ( height <= m_MaxBlockHeight ) ) + { + m_MaxBlockWidth = width; + m_MaxBlockHeight = height; + } + + return false; + } + + // It fit! + // Keep up with the smallest possible size for the image so far. + if( *returnY + height > m_MinimumHeight ) + m_MinimumHeight = *returnY + height; + + // Update the wavefront info. + int x; + for( x = bestX; x < bestX + width; x++ ) + { + m_pLightmapWavefront[x] = outerMinY + height; + } + + // AddBlockToLightmapImage( *returnX, *returnY, width, height ); + m_AreaUsed += width * height; + + return true; +} + diff --git a/mp/src/utils/vrad/imagepacker.h b/mp/src/utils/vrad/imagepacker.h index 0ee1f50e..93a4421a 100644 --- a/mp/src/utils/vrad/imagepacker.h +++ b/mp/src/utils/vrad/imagepacker.h @@ -1,51 +1,51 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// The copyright to the contents herein is the property of Valve, L.L.C. -// The contents may be used and/or copied only with the written permission of -// Valve, L.L.C., or in accordance with the terms and conditions stipulated in -// the agreement/contract under which the contents have been supplied. -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// $NoKeywords: $ -//============================================================================= - -#ifndef IMAGEPACKER_H -#define IMAGEPACKER_H - -#ifdef _WIN32 -#pragma once -#endif - -#define MAX_MAX_LIGHTMAP_WIDTH 2048 - - -//----------------------------------------------------------------------------- -// This packs a single lightmap -//----------------------------------------------------------------------------- -class CImagePacker -{ -public: - bool Reset( int maxLightmapWidth, int maxLightmapHeight ); - bool AddBlock( int width, int height, int *returnX, int *returnY ); - -protected: - int GetMaxYIndex( int firstX, int width ); - - int m_MaxLightmapWidth; - int m_MaxLightmapHeight; - int m_pLightmapWavefront[MAX_MAX_LIGHTMAP_WIDTH]; - int m_AreaUsed; - int m_MinimumHeight; - - // For optimization purposes: - // These store the width + height of the first image - // that was unable to be stored in this image - int m_MaxBlockWidth; - int m_MaxBlockHeight; -}; - - -#endif // IMAGEPACKER_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//============================================================================= + +#ifndef IMAGEPACKER_H +#define IMAGEPACKER_H + +#ifdef _WIN32 +#pragma once +#endif + +#define MAX_MAX_LIGHTMAP_WIDTH 2048 + + +//----------------------------------------------------------------------------- +// This packs a single lightmap +//----------------------------------------------------------------------------- +class CImagePacker +{ +public: + bool Reset( int maxLightmapWidth, int maxLightmapHeight ); + bool AddBlock( int width, int height, int *returnX, int *returnY ); + +protected: + int GetMaxYIndex( int firstX, int width ); + + int m_MaxLightmapWidth; + int m_MaxLightmapHeight; + int m_pLightmapWavefront[MAX_MAX_LIGHTMAP_WIDTH]; + int m_AreaUsed; + int m_MinimumHeight; + + // For optimization purposes: + // These store the width + height of the first image + // that was unable to be stored in this image + int m_MaxBlockWidth; + int m_MaxBlockHeight; +}; + + +#endif // IMAGEPACKER_H diff --git a/mp/src/utils/vrad/incremental.cpp b/mp/src/utils/vrad/incremental.cpp index 9dd877e0..ad46d04d 100644 --- a/mp/src/utils/vrad/incremental.cpp +++ b/mp/src/utils/vrad/incremental.cpp @@ -1,766 +1,766 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#include "incremental.h" -#include "lightmap.h" - - - -static bool g_bFileError = false; - - -// -------------------------------------------------------------------------------- // -// Static helpers. -// -------------------------------------------------------------------------------- // - -static bool CompareLights( dworldlight_t *a, dworldlight_t *b ) -{ - static float flEpsilon = 1e-7; - - bool a1 = VectorsAreEqual( a->origin, b->origin, flEpsilon ); - bool a2 = VectorsAreEqual( a->intensity, b->intensity, 1.1f ); // intensities are huge numbers - bool a3 = VectorsAreEqual( a->normal, b->normal, flEpsilon ); - bool a4 = fabs( a->constant_attn - b->constant_attn ) < flEpsilon; - bool a5 = fabs( a->linear_attn - b->linear_attn ) < flEpsilon; - bool a6 = fabs( a->quadratic_attn - b->quadratic_attn ) < flEpsilon; - bool a7 = fabs( float( a->flags - b->flags ) ) < flEpsilon; - bool a8 = fabs( a->stopdot - b->stopdot ) < flEpsilon; - bool a9 = fabs( a->stopdot2 - b->stopdot2 ) < flEpsilon; - bool a10 = fabs( a->exponent - b->exponent ) < flEpsilon; - bool a11 = fabs( a->radius - b->radius ) < flEpsilon; - - return a1 && a2 && a3 && a4 && a5 && a6 && a7 && a8 && a9 && a10 && a11; -} - - -long FileOpen( char const *pFilename, bool bRead ) -{ - g_bFileError = false; - return (long)g_pFileSystem->Open( pFilename, bRead ? "rb" : "wb" ); -} - - -void FileClose( long fp ) -{ - if( fp ) - g_pFileSystem->Close( (FILE*)fp ); -} - - -// Returns true if there was an error reading from the file. -bool FileError() -{ - return g_bFileError; -} - -static inline void FileRead( long fp, void *pOut, int size ) -{ - if( g_bFileError || g_pFileSystem->Read( pOut, size, (FileHandle_t)fp ) != size ) - { - g_bFileError = true; - memset( pOut, 0, size ); - } -} - - -template -static inline void FileRead( long fp, T &out ) -{ - FileRead( fp, &out, sizeof(out) ); -} - - -static inline void FileWrite( long fp, void const *pData, int size ) -{ - if( g_bFileError || g_pFileSystem->Write( pData, size, (FileHandle_t)fp ) != size ) - { - g_bFileError = true; - } -} - - -template -static inline void FileWrite( long fp, T out ) -{ - FileWrite( fp, &out, sizeof(out) ); -} - - -IIncremental* GetIncremental() -{ - static CIncremental inc; - return &inc; -} - - -// -------------------------------------------------------------------------------- // -// CIncremental. -// -------------------------------------------------------------------------------- // - -CIncremental::CIncremental() -{ - m_TotalMemory = 0; - m_pIncrementalFilename = NULL; - m_pBSPFilename = NULL; - m_bSuccessfulRun = false; -} - - -CIncremental::~CIncremental() -{ -} - - -bool CIncremental::Init( char const *pBSPFilename, char const *pIncrementalFilename ) -{ - m_pBSPFilename = pBSPFilename; - m_pIncrementalFilename = pIncrementalFilename; - return true; -} - - -bool CIncremental::PrepareForLighting() -{ - if( !m_pBSPFilename ) - return false; - - // Clear the touched faces list. - m_FacesTouched.SetSize( numfaces ); - memset( m_FacesTouched.Base(), 0, numfaces ); - - // If we haven't done a complete successful run yet, then we either haven't - // loaded the lights, or a run was aborted and our lights are half-done so we - // should reload them. - if( !m_bSuccessfulRun ) - LoadIncrementalFile(); - - // unmatched = a list of the lights we have - CUtlLinkedList unmatched; - for( int i=m_Lights.Head(); i != m_Lights.InvalidIndex(); i = m_Lights.Next(i) ) - unmatched.AddToTail( i ); - - // Match the light lists and get rid of lights that we already have all the data for. - directlight_t *pNext; - directlight_t **pPrev = &activelights; - for( directlight_t *dl=activelights; dl != NULL; dl = pNext ) - { - pNext = dl->next; - - //float flClosest = 3000000000; - //CIncLight *pClosest = 0; - - // Look for this light in our light list. - int iNextUnmatched, iUnmatched; - for( iUnmatched=unmatched.Head(); iUnmatched != unmatched.InvalidIndex(); iUnmatched = iNextUnmatched ) - { - iNextUnmatched = unmatched.Next( iUnmatched ); - - CIncLight *pLight = m_Lights[ unmatched[iUnmatched] ]; - - //float flTest = (pLight->m_Light.origin - dl->light.origin).Length(); - //if( flTest < flClosest ) - //{ - // flClosest = flTest; - // pClosest = pLight; - //} - - if( CompareLights( &dl->light, &pLight->m_Light ) ) - { - unmatched.Remove( iUnmatched ); - - // Ok, we have this light's data already, yay! - // Get rid of it from the active light list. - *pPrev = dl->next; - free( dl ); - dl = 0; - break; - } - } - - //bool bTest=false; - //if(bTest) - // CompareLights( &dl->light, &pClosest->m_Light ); - - if( iUnmatched == unmatched.InvalidIndex() ) - pPrev = &dl->next; - } - - // Remove any of our lights that were unmatched. - for( int iUnmatched=unmatched.Head(); iUnmatched != unmatched.InvalidIndex(); iUnmatched = unmatched.Next( iUnmatched ) ) - { - CIncLight *pLight = m_Lights[ unmatched[iUnmatched] ]; - - // First tag faces that it touched so they get recomposited. - for( unsigned short iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) ) - { - m_FacesTouched[ pLight->m_LightFaces[iFace]->m_FaceIndex ] = 1; - } - - delete pLight; - m_Lights.Remove( unmatched[iUnmatched] ); - } - - // Now add a light structure for each new light. - AddLightsForActiveLights(); - - return true; -} - - -bool CIncremental::ReadIncrementalHeader( long fp, CIncrementalHeader *pHeader ) -{ - int version; - FileRead( fp, version ); - if( version != INCREMENTALFILE_VERSION ) - return false; - - int nFaces; - FileRead( fp, nFaces ); - - pHeader->m_FaceLightmapSizes.SetSize( nFaces ); - FileRead( fp, pHeader->m_FaceLightmapSizes.Base(), sizeof(CIncrementalHeader::CLMSize) * nFaces ); - - return !FileError(); -} - - -bool CIncremental::WriteIncrementalHeader( long fp ) -{ - int version = INCREMENTALFILE_VERSION; - FileWrite( fp, version ); - - int nFaces = numfaces; - FileWrite( fp, nFaces ); - - CIncrementalHeader hdr; - hdr.m_FaceLightmapSizes.SetSize( nFaces ); - - for( int i=0; i < nFaces; i++ ) - { - hdr.m_FaceLightmapSizes[i].m_Width = g_pFaces[i].m_LightmapTextureSizeInLuxels[0]; - hdr.m_FaceLightmapSizes[i].m_Height = g_pFaces[i].m_LightmapTextureSizeInLuxels[1]; - } - - FileWrite( fp, hdr.m_FaceLightmapSizes.Base(), sizeof(CIncrementalHeader::CLMSize) * nFaces ); - - return !FileError(); -} - - -bool CIncremental::IsIncrementalFileValid() -{ - long fp = FileOpen( m_pIncrementalFilename, true ); - if( !fp ) - return false; - - bool bValid = false; - CIncrementalHeader hdr; - if( ReadIncrementalHeader( fp, &hdr ) ) - { - // If the number of faces is the same and their lightmap sizes are the same, - // then this file is considered a legitimate incremental file. - if( hdr.m_FaceLightmapSizes.Count() == numfaces ) - { - int i; - for( i=0; i < numfaces; i++ ) - { - if( hdr.m_FaceLightmapSizes[i].m_Width != g_pFaces[i].m_LightmapTextureSizeInLuxels[0] || - hdr.m_FaceLightmapSizes[i].m_Height != g_pFaces[i].m_LightmapTextureSizeInLuxels[1] ) - { - break; - } - } - - // Were all faces valid? - if( i == numfaces ) - bValid = true; - } - } - - FileClose( fp ); - return bValid && !FileError(); -} - - -void CIncremental::AddLightToFace( - IncrementalLightID lightID, - int iFace, - int iSample, - int lmSize, - float dot, - int iThread ) -{ - // If we're not being used, don't do anything. - if( !m_pIncrementalFilename ) - return; - - CIncLight *pLight = m_Lights[lightID]; - - // Check for the 99.99% case in which the face already exists. - CLightFace *pFace; - if( pLight->m_pCachedFaces[iThread] && - pLight->m_pCachedFaces[iThread]->m_FaceIndex == iFace ) - { - pFace = pLight->m_pCachedFaces[iThread]; - } - else - { - bool bNew; - - EnterCriticalSection( &pLight->m_CS ); - pFace = pLight->FindOrCreateLightFace( iFace, lmSize, &bNew ); - LeaveCriticalSection( &pLight->m_CS ); - - pLight->m_pCachedFaces[iThread] = pFace; - - if( bNew ) - m_TotalMemory += pFace->m_LightValues.Count() * sizeof( pFace->m_LightValues[0] ); - } - - // Add this into the light's data. - pFace->m_LightValues[iSample].m_Dot = dot; -} - - -unsigned short DecodeCharOrShort( CUtlBuffer *pIn ) -{ - unsigned short val = pIn->GetUnsignedChar(); - if( val & 0x80 ) - { - val = ((val & 0x7F) << 8) | pIn->GetUnsignedChar(); - } - - return val; -} - - -void EncodeCharOrShort( CUtlBuffer *pBuf, unsigned short val ) -{ - if( (val & 0xFF80) == 0 ) - { - pBuf->PutUnsignedChar( (unsigned char)val ); - } - else - { - if( val > 32767 ) - val = 32767; - - pBuf->PutUnsignedChar( (val >> 8) | 0x80 ); - pBuf->PutUnsignedChar( val & 0xFF ); - } -} - - -void DecompressLightData( CUtlBuffer *pIn, CUtlVector *pOut ) -{ - int iOut = 0; - while( pIn->TellGet() < pIn->TellPut() ) - { - unsigned char runLength = pIn->GetUnsignedChar(); - unsigned short usVal = DecodeCharOrShort( pIn ); - - while( runLength > 0 ) - { - --runLength; - - pOut->Element(iOut).m_Dot = usVal; - ++iOut; - } - } -} - -#ifdef _WIN32 -#pragma warning (disable:4701) -#endif - -void CompressLightData( - CLightValue const *pValues, - int nValues, - CUtlBuffer *pBuf ) -{ - unsigned char runLength=0; - unsigned short flLastValue; - - for( int i=0; i < nValues; i++ ) - { - unsigned short flCurValue = (unsigned short)pValues[i].m_Dot; - - if( i == 0 ) - { - flLastValue = flCurValue; - runLength = 1; - } - else if( flCurValue == flLastValue && runLength < 255 ) - { - ++runLength; - } - else - { - pBuf->PutUnsignedChar( runLength ); - EncodeCharOrShort( pBuf, flLastValue ); - - flLastValue = flCurValue; - runLength = 1; - } - } - - // Write the end.. - if( runLength ) - { - pBuf->PutUnsignedChar( runLength ); - EncodeCharOrShort( pBuf, flLastValue ); - } -} - -#ifdef _WIN32 -#pragma warning (default:4701) -#endif - -void MultiplyValues( CUtlVector &values, float scale ) -{ - for( int i=0; i < values.Count(); i++ ) - values[i].m_Dot *= scale; -} - - -void CIncremental::FinishFace( - IncrementalLightID lightID, - int iFace, - int iThread ) -{ - CIncLight *pLight = m_Lights[lightID]; - - // Check for the 99.99% case in which the face already exists. - CLightFace *pFace; - if( pLight->m_pCachedFaces[iThread] && pLight->m_pCachedFaces[iThread]->m_FaceIndex == iFace ) - { - pFace = pLight->m_pCachedFaces[iThread]; - - // Compress the data. - MultiplyValues( pFace->m_LightValues, pLight->m_flMaxIntensity ); - - pFace->m_CompressedData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); - CompressLightData( - pFace->m_LightValues.Base(), - pFace->m_LightValues.Count(), - &pFace->m_CompressedData ); - -#if 0 - // test decompression - CUtlVector test; - test.SetSize( 2048 ); - pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); - DecompressLightData( &pFace->m_CompressedData, &test ); -#endif - - if( pFace->m_CompressedData.TellPut() == 0 ) - { - // No contribution.. delete this face from the light. - EnterCriticalSection( &pLight->m_CS ); - pLight->m_LightFaces.Remove( pFace->m_LightFacesIndex ); - delete pFace; - LeaveCriticalSection( &pLight->m_CS ); - } - else - { - // Discard the uncompressed data. - pFace->m_LightValues.Purge(); - m_FacesTouched[ pFace->m_FaceIndex ] = 1; - } - } -} - - -bool CIncremental::Finalize() -{ - // If we're not being used, don't do anything. - if( !m_pIncrementalFilename || !m_pBSPFilename ) - return false; - - CUtlVector faceLights; - LinkLightsToFaces( faceLights ); - - Vector faceLight[(MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) * (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2)]; - CUtlVector faceLightValues; - faceLightValues.SetSize( (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) * (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) ); - - // Only update the faces we've touched. - for( int facenum = 0; facenum < numfaces; facenum++ ) - { - if( !m_FacesTouched[facenum] || !faceLights[facenum].Count() ) - continue; - - int w = g_pFaces[facenum].m_LightmapTextureSizeInLuxels[0]+1; - int h = g_pFaces[facenum].m_LightmapTextureSizeInLuxels[1]+1; - int nLuxels = w * h; - assert( nLuxels <= sizeof(faceLight) / sizeof(faceLight[0]) ); - - // Clear the lighting for this face. - memset( faceLight, 0, nLuxels * sizeof(Vector) ); - - // Composite all the light contributions. - for( int iFace=0; iFace < faceLights[facenum].Count(); iFace++ ) - { - CLightFace *pFace = faceLights[facenum][iFace]; - - pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); - DecompressLightData( &pFace->m_CompressedData, &faceLightValues ); - - for( int iSample=0; iSample < nLuxels; iSample++ ) - { - float flDot = faceLightValues[iSample].m_Dot; - if( flDot ) - { - VectorMA( - faceLight[iSample], - flDot / pFace->m_pLight->m_flMaxIntensity, - pFace->m_pLight->m_Light.intensity, - faceLight[iSample] ); - } - } - } - - // Convert to the floating-point representation in the BSP file. - Vector *pSrc = faceLight; - unsigned char *pDest = &(*pdlightdata)[ g_pFaces[facenum].lightofs ]; - - for( int iSample=0; iSample < nLuxels; iSample++ ) - { - VectorToColorRGBExp32( *pSrc, *( ColorRGBExp32 *)pDest ); - pDest += 4; - pSrc++; - } - } - - m_bSuccessfulRun = true; - return true; -} - - -void CIncremental::GetFacesTouched( CUtlVector &touched ) -{ - touched.CopyArray( m_FacesTouched.Base(), m_FacesTouched.Count() ); -} - - -bool CIncremental::Serialize() -{ - if( !SaveIncrementalFile() ) - return false; - - WriteBSPFile( (char*)m_pBSPFilename ); - return true; -} - - -void CIncremental::Term() -{ - m_Lights.PurgeAndDeleteElements(); - m_TotalMemory = 0; -} - - -void CIncremental::AddLightsForActiveLights() -{ - // Create our lights. - for( directlight_t *dl=activelights; dl != NULL; dl = dl->next ) - { - CIncLight *pLight = new CIncLight; - dl->m_IncrementalID = m_Lights.AddToTail( pLight ); - - // Copy the light information. - pLight->m_Light = dl->light; - pLight->m_flMaxIntensity = max( dl->light.intensity[0], max( dl->light.intensity[1], dl->light.intensity[2] ) ); - } -} - - -bool CIncremental::LoadIncrementalFile() -{ - Term(); - - if( !IsIncrementalFileValid() ) - return false; - - long fp = FileOpen( m_pIncrementalFilename, true ); - if( !fp ) - return false; - - // Read the header. - CIncrementalHeader hdr; - if( !ReadIncrementalHeader( fp, &hdr ) ) - { - FileClose( fp ); - return false; - } - - - // Read the lights. - int nLights; - FileRead( fp, nLights ); - for( int iLight=0; iLight < nLights; iLight++ ) - { - CIncLight *pLight = new CIncLight; - m_Lights.AddToTail( pLight ); - - FileRead( fp, pLight->m_Light ); - pLight->m_flMaxIntensity = - max( pLight->m_Light.intensity.x, - max( pLight->m_Light.intensity.y, pLight->m_Light.intensity.z ) ); - - int nFaces; - FileRead( fp, nFaces ); - assert( nFaces < 70000 ); - - for( int iFace=0; iFace < nFaces; iFace++ ) - { - CLightFace *pFace = new CLightFace; - pLight->m_LightFaces.AddToTail( pFace ); - - pFace->m_pLight = pLight; - FileRead( fp, pFace->m_FaceIndex ); - - int dataSize; - FileRead( fp, dataSize ); - - pFace->m_CompressedData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); - while( dataSize ) - { - --dataSize; - - unsigned char ucData; - FileRead( fp, ucData ); - - pFace->m_CompressedData.PutUnsignedChar( ucData ); - } - } - } - - - FileClose( fp ); - return !FileError(); -} - - -bool CIncremental::SaveIncrementalFile() -{ - long fp = FileOpen( m_pIncrementalFilename, false ); - if( !fp ) - return false; - - if( !WriteIncrementalHeader( fp ) ) - { - FileClose( fp ); - return false; - } - - // Write the lights. - int nLights = m_Lights.Count(); - FileWrite( fp, nLights ); - for( int iLight=m_Lights.Head(); iLight != m_Lights.InvalidIndex(); iLight = m_Lights.Next( iLight ) ) - { - CIncLight *pLight = m_Lights[iLight]; - - FileWrite( fp, pLight->m_Light ); - - int nFaces = pLight->m_LightFaces.Count(); - FileWrite( fp, nFaces ); - for( int iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) ) - { - CLightFace *pFace = pLight->m_LightFaces[iFace]; - - FileWrite( fp, pFace->m_FaceIndex ); - - int dataSize = pFace->m_CompressedData.TellPut(); - FileWrite( fp, dataSize ); - - pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); - while( dataSize ) - { - --dataSize; - FileWrite( fp, pFace->m_CompressedData.GetUnsignedChar() ); - } - } - } - - - FileClose( fp ); - return !FileError(); -} - - -void CIncremental::LinkLightsToFaces( CUtlVector &faceLights ) -{ - faceLights.SetSize( numfaces ); - - for( int iLight=m_Lights.Head(); iLight != m_Lights.InvalidIndex(); iLight = m_Lights.Next( iLight ) ) - { - CIncLight *pLight = m_Lights[iLight]; - - for( int iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) ) - { - CLightFace *pFace = pLight->m_LightFaces[iFace]; - - if( m_FacesTouched[pFace->m_FaceIndex] ) - faceLights[ pFace->m_FaceIndex ].AddToTail( pFace ); - } - } -} - - -// ------------------------------------------------------------------ // -// CIncLight -// ------------------------------------------------------------------ // - -CIncLight::CIncLight() -{ - memset( m_pCachedFaces, 0, sizeof(m_pCachedFaces) ); - InitializeCriticalSection( &m_CS ); -} - - -CIncLight::~CIncLight() -{ - m_LightFaces.PurgeAndDeleteElements(); - DeleteCriticalSection( &m_CS ); -} - - -CLightFace* CIncLight::FindOrCreateLightFace( int iFace, int lmSize, bool *bNew ) -{ - if( bNew ) - *bNew = false; - - - // Look for it. - for( int i=m_LightFaces.Head(); i != m_LightFaces.InvalidIndex(); i=m_LightFaces.Next(i) ) - { - CLightFace *pFace = m_LightFaces[i]; - - if( pFace->m_FaceIndex == iFace ) - { - assert( pFace->m_LightValues.Count() == lmSize ); - return pFace; - } - } - - // Ok, create one. - CLightFace *pFace = new CLightFace; - pFace->m_LightFacesIndex = m_LightFaces.AddToTail( pFace ); - pFace->m_pLight = this; - - pFace->m_FaceIndex = iFace; - pFace->m_LightValues.SetSize( lmSize ); - memset( pFace->m_LightValues.Base(), 0, sizeof( CLightValue ) * lmSize ); - - if( bNew ) - *bNew = true; - - return pFace; -} - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "incremental.h" +#include "lightmap.h" + + + +static bool g_bFileError = false; + + +// -------------------------------------------------------------------------------- // +// Static helpers. +// -------------------------------------------------------------------------------- // + +static bool CompareLights( dworldlight_t *a, dworldlight_t *b ) +{ + static float flEpsilon = 1e-7; + + bool a1 = VectorsAreEqual( a->origin, b->origin, flEpsilon ); + bool a2 = VectorsAreEqual( a->intensity, b->intensity, 1.1f ); // intensities are huge numbers + bool a3 = VectorsAreEqual( a->normal, b->normal, flEpsilon ); + bool a4 = fabs( a->constant_attn - b->constant_attn ) < flEpsilon; + bool a5 = fabs( a->linear_attn - b->linear_attn ) < flEpsilon; + bool a6 = fabs( a->quadratic_attn - b->quadratic_attn ) < flEpsilon; + bool a7 = fabs( float( a->flags - b->flags ) ) < flEpsilon; + bool a8 = fabs( a->stopdot - b->stopdot ) < flEpsilon; + bool a9 = fabs( a->stopdot2 - b->stopdot2 ) < flEpsilon; + bool a10 = fabs( a->exponent - b->exponent ) < flEpsilon; + bool a11 = fabs( a->radius - b->radius ) < flEpsilon; + + return a1 && a2 && a3 && a4 && a5 && a6 && a7 && a8 && a9 && a10 && a11; +} + + +long FileOpen( char const *pFilename, bool bRead ) +{ + g_bFileError = false; + return (long)g_pFileSystem->Open( pFilename, bRead ? "rb" : "wb" ); +} + + +void FileClose( long fp ) +{ + if( fp ) + g_pFileSystem->Close( (FILE*)fp ); +} + + +// Returns true if there was an error reading from the file. +bool FileError() +{ + return g_bFileError; +} + +static inline void FileRead( long fp, void *pOut, int size ) +{ + if( g_bFileError || g_pFileSystem->Read( pOut, size, (FileHandle_t)fp ) != size ) + { + g_bFileError = true; + memset( pOut, 0, size ); + } +} + + +template +static inline void FileRead( long fp, T &out ) +{ + FileRead( fp, &out, sizeof(out) ); +} + + +static inline void FileWrite( long fp, void const *pData, int size ) +{ + if( g_bFileError || g_pFileSystem->Write( pData, size, (FileHandle_t)fp ) != size ) + { + g_bFileError = true; + } +} + + +template +static inline void FileWrite( long fp, T out ) +{ + FileWrite( fp, &out, sizeof(out) ); +} + + +IIncremental* GetIncremental() +{ + static CIncremental inc; + return &inc; +} + + +// -------------------------------------------------------------------------------- // +// CIncremental. +// -------------------------------------------------------------------------------- // + +CIncremental::CIncremental() +{ + m_TotalMemory = 0; + m_pIncrementalFilename = NULL; + m_pBSPFilename = NULL; + m_bSuccessfulRun = false; +} + + +CIncremental::~CIncremental() +{ +} + + +bool CIncremental::Init( char const *pBSPFilename, char const *pIncrementalFilename ) +{ + m_pBSPFilename = pBSPFilename; + m_pIncrementalFilename = pIncrementalFilename; + return true; +} + + +bool CIncremental::PrepareForLighting() +{ + if( !m_pBSPFilename ) + return false; + + // Clear the touched faces list. + m_FacesTouched.SetSize( numfaces ); + memset( m_FacesTouched.Base(), 0, numfaces ); + + // If we haven't done a complete successful run yet, then we either haven't + // loaded the lights, or a run was aborted and our lights are half-done so we + // should reload them. + if( !m_bSuccessfulRun ) + LoadIncrementalFile(); + + // unmatched = a list of the lights we have + CUtlLinkedList unmatched; + for( int i=m_Lights.Head(); i != m_Lights.InvalidIndex(); i = m_Lights.Next(i) ) + unmatched.AddToTail( i ); + + // Match the light lists and get rid of lights that we already have all the data for. + directlight_t *pNext; + directlight_t **pPrev = &activelights; + for( directlight_t *dl=activelights; dl != NULL; dl = pNext ) + { + pNext = dl->next; + + //float flClosest = 3000000000; + //CIncLight *pClosest = 0; + + // Look for this light in our light list. + int iNextUnmatched, iUnmatched; + for( iUnmatched=unmatched.Head(); iUnmatched != unmatched.InvalidIndex(); iUnmatched = iNextUnmatched ) + { + iNextUnmatched = unmatched.Next( iUnmatched ); + + CIncLight *pLight = m_Lights[ unmatched[iUnmatched] ]; + + //float flTest = (pLight->m_Light.origin - dl->light.origin).Length(); + //if( flTest < flClosest ) + //{ + // flClosest = flTest; + // pClosest = pLight; + //} + + if( CompareLights( &dl->light, &pLight->m_Light ) ) + { + unmatched.Remove( iUnmatched ); + + // Ok, we have this light's data already, yay! + // Get rid of it from the active light list. + *pPrev = dl->next; + free( dl ); + dl = 0; + break; + } + } + + //bool bTest=false; + //if(bTest) + // CompareLights( &dl->light, &pClosest->m_Light ); + + if( iUnmatched == unmatched.InvalidIndex() ) + pPrev = &dl->next; + } + + // Remove any of our lights that were unmatched. + for( int iUnmatched=unmatched.Head(); iUnmatched != unmatched.InvalidIndex(); iUnmatched = unmatched.Next( iUnmatched ) ) + { + CIncLight *pLight = m_Lights[ unmatched[iUnmatched] ]; + + // First tag faces that it touched so they get recomposited. + for( unsigned short iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) ) + { + m_FacesTouched[ pLight->m_LightFaces[iFace]->m_FaceIndex ] = 1; + } + + delete pLight; + m_Lights.Remove( unmatched[iUnmatched] ); + } + + // Now add a light structure for each new light. + AddLightsForActiveLights(); + + return true; +} + + +bool CIncremental::ReadIncrementalHeader( long fp, CIncrementalHeader *pHeader ) +{ + int version; + FileRead( fp, version ); + if( version != INCREMENTALFILE_VERSION ) + return false; + + int nFaces; + FileRead( fp, nFaces ); + + pHeader->m_FaceLightmapSizes.SetSize( nFaces ); + FileRead( fp, pHeader->m_FaceLightmapSizes.Base(), sizeof(CIncrementalHeader::CLMSize) * nFaces ); + + return !FileError(); +} + + +bool CIncremental::WriteIncrementalHeader( long fp ) +{ + int version = INCREMENTALFILE_VERSION; + FileWrite( fp, version ); + + int nFaces = numfaces; + FileWrite( fp, nFaces ); + + CIncrementalHeader hdr; + hdr.m_FaceLightmapSizes.SetSize( nFaces ); + + for( int i=0; i < nFaces; i++ ) + { + hdr.m_FaceLightmapSizes[i].m_Width = g_pFaces[i].m_LightmapTextureSizeInLuxels[0]; + hdr.m_FaceLightmapSizes[i].m_Height = g_pFaces[i].m_LightmapTextureSizeInLuxels[1]; + } + + FileWrite( fp, hdr.m_FaceLightmapSizes.Base(), sizeof(CIncrementalHeader::CLMSize) * nFaces ); + + return !FileError(); +} + + +bool CIncremental::IsIncrementalFileValid() +{ + long fp = FileOpen( m_pIncrementalFilename, true ); + if( !fp ) + return false; + + bool bValid = false; + CIncrementalHeader hdr; + if( ReadIncrementalHeader( fp, &hdr ) ) + { + // If the number of faces is the same and their lightmap sizes are the same, + // then this file is considered a legitimate incremental file. + if( hdr.m_FaceLightmapSizes.Count() == numfaces ) + { + int i; + for( i=0; i < numfaces; i++ ) + { + if( hdr.m_FaceLightmapSizes[i].m_Width != g_pFaces[i].m_LightmapTextureSizeInLuxels[0] || + hdr.m_FaceLightmapSizes[i].m_Height != g_pFaces[i].m_LightmapTextureSizeInLuxels[1] ) + { + break; + } + } + + // Were all faces valid? + if( i == numfaces ) + bValid = true; + } + } + + FileClose( fp ); + return bValid && !FileError(); +} + + +void CIncremental::AddLightToFace( + IncrementalLightID lightID, + int iFace, + int iSample, + int lmSize, + float dot, + int iThread ) +{ + // If we're not being used, don't do anything. + if( !m_pIncrementalFilename ) + return; + + CIncLight *pLight = m_Lights[lightID]; + + // Check for the 99.99% case in which the face already exists. + CLightFace *pFace; + if( pLight->m_pCachedFaces[iThread] && + pLight->m_pCachedFaces[iThread]->m_FaceIndex == iFace ) + { + pFace = pLight->m_pCachedFaces[iThread]; + } + else + { + bool bNew; + + EnterCriticalSection( &pLight->m_CS ); + pFace = pLight->FindOrCreateLightFace( iFace, lmSize, &bNew ); + LeaveCriticalSection( &pLight->m_CS ); + + pLight->m_pCachedFaces[iThread] = pFace; + + if( bNew ) + m_TotalMemory += pFace->m_LightValues.Count() * sizeof( pFace->m_LightValues[0] ); + } + + // Add this into the light's data. + pFace->m_LightValues[iSample].m_Dot = dot; +} + + +unsigned short DecodeCharOrShort( CUtlBuffer *pIn ) +{ + unsigned short val = pIn->GetUnsignedChar(); + if( val & 0x80 ) + { + val = ((val & 0x7F) << 8) | pIn->GetUnsignedChar(); + } + + return val; +} + + +void EncodeCharOrShort( CUtlBuffer *pBuf, unsigned short val ) +{ + if( (val & 0xFF80) == 0 ) + { + pBuf->PutUnsignedChar( (unsigned char)val ); + } + else + { + if( val > 32767 ) + val = 32767; + + pBuf->PutUnsignedChar( (val >> 8) | 0x80 ); + pBuf->PutUnsignedChar( val & 0xFF ); + } +} + + +void DecompressLightData( CUtlBuffer *pIn, CUtlVector *pOut ) +{ + int iOut = 0; + while( pIn->TellGet() < pIn->TellPut() ) + { + unsigned char runLength = pIn->GetUnsignedChar(); + unsigned short usVal = DecodeCharOrShort( pIn ); + + while( runLength > 0 ) + { + --runLength; + + pOut->Element(iOut).m_Dot = usVal; + ++iOut; + } + } +} + +#ifdef _WIN32 +#pragma warning (disable:4701) +#endif + +void CompressLightData( + CLightValue const *pValues, + int nValues, + CUtlBuffer *pBuf ) +{ + unsigned char runLength=0; + unsigned short flLastValue; + + for( int i=0; i < nValues; i++ ) + { + unsigned short flCurValue = (unsigned short)pValues[i].m_Dot; + + if( i == 0 ) + { + flLastValue = flCurValue; + runLength = 1; + } + else if( flCurValue == flLastValue && runLength < 255 ) + { + ++runLength; + } + else + { + pBuf->PutUnsignedChar( runLength ); + EncodeCharOrShort( pBuf, flLastValue ); + + flLastValue = flCurValue; + runLength = 1; + } + } + + // Write the end.. + if( runLength ) + { + pBuf->PutUnsignedChar( runLength ); + EncodeCharOrShort( pBuf, flLastValue ); + } +} + +#ifdef _WIN32 +#pragma warning (default:4701) +#endif + +void MultiplyValues( CUtlVector &values, float scale ) +{ + for( int i=0; i < values.Count(); i++ ) + values[i].m_Dot *= scale; +} + + +void CIncremental::FinishFace( + IncrementalLightID lightID, + int iFace, + int iThread ) +{ + CIncLight *pLight = m_Lights[lightID]; + + // Check for the 99.99% case in which the face already exists. + CLightFace *pFace; + if( pLight->m_pCachedFaces[iThread] && pLight->m_pCachedFaces[iThread]->m_FaceIndex == iFace ) + { + pFace = pLight->m_pCachedFaces[iThread]; + + // Compress the data. + MultiplyValues( pFace->m_LightValues, pLight->m_flMaxIntensity ); + + pFace->m_CompressedData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); + CompressLightData( + pFace->m_LightValues.Base(), + pFace->m_LightValues.Count(), + &pFace->m_CompressedData ); + +#if 0 + // test decompression + CUtlVector test; + test.SetSize( 2048 ); + pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + DecompressLightData( &pFace->m_CompressedData, &test ); +#endif + + if( pFace->m_CompressedData.TellPut() == 0 ) + { + // No contribution.. delete this face from the light. + EnterCriticalSection( &pLight->m_CS ); + pLight->m_LightFaces.Remove( pFace->m_LightFacesIndex ); + delete pFace; + LeaveCriticalSection( &pLight->m_CS ); + } + else + { + // Discard the uncompressed data. + pFace->m_LightValues.Purge(); + m_FacesTouched[ pFace->m_FaceIndex ] = 1; + } + } +} + + +bool CIncremental::Finalize() +{ + // If we're not being used, don't do anything. + if( !m_pIncrementalFilename || !m_pBSPFilename ) + return false; + + CUtlVector faceLights; + LinkLightsToFaces( faceLights ); + + Vector faceLight[(MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) * (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2)]; + CUtlVector faceLightValues; + faceLightValues.SetSize( (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) * (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) ); + + // Only update the faces we've touched. + for( int facenum = 0; facenum < numfaces; facenum++ ) + { + if( !m_FacesTouched[facenum] || !faceLights[facenum].Count() ) + continue; + + int w = g_pFaces[facenum].m_LightmapTextureSizeInLuxels[0]+1; + int h = g_pFaces[facenum].m_LightmapTextureSizeInLuxels[1]+1; + int nLuxels = w * h; + assert( nLuxels <= sizeof(faceLight) / sizeof(faceLight[0]) ); + + // Clear the lighting for this face. + memset( faceLight, 0, nLuxels * sizeof(Vector) ); + + // Composite all the light contributions. + for( int iFace=0; iFace < faceLights[facenum].Count(); iFace++ ) + { + CLightFace *pFace = faceLights[facenum][iFace]; + + pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + DecompressLightData( &pFace->m_CompressedData, &faceLightValues ); + + for( int iSample=0; iSample < nLuxels; iSample++ ) + { + float flDot = faceLightValues[iSample].m_Dot; + if( flDot ) + { + VectorMA( + faceLight[iSample], + flDot / pFace->m_pLight->m_flMaxIntensity, + pFace->m_pLight->m_Light.intensity, + faceLight[iSample] ); + } + } + } + + // Convert to the floating-point representation in the BSP file. + Vector *pSrc = faceLight; + unsigned char *pDest = &(*pdlightdata)[ g_pFaces[facenum].lightofs ]; + + for( int iSample=0; iSample < nLuxels; iSample++ ) + { + VectorToColorRGBExp32( *pSrc, *( ColorRGBExp32 *)pDest ); + pDest += 4; + pSrc++; + } + } + + m_bSuccessfulRun = true; + return true; +} + + +void CIncremental::GetFacesTouched( CUtlVector &touched ) +{ + touched.CopyArray( m_FacesTouched.Base(), m_FacesTouched.Count() ); +} + + +bool CIncremental::Serialize() +{ + if( !SaveIncrementalFile() ) + return false; + + WriteBSPFile( (char*)m_pBSPFilename ); + return true; +} + + +void CIncremental::Term() +{ + m_Lights.PurgeAndDeleteElements(); + m_TotalMemory = 0; +} + + +void CIncremental::AddLightsForActiveLights() +{ + // Create our lights. + for( directlight_t *dl=activelights; dl != NULL; dl = dl->next ) + { + CIncLight *pLight = new CIncLight; + dl->m_IncrementalID = m_Lights.AddToTail( pLight ); + + // Copy the light information. + pLight->m_Light = dl->light; + pLight->m_flMaxIntensity = max( dl->light.intensity[0], max( dl->light.intensity[1], dl->light.intensity[2] ) ); + } +} + + +bool CIncremental::LoadIncrementalFile() +{ + Term(); + + if( !IsIncrementalFileValid() ) + return false; + + long fp = FileOpen( m_pIncrementalFilename, true ); + if( !fp ) + return false; + + // Read the header. + CIncrementalHeader hdr; + if( !ReadIncrementalHeader( fp, &hdr ) ) + { + FileClose( fp ); + return false; + } + + + // Read the lights. + int nLights; + FileRead( fp, nLights ); + for( int iLight=0; iLight < nLights; iLight++ ) + { + CIncLight *pLight = new CIncLight; + m_Lights.AddToTail( pLight ); + + FileRead( fp, pLight->m_Light ); + pLight->m_flMaxIntensity = + max( pLight->m_Light.intensity.x, + max( pLight->m_Light.intensity.y, pLight->m_Light.intensity.z ) ); + + int nFaces; + FileRead( fp, nFaces ); + assert( nFaces < 70000 ); + + for( int iFace=0; iFace < nFaces; iFace++ ) + { + CLightFace *pFace = new CLightFace; + pLight->m_LightFaces.AddToTail( pFace ); + + pFace->m_pLight = pLight; + FileRead( fp, pFace->m_FaceIndex ); + + int dataSize; + FileRead( fp, dataSize ); + + pFace->m_CompressedData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); + while( dataSize ) + { + --dataSize; + + unsigned char ucData; + FileRead( fp, ucData ); + + pFace->m_CompressedData.PutUnsignedChar( ucData ); + } + } + } + + + FileClose( fp ); + return !FileError(); +} + + +bool CIncremental::SaveIncrementalFile() +{ + long fp = FileOpen( m_pIncrementalFilename, false ); + if( !fp ) + return false; + + if( !WriteIncrementalHeader( fp ) ) + { + FileClose( fp ); + return false; + } + + // Write the lights. + int nLights = m_Lights.Count(); + FileWrite( fp, nLights ); + for( int iLight=m_Lights.Head(); iLight != m_Lights.InvalidIndex(); iLight = m_Lights.Next( iLight ) ) + { + CIncLight *pLight = m_Lights[iLight]; + + FileWrite( fp, pLight->m_Light ); + + int nFaces = pLight->m_LightFaces.Count(); + FileWrite( fp, nFaces ); + for( int iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) ) + { + CLightFace *pFace = pLight->m_LightFaces[iFace]; + + FileWrite( fp, pFace->m_FaceIndex ); + + int dataSize = pFace->m_CompressedData.TellPut(); + FileWrite( fp, dataSize ); + + pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + while( dataSize ) + { + --dataSize; + FileWrite( fp, pFace->m_CompressedData.GetUnsignedChar() ); + } + } + } + + + FileClose( fp ); + return !FileError(); +} + + +void CIncremental::LinkLightsToFaces( CUtlVector &faceLights ) +{ + faceLights.SetSize( numfaces ); + + for( int iLight=m_Lights.Head(); iLight != m_Lights.InvalidIndex(); iLight = m_Lights.Next( iLight ) ) + { + CIncLight *pLight = m_Lights[iLight]; + + for( int iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) ) + { + CLightFace *pFace = pLight->m_LightFaces[iFace]; + + if( m_FacesTouched[pFace->m_FaceIndex] ) + faceLights[ pFace->m_FaceIndex ].AddToTail( pFace ); + } + } +} + + +// ------------------------------------------------------------------ // +// CIncLight +// ------------------------------------------------------------------ // + +CIncLight::CIncLight() +{ + memset( m_pCachedFaces, 0, sizeof(m_pCachedFaces) ); + InitializeCriticalSection( &m_CS ); +} + + +CIncLight::~CIncLight() +{ + m_LightFaces.PurgeAndDeleteElements(); + DeleteCriticalSection( &m_CS ); +} + + +CLightFace* CIncLight::FindOrCreateLightFace( int iFace, int lmSize, bool *bNew ) +{ + if( bNew ) + *bNew = false; + + + // Look for it. + for( int i=m_LightFaces.Head(); i != m_LightFaces.InvalidIndex(); i=m_LightFaces.Next(i) ) + { + CLightFace *pFace = m_LightFaces[i]; + + if( pFace->m_FaceIndex == iFace ) + { + assert( pFace->m_LightValues.Count() == lmSize ); + return pFace; + } + } + + // Ok, create one. + CLightFace *pFace = new CLightFace; + pFace->m_LightFacesIndex = m_LightFaces.AddToTail( pFace ); + pFace->m_pLight = this; + + pFace->m_FaceIndex = iFace; + pFace->m_LightValues.SetSize( lmSize ); + memset( pFace->m_LightValues.Base(), 0, sizeof( CLightValue ) * lmSize ); + + if( bNew ) + *bNew = true; + + return pFace; +} + + diff --git a/mp/src/utils/vrad/incremental.h b/mp/src/utils/vrad/incremental.h index fdac144e..9e98054b 100644 --- a/mp/src/utils/vrad/incremental.h +++ b/mp/src/utils/vrad/incremental.h @@ -1,175 +1,175 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef INCREMENTAL_H -#define INCREMENTAL_H -#ifdef _WIN32 -#pragma once -#endif - - - -#include "iincremental.h" -#include "utllinkedlist.h" -#include "utlvector.h" -#include "utlbuffer.h" -#include "vrad.h" - - -#define INCREMENTALFILE_VERSION 31241 - - -class CIncLight; - - -class CLightValue -{ -public: - float m_Dot; -}; - - -class CLightFace -{ -public: - unsigned short m_FaceIndex; // global face index - unsigned short m_LightFacesIndex; // index into CIncLight::m_LightFaces. - - // The lightmap grid for this face. Only used while building lighting data for a face. - // Compressed into m_CompressedData immediately afterwards. - CUtlVector m_LightValues; - - CUtlBuffer m_CompressedData; - CIncLight *m_pLight; -}; - - -class CIncLight -{ -public: - CIncLight(); - ~CIncLight(); - - CLightFace* FindOrCreateLightFace( int iFace, int lmSize, bool *bNew=NULL ); - - -public: - - CRITICAL_SECTION m_CS; - - // This is the light for which m_LightFaces was built. - dworldlight_t m_Light; - - CLightFace *m_pCachedFaces[MAX_TOOL_THREADS+1]; - - // The list of faces that this light contributes to. - CUtlLinkedList m_LightFaces; - - // Largest value in intensity of light. Used to scale dot products up into a - // range where their values make sense. - float m_flMaxIntensity; -}; - - -class CIncrementalHeader -{ -public: - class CLMSize - { - public: - unsigned char m_Width; - unsigned char m_Height; - }; - - CUtlVector m_FaceLightmapSizes; -}; - - -class CIncremental : public IIncremental -{ -public: - - CIncremental(); - ~CIncremental(); - - - -// IIncremental overrides. -public: - - virtual bool Init( char const *pBSPFilename, char const *pIncrementalFilename ); - - // Load the light definitions out of the incremental file. - // Figure out which lights have changed. - // Change 'activelights' to only consist of new or changed lights. - virtual bool PrepareForLighting(); - - virtual void AddLightToFace( - IncrementalLightID lightID, - int iFace, - int iSample, - int lmSize, - float dot, - int iThread ); - - virtual void FinishFace( - IncrementalLightID lightID, - int iFace, - int iThread ); - - // For each face that was changed during the lighting process, save out - // new data for it in the incremental file. - virtual bool Finalize(); - - virtual void GetFacesTouched( CUtlVector &touched ); - - virtual bool Serialize(); - - -private: - - // Read/write the header from the file. - bool ReadIncrementalHeader( long fp, CIncrementalHeader *pHeader ); - bool WriteIncrementalHeader( long fp ); - - // Returns true if the incremental file is valid and we can use InitUpdate. - bool IsIncrementalFileValid(); - - void Term(); - - // For each light in 'activelights', add a light to m_Lights and link them together. - void AddLightsForActiveLights(); - - // Load and save the state. - bool LoadIncrementalFile(); - bool SaveIncrementalFile(); - - typedef CUtlVector CFaceLightList; - void LinkLightsToFaces( CUtlVector &faceLights ); - - -private: - - char const *m_pIncrementalFilename; - char const *m_pBSPFilename; - - CUtlLinkedList - m_Lights; - - // The face index is set to 1 if a face has new lighting data applied to it. - // This is used to optimize the set of lightmaps we recomposite. - CUtlVector m_FacesTouched; - - int m_TotalMemory; - - // Set to true when one or more runs were completed successfully. - bool m_bSuccessfulRun; -}; - - - -#endif // INCREMENTAL_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef INCREMENTAL_H +#define INCREMENTAL_H +#ifdef _WIN32 +#pragma once +#endif + + + +#include "iincremental.h" +#include "utllinkedlist.h" +#include "utlvector.h" +#include "utlbuffer.h" +#include "vrad.h" + + +#define INCREMENTALFILE_VERSION 31241 + + +class CIncLight; + + +class CLightValue +{ +public: + float m_Dot; +}; + + +class CLightFace +{ +public: + unsigned short m_FaceIndex; // global face index + unsigned short m_LightFacesIndex; // index into CIncLight::m_LightFaces. + + // The lightmap grid for this face. Only used while building lighting data for a face. + // Compressed into m_CompressedData immediately afterwards. + CUtlVector m_LightValues; + + CUtlBuffer m_CompressedData; + CIncLight *m_pLight; +}; + + +class CIncLight +{ +public: + CIncLight(); + ~CIncLight(); + + CLightFace* FindOrCreateLightFace( int iFace, int lmSize, bool *bNew=NULL ); + + +public: + + CRITICAL_SECTION m_CS; + + // This is the light for which m_LightFaces was built. + dworldlight_t m_Light; + + CLightFace *m_pCachedFaces[MAX_TOOL_THREADS+1]; + + // The list of faces that this light contributes to. + CUtlLinkedList m_LightFaces; + + // Largest value in intensity of light. Used to scale dot products up into a + // range where their values make sense. + float m_flMaxIntensity; +}; + + +class CIncrementalHeader +{ +public: + class CLMSize + { + public: + unsigned char m_Width; + unsigned char m_Height; + }; + + CUtlVector m_FaceLightmapSizes; +}; + + +class CIncremental : public IIncremental +{ +public: + + CIncremental(); + ~CIncremental(); + + + +// IIncremental overrides. +public: + + virtual bool Init( char const *pBSPFilename, char const *pIncrementalFilename ); + + // Load the light definitions out of the incremental file. + // Figure out which lights have changed. + // Change 'activelights' to only consist of new or changed lights. + virtual bool PrepareForLighting(); + + virtual void AddLightToFace( + IncrementalLightID lightID, + int iFace, + int iSample, + int lmSize, + float dot, + int iThread ); + + virtual void FinishFace( + IncrementalLightID lightID, + int iFace, + int iThread ); + + // For each face that was changed during the lighting process, save out + // new data for it in the incremental file. + virtual bool Finalize(); + + virtual void GetFacesTouched( CUtlVector &touched ); + + virtual bool Serialize(); + + +private: + + // Read/write the header from the file. + bool ReadIncrementalHeader( long fp, CIncrementalHeader *pHeader ); + bool WriteIncrementalHeader( long fp ); + + // Returns true if the incremental file is valid and we can use InitUpdate. + bool IsIncrementalFileValid(); + + void Term(); + + // For each light in 'activelights', add a light to m_Lights and link them together. + void AddLightsForActiveLights(); + + // Load and save the state. + bool LoadIncrementalFile(); + bool SaveIncrementalFile(); + + typedef CUtlVector CFaceLightList; + void LinkLightsToFaces( CUtlVector &faceLights ); + + +private: + + char const *m_pIncrementalFilename; + char const *m_pBSPFilename; + + CUtlLinkedList + m_Lights; + + // The face index is set to 1 if a face has new lighting data applied to it. + // This is used to optimize the set of lightmaps we recomposite. + CUtlVector m_FacesTouched; + + int m_TotalMemory; + + // Set to true when one or more runs were completed successfully. + bool m_bSuccessfulRun; +}; + + + +#endif // INCREMENTAL_H diff --git a/mp/src/utils/vrad/leaf_ambient_lighting.cpp b/mp/src/utils/vrad/leaf_ambient_lighting.cpp index ea26c8c6..3836592e 100644 --- a/mp/src/utils/vrad/leaf_ambient_lighting.cpp +++ b/mp/src/utils/vrad/leaf_ambient_lighting.cpp @@ -1,708 +1,708 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#include "vrad.h" -#include "leaf_ambient_lighting.h" -#include "bsplib.h" -#include "vraddetailprops.h" -#include "mathlib/anorms.h" -#include "pacifier.h" -#include "coordsize.h" -#include "vstdlib/random.h" -#include "bsptreedata.h" -#include "messbuf.h" -#include "vmpi.h" -#include "vmpi_distribute_work.h" - -static TableVector g_BoxDirections[6] = -{ - { 1, 0, 0 }, - { -1, 0, 0 }, - { 0, 1, 0 }, - { 0, -1, 0 }, - { 0, 0, 1 }, - { 0, 0, -1 }, -}; - - - -static void ComputeAmbientFromSurface( dface_t *surfID, dworldlight_t* pSkylight, - Vector& radcolor ) -{ - if ( !surfID ) - return; - - texinfo_t *pTexInfo = &texinfo[surfID->texinfo]; - - // If we hit the sky, use the sky ambient - if ( pTexInfo->flags & SURF_SKY ) - { - if ( pSkylight ) - { - // add in sky ambient - VectorCopy( pSkylight->intensity, radcolor ); - } - } - else - { - Vector reflectivity = dtexdata[pTexInfo->texdata].reflectivity; - VectorMultiply( radcolor, reflectivity, radcolor ); - } -} - - -// TODO: it's CRAZY how much lighting code we share with the engine. It should all be shared code. -float Engine_WorldLightAngle( const dworldlight_t *wl, const Vector& lnormal, const Vector& snormal, const Vector& delta ) -{ - float dot, dot2; - - Assert( wl->type == emit_surface ); - - dot = DotProduct( snormal, delta ); - if (dot < 0) - return 0; - - dot2 = -DotProduct (delta, lnormal); - if (dot2 <= ON_EPSILON/10) - return 0; // behind light surface - - return dot * dot2; -} - - -// TODO: it's CRAZY how much lighting code we share with the engine. It should all be shared code. -float Engine_WorldLightDistanceFalloff( const dworldlight_t *wl, const Vector& delta ) -{ - Assert( wl->type == emit_surface ); - - // Cull out stuff that's too far - if (wl->radius != 0) - { - if ( DotProduct( delta, delta ) > (wl->radius * wl->radius)) - return 0.0f; - } - - return InvRSquared(delta); -} - - -void AddEmitSurfaceLights( const Vector &vStart, Vector lightBoxColor[6] ) -{ - fltx4 fractionVisible; - - FourVectors vStart4, wlOrigin4; - vStart4.DuplicateVector ( vStart ); - - for ( int iLight=0; iLight < *pNumworldlights; iLight++ ) - { - dworldlight_t *wl = &dworldlights[iLight]; - - // Should this light even go in the ambient cubes? - if ( !( wl->flags & DWL_FLAGS_INAMBIENTCUBE ) ) - continue; - - Assert( wl->type == emit_surface ); - - // Can this light see the point? - wlOrigin4.DuplicateVector ( wl->origin ); - TestLine ( vStart4, wlOrigin4, &fractionVisible ); - if ( !TestSignSIMD ( CmpGtSIMD ( fractionVisible, Four_Zeros ) ) ) - continue; - - // Add this light's contribution. - Vector vDelta = wl->origin - vStart; - float flDistanceScale = Engine_WorldLightDistanceFalloff( wl, vDelta ); - - Vector vDeltaNorm = vDelta; - VectorNormalize( vDeltaNorm ); - float flAngleScale = Engine_WorldLightAngle( wl, wl->normal, vDeltaNorm, vDeltaNorm ); - - float ratio = flDistanceScale * flAngleScale * SubFloat ( fractionVisible, 0 ); - if ( ratio == 0 ) - continue; - - for ( int i=0; i < 6; i++ ) - { - float t = DotProduct( g_BoxDirections[i], vDeltaNorm ); - if ( t > 0 ) - { - lightBoxColor[i] += wl->intensity * (t * ratio); - } - } - } -} - - -void ComputeAmbientFromSphericalSamples( int iThread, const Vector &vStart, Vector lightBoxColor[6] ) -{ - // Figure out the color that rays hit when shot out from this position. - Vector radcolor[NUMVERTEXNORMALS]; - float tanTheta = tan(VERTEXNORMAL_CONE_INNER_ANGLE); - - for ( int i = 0; i < NUMVERTEXNORMALS; i++ ) - { - Vector vEnd = vStart + g_anorms[i] * (COORD_EXTENT * 1.74); - - // Now that we've got a ray, see what surface we've hit - Vector lightStyleColors[MAX_LIGHTSTYLES]; - lightStyleColors[0].Init(); // We only care about light style 0 here. - CalcRayAmbientLighting( iThread, vStart, vEnd, tanTheta, lightStyleColors ); - - radcolor[i] = lightStyleColors[0]; - } - - // accumulate samples into radiant box - for ( int j = 6; --j >= 0; ) - { - float t = 0; - - lightBoxColor[j].Init(); - - for (int i = 0; i < NUMVERTEXNORMALS; i++) - { - float c = DotProduct( g_anorms[i], g_BoxDirections[j] ); - if (c > 0) - { - t += c; - lightBoxColor[j] += radcolor[i] * c; - } - } - - lightBoxColor[j] *= 1/t; - } - - // Now add direct light from the emit_surface lights. These go in the ambient cube because - // there are a ton of them and they are often so dim that they get filtered out by r_worldlightmin. - AddEmitSurfaceLights( vStart, lightBoxColor ); -} - - -bool IsLeafAmbientSurfaceLight( dworldlight_t *wl ) -{ - static const float g_flWorldLightMinEmitSurface = 0.005f; - static const float g_flWorldLightMinEmitSurfaceDistanceRatio = ( InvRSquared( Vector( 0, 0, 512 ) ) ); - - if ( wl->type != emit_surface ) - return false; - - if ( wl->style != 0 ) - return false; - - float intensity = max( wl->intensity[0], wl->intensity[1] ); - intensity = max( intensity, wl->intensity[2] ); - - return (intensity * g_flWorldLightMinEmitSurfaceDistanceRatio) < g_flWorldLightMinEmitSurface; -} - - -class CLeafSampler -{ -public: - CLeafSampler( int iThread ) : m_iThread(iThread) {} - - // Generate a random point in the leaf's bounding volume - // reject any points that aren't actually in the leaf - // do a couple of tracing heuristics to eliminate points that are inside detail brushes - // or underneath displacement surfaces in the leaf - // return once we have a valid point, use the center if one can't be computed quickly - void GenerateLeafSamplePosition( int leafIndex, const CUtlVector &leafPlanes, Vector &samplePosition ) - { - dleaf_t *pLeaf = dleafs + leafIndex; - - float dx = pLeaf->maxs[0] - pLeaf->mins[0]; - float dy = pLeaf->maxs[1] - pLeaf->mins[1]; - float dz = pLeaf->maxs[2] - pLeaf->mins[2]; - bool bValid = false; - for ( int i = 0; i < 1000 && !bValid; i++ ) - { - samplePosition.x = pLeaf->mins[0] + m_random.RandomFloat(0, dx); - samplePosition.y = pLeaf->mins[1] + m_random.RandomFloat(0, dy); - samplePosition.z = pLeaf->mins[2] + m_random.RandomFloat(0, dz); - bValid = true; - - for ( int j = leafPlanes.Count(); --j >= 0 && bValid; ) - { - float d = DotProduct(leafPlanes[j].normal, samplePosition) - leafPlanes[j].dist; - if ( d < DIST_EPSILON ) - { - // not inside the leaf, try again - bValid = false; - break; - } - } - if ( !bValid ) - continue; - - for ( int j = 0; j < 6; j++ ) - { - Vector start = samplePosition; - int axis = j%3; - start[axis] = (j<3) ? pLeaf->mins[axis] : pLeaf->maxs[axis]; - float t; - Vector normal; - CastRayInLeaf( m_iThread, samplePosition, start, leafIndex, &t, &normal ); - if ( t == 0.0f ) - { - // inside a func_detail, try again. - bValid = false; - break; - } - if ( t != 1.0f ) - { - Vector delta = start - samplePosition; - if ( DotProduct(delta, normal) > 0 ) - { - // hit backside of displacement, try again. - bValid = false; - break; - } - } - } - } - if ( !bValid ) - { - // didn't generate a valid sample point, just use the center of the leaf bbox - samplePosition = ( Vector( pLeaf->mins[0], pLeaf->mins[1], pLeaf->mins[2] ) + Vector( pLeaf->maxs[0], pLeaf->maxs[1], pLeaf->maxs[2] ) ) * 0.5f; - } - } - -private: - int m_iThread; - CUniformRandomStream m_random; -}; - -// gets a list of the planes pointing into a leaf -void GetLeafBoundaryPlanes( CUtlVector &list, int leafIndex ) -{ - list.RemoveAll(); - int nodeIndex = leafparents[leafIndex]; - int child = -(leafIndex + 1); - while ( nodeIndex >= 0 ) - { - dnode_t *pNode = dnodes + nodeIndex; - dplane_t *pNodePlane = dplanes + pNode->planenum; - if ( pNode->children[0] == child ) - { - // front side - list.AddToTail( *pNodePlane ); - } - else - { - // back side - int plane = list.AddToTail(); - list[plane].dist = -pNodePlane->dist; - list[plane].normal = -pNodePlane->normal; - list[plane].type = pNodePlane->type; - } - child = nodeIndex; - nodeIndex = nodeparents[child]; - } -} - -// this stores each sample of the ambient lighting -struct ambientsample_t -{ - Vector pos; - Vector cube[6]; -}; - -// add the sample to the list. If we exceed the maximum number of samples, the worst sample will -// be discarded. This has the effect of converging on the best samples when enough are added. -void AddSampleToList( CUtlVector &list, const Vector &samplePosition, Vector *pCube ) -{ - const int MAX_SAMPLES = 16; - - int index = list.AddToTail(); - list[index].pos = samplePosition; - for ( int i = 0; i < 6; i++ ) - { - list[index].cube[i] = pCube[i]; - } - - if ( list.Count() <= MAX_SAMPLES ) - return; - - int nearestNeighborIndex = 0; - float nearestNeighborDist = FLT_MAX; - float nearestNeighborTotal = 0; - for ( int i = 0; i < list.Count(); i++ ) - { - int closestIndex = 0; - float closestDist = FLT_MAX; - float totalDC = 0; - for ( int j = 0; j < list.Count(); j++ ) - { - if ( j == i ) - continue; - float dist = (list[i].pos - list[j].pos).Length(); - float maxDC = 0; - for ( int k = 0; k < 6; k++ ) - { - // color delta is computed per-component, per cube side - for (int s = 0; s < 3; s++ ) - { - float dc = fabs(list[i].cube[k][s] - list[j].cube[k][s]); - maxDC = max(maxDC,dc); - } - totalDC += maxDC; - } - // need a measurable difference in color or we'll just rely on position - if ( maxDC < 1e-4f ) - { - maxDC = 0; - } - else if ( maxDC > 1.0f ) - { - maxDC = 1.0f; - } - // selection criteria is 10% distance, 90% color difference - // choose samples that fill the space (large distance from each other) - // and have largest color variation - float distanceFactor = 0.1f + (maxDC * 0.9f); - dist *= distanceFactor; - - // find the "closest" sample to this one - if ( dist < closestDist ) - { - closestDist = dist; - closestIndex = j; - } - } - // the sample with the "closest" neighbor is rejected - if ( closestDist < nearestNeighborDist || (closestDist == nearestNeighborDist && totalDC < nearestNeighborTotal) ) - { - nearestNeighborDist = closestDist; - nearestNeighborIndex = i; - } - } - list.FastRemove( nearestNeighborIndex ); -} - -// max number of units in gamma space of per-side delta -int CubeDeltaGammaSpace( Vector *pCube0, Vector *pCube1 ) -{ - int maxDelta = 0; - // do this comparison in gamma space to try and get a perceptual basis for the compare - for ( int i = 0; i < 6; i++ ) - { - for ( int j = 0; j < 3; j++ ) - { - int val0 = LinearToScreenGamma( pCube0[i][j] ); - int val1 = LinearToScreenGamma( pCube1[i][j] ); - int delta = abs(val0-val1); - if ( delta > maxDelta ) - maxDelta = delta; - } - } - return maxDelta; -} -// reconstruct the ambient lighting for a leaf at the given position in worldspace -// optionally skip one of the entries in the list -void Mod_LeafAmbientColorAtPos( Vector *pOut, const Vector &pos, const CUtlVector &list, int skipIndex ) -{ - for ( int i = 0; i < 6; i++ ) - { - pOut[i].Init(); - } - float totalFactor = 0; - for ( int i = 0; i < list.Count(); i++ ) - { - if ( i == skipIndex ) - continue; - // do an inverse squared distance weighted average of the samples to reconstruct - // the original function - float dist = (list[i].pos - pos).LengthSqr(); - float factor = 1.0f / (dist + 1.0f); - totalFactor += factor; - for ( int j = 0; j < 6; j++ ) - { - pOut[j] += list[i].cube[j] * factor; - } - } - for ( int i = 0; i < 6; i++ ) - { - pOut[i] *= (1.0f / totalFactor); - } -} - -// this samples the lighting at each sample and removes any unnecessary samples -void CompressAmbientSampleList( CUtlVector &list ) -{ - Vector testCube[6]; - for ( int i = 0; i < list.Count(); i++ ) - { - if ( list.Count() > 1 ) - { - Mod_LeafAmbientColorAtPos( testCube, list[i].pos, list, i ); - if ( CubeDeltaGammaSpace(testCube, list[i].cube) < 3 ) - { - list.FastRemove(i); - i--; - } - } - } -} - -// basically this is an intersection routine that returns a distance between the boxes -float AABBDistance( const Vector &mins0, const Vector &maxs0, const Vector &mins1, const Vector &maxs1 ) -{ - Vector delta; - for ( int i = 0; i < 3; i++ ) - { - float greatestMin = max(mins0[i], mins1[i]); - float leastMax = min(maxs0[i], maxs1[i]); - delta[i] = (greatestMin < leastMax) ? 0 : (leastMax - greatestMin); - } - return delta.Length(); -} - -// build a list of leaves from a query -class CLeafList : public ISpatialLeafEnumerator -{ -public: - virtual bool EnumerateLeaf( int leaf, int context ) - { - m_list.AddToTail(leaf); - return true; - } - - CUtlVector m_list; -}; - -// conver short[3] to vector -static void LeafBounds( int leafIndex, Vector &mins, Vector &maxs ) -{ - for ( int i = 0; i < 3; i++ ) - { - mins[i] = dleafs[leafIndex].mins[i]; - maxs[i] = dleafs[leafIndex].maxs[i]; - } -} - -// returns the index of the nearest leaf with ambient samples -int NearestNeighborWithLight(int leafID) -{ - Vector mins, maxs; - LeafBounds( leafID, mins, maxs ); - Vector size = maxs - mins; - CLeafList leafList; - ToolBSPTree()->EnumerateLeavesInBox( mins-size, maxs+size, &leafList, 0 ); - float bestDist = FLT_MAX; - int bestIndex = leafID; - for ( int i = 0; i < leafList.m_list.Count(); i++ ) - { - int testIndex = leafList.m_list[i]; - if ( !g_pLeafAmbientIndex->Element(testIndex).ambientSampleCount ) - continue; - - Vector testMins, testMaxs; - LeafBounds( testIndex, testMins, testMaxs ); - float dist = AABBDistance( mins, maxs, testMins, testMaxs ); - if ( dist < bestDist ) - { - bestDist = dist; - bestIndex = testIndex; - } - } - return bestIndex; -} - -// maps a float to a byte fraction between min & max -static byte Fixed8Fraction( float t, float tMin, float tMax ) -{ - if ( tMax <= tMin ) - return 0; - - float frac = RemapValClamped( t, tMin, tMax, 0.0f, 255.0f ); - return byte(frac+0.5f); -} - -CUtlVector< CUtlVector > g_LeafAmbientSamples; - -void ComputeAmbientForLeaf( int iThread, int leafID, CUtlVector &list ) -{ - CUtlVector leafPlanes; - CLeafSampler sampler( iThread ); - - GetLeafBoundaryPlanes( leafPlanes, leafID ); - list.RemoveAll(); - // this heuristic tries to generate at least one sample per volume (chosen to be similar to the size of a player) in the space - int xSize = (dleafs[leafID].maxs[0] - dleafs[leafID].mins[0]) / 32; - int ySize = (dleafs[leafID].maxs[1] - dleafs[leafID].mins[1]) / 32; - int zSize = (dleafs[leafID].maxs[2] - dleafs[leafID].mins[2]) / 64; - xSize = max(xSize,1); - ySize = max(xSize,1); - zSize = max(xSize,1); - // generate update 128 candidate samples, always at least one sample - int volumeCount = xSize * ySize * zSize; - if ( g_bFastAmbient ) - { - // save compute time, only do one sample - volumeCount = 1; - } - int sampleCount = clamp( volumeCount, 1, 128 ); - if ( dleafs[leafID].contents & CONTENTS_SOLID ) - { - // don't generate any samples in solid leaves - // NOTE: We copy the nearest non-solid leaf sample pointers into this leaf at the end - return; - } - Vector cube[6]; - for ( int i = 0; i < sampleCount; i++ ) - { - // compute each candidate sample and add to the list - Vector samplePosition; - sampler.GenerateLeafSamplePosition( leafID, leafPlanes, samplePosition ); - ComputeAmbientFromSphericalSamples( iThread, samplePosition, cube ); - // note this will remove the least valuable sample once the limit is reached - AddSampleToList( list, samplePosition, cube ); - } - - // remove any samples that can be reconstructed with the remaining data - CompressAmbientSampleList( list ); -} - -static void ThreadComputeLeafAmbient( int iThread, void *pUserData ) -{ - CUtlVector list; - while (1) - { - int leafID = GetThreadWork (); - if (leafID == -1) - break; - list.RemoveAll(); - ComputeAmbientForLeaf(iThread, leafID, list); - // copy to the output array - g_LeafAmbientSamples[leafID].SetCount( list.Count() ); - for ( int i = 0; i < list.Count(); i++ ) - { - g_LeafAmbientSamples[leafID].Element(i) = list.Element(i); - } - } -} - -void VMPI_ProcessLeafAmbient( int iThread, uint64 iLeaf, MessageBuffer *pBuf ) -{ - CUtlVector list; - ComputeAmbientForLeaf(iThread, (int)iLeaf, list); - - VMPI_SetCurrentStage( "EncodeLeafAmbientResults" ); - - // Encode the results. - int nSamples = list.Count(); - pBuf->write( &nSamples, sizeof( nSamples ) ); - if ( nSamples ) - { - pBuf->write( list.Base(), list.Count() * sizeof( ambientsample_t ) ); - } -} - -//----------------------------------------------------------------------------- -// Called on the master when a worker finishes processing a static prop. -//----------------------------------------------------------------------------- -void VMPI_ReceiveLeafAmbientResults( uint64 leafID, MessageBuffer *pBuf, int iWorker ) -{ - // Decode the results. - int nSamples; - pBuf->read( &nSamples, sizeof( nSamples ) ); - - g_LeafAmbientSamples[leafID].SetCount( nSamples ); - if ( nSamples ) - { - pBuf->read(g_LeafAmbientSamples[leafID].Base(), nSamples * sizeof(ambientsample_t) ); - } -} - - -void ComputePerLeafAmbientLighting() -{ - // Figure out which lights should go in the per-leaf ambient cubes. - int nInAmbientCube = 0; - int nSurfaceLights = 0; - for ( int i=0; i < *pNumworldlights; i++ ) - { - dworldlight_t *wl = &dworldlights[i]; - - if ( IsLeafAmbientSurfaceLight( wl ) ) - wl->flags |= DWL_FLAGS_INAMBIENTCUBE; - else - wl->flags &= ~DWL_FLAGS_INAMBIENTCUBE; - - if ( wl->type == emit_surface ) - ++nSurfaceLights; - - if ( wl->flags & DWL_FLAGS_INAMBIENTCUBE ) - ++nInAmbientCube; - } - - Msg( "%d of %d (%d%% of) surface lights went in leaf ambient cubes.\n", nInAmbientCube, nSurfaceLights, nSurfaceLights ? ((nInAmbientCube*100) / nSurfaceLights) : 0 ); - - g_LeafAmbientSamples.SetCount(numleafs); - - if ( g_bUseMPI ) - { - // Distribute the work among the workers. - VMPI_SetCurrentStage( "ComputeLeafAmbientLighting" ); - DistributeWork( numleafs, VMPI_DISTRIBUTEWORK_PACKETID, VMPI_ProcessLeafAmbient, VMPI_ReceiveLeafAmbientResults ); - } - else - { - RunThreadsOn(numleafs, true, ThreadComputeLeafAmbient); - } - - // now write out the data - Msg("Writing leaf ambient..."); - g_pLeafAmbientIndex->RemoveAll(); - g_pLeafAmbientLighting->RemoveAll(); - g_pLeafAmbientIndex->SetCount( numleafs ); - g_pLeafAmbientLighting->EnsureCapacity( numleafs*4 ); - for ( int leafID = 0; leafID < numleafs; leafID++ ) - { - const CUtlVector &list = g_LeafAmbientSamples[leafID]; - g_pLeafAmbientIndex->Element(leafID).ambientSampleCount = list.Count(); - if ( !list.Count() ) - { - g_pLeafAmbientIndex->Element(leafID).firstAmbientSample = 0; - } - else - { - g_pLeafAmbientIndex->Element(leafID).firstAmbientSample = g_pLeafAmbientLighting->Count(); - // compute the samples in disk format. Encode the positions in 8-bits using leaf bounds fractions - for ( int i = 0; i < list.Count(); i++ ) - { - int outIndex = g_pLeafAmbientLighting->AddToTail(); - dleafambientlighting_t &light = g_pLeafAmbientLighting->Element(outIndex); - - light.x = Fixed8Fraction( list[i].pos.x, dleafs[leafID].mins[0], dleafs[leafID].maxs[0] ); - light.y = Fixed8Fraction( list[i].pos.y, dleafs[leafID].mins[1], dleafs[leafID].maxs[1] ); - light.z = Fixed8Fraction( list[i].pos.z, dleafs[leafID].mins[2], dleafs[leafID].maxs[2] ); - light.pad = 0; - for ( int side = 0; side < 6; side++ ) - { - VectorToColorRGBExp32( list[i].cube[side], light.cube.m_Color[side] ); - } - } - } - } - for ( int i = 0; i < numleafs; i++ ) - { - // UNDONE: Do this dynamically in the engine instead. This will allow us to sample across leaf - // boundaries always which should improve the quality of lighting in general - if ( g_pLeafAmbientIndex->Element(i).ambientSampleCount == 0 ) - { - if ( !(dleafs[i].contents & CONTENTS_SOLID) ) - { - Msg("Bad leaf ambient for leaf %d\n", i ); - } - - int refLeaf = NearestNeighborWithLight(i); - g_pLeafAmbientIndex->Element(i).ambientSampleCount = 0; - g_pLeafAmbientIndex->Element(i).firstAmbientSample = refLeaf; - } - } - Msg("done\n"); -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "vrad.h" +#include "leaf_ambient_lighting.h" +#include "bsplib.h" +#include "vraddetailprops.h" +#include "mathlib/anorms.h" +#include "pacifier.h" +#include "coordsize.h" +#include "vstdlib/random.h" +#include "bsptreedata.h" +#include "messbuf.h" +#include "vmpi.h" +#include "vmpi_distribute_work.h" + +static TableVector g_BoxDirections[6] = +{ + { 1, 0, 0 }, + { -1, 0, 0 }, + { 0, 1, 0 }, + { 0, -1, 0 }, + { 0, 0, 1 }, + { 0, 0, -1 }, +}; + + + +static void ComputeAmbientFromSurface( dface_t *surfID, dworldlight_t* pSkylight, + Vector& radcolor ) +{ + if ( !surfID ) + return; + + texinfo_t *pTexInfo = &texinfo[surfID->texinfo]; + + // If we hit the sky, use the sky ambient + if ( pTexInfo->flags & SURF_SKY ) + { + if ( pSkylight ) + { + // add in sky ambient + VectorCopy( pSkylight->intensity, radcolor ); + } + } + else + { + Vector reflectivity = dtexdata[pTexInfo->texdata].reflectivity; + VectorMultiply( radcolor, reflectivity, radcolor ); + } +} + + +// TODO: it's CRAZY how much lighting code we share with the engine. It should all be shared code. +float Engine_WorldLightAngle( const dworldlight_t *wl, const Vector& lnormal, const Vector& snormal, const Vector& delta ) +{ + float dot, dot2; + + Assert( wl->type == emit_surface ); + + dot = DotProduct( snormal, delta ); + if (dot < 0) + return 0; + + dot2 = -DotProduct (delta, lnormal); + if (dot2 <= ON_EPSILON/10) + return 0; // behind light surface + + return dot * dot2; +} + + +// TODO: it's CRAZY how much lighting code we share with the engine. It should all be shared code. +float Engine_WorldLightDistanceFalloff( const dworldlight_t *wl, const Vector& delta ) +{ + Assert( wl->type == emit_surface ); + + // Cull out stuff that's too far + if (wl->radius != 0) + { + if ( DotProduct( delta, delta ) > (wl->radius * wl->radius)) + return 0.0f; + } + + return InvRSquared(delta); +} + + +void AddEmitSurfaceLights( const Vector &vStart, Vector lightBoxColor[6] ) +{ + fltx4 fractionVisible; + + FourVectors vStart4, wlOrigin4; + vStart4.DuplicateVector ( vStart ); + + for ( int iLight=0; iLight < *pNumworldlights; iLight++ ) + { + dworldlight_t *wl = &dworldlights[iLight]; + + // Should this light even go in the ambient cubes? + if ( !( wl->flags & DWL_FLAGS_INAMBIENTCUBE ) ) + continue; + + Assert( wl->type == emit_surface ); + + // Can this light see the point? + wlOrigin4.DuplicateVector ( wl->origin ); + TestLine ( vStart4, wlOrigin4, &fractionVisible ); + if ( !TestSignSIMD ( CmpGtSIMD ( fractionVisible, Four_Zeros ) ) ) + continue; + + // Add this light's contribution. + Vector vDelta = wl->origin - vStart; + float flDistanceScale = Engine_WorldLightDistanceFalloff( wl, vDelta ); + + Vector vDeltaNorm = vDelta; + VectorNormalize( vDeltaNorm ); + float flAngleScale = Engine_WorldLightAngle( wl, wl->normal, vDeltaNorm, vDeltaNorm ); + + float ratio = flDistanceScale * flAngleScale * SubFloat ( fractionVisible, 0 ); + if ( ratio == 0 ) + continue; + + for ( int i=0; i < 6; i++ ) + { + float t = DotProduct( g_BoxDirections[i], vDeltaNorm ); + if ( t > 0 ) + { + lightBoxColor[i] += wl->intensity * (t * ratio); + } + } + } +} + + +void ComputeAmbientFromSphericalSamples( int iThread, const Vector &vStart, Vector lightBoxColor[6] ) +{ + // Figure out the color that rays hit when shot out from this position. + Vector radcolor[NUMVERTEXNORMALS]; + float tanTheta = tan(VERTEXNORMAL_CONE_INNER_ANGLE); + + for ( int i = 0; i < NUMVERTEXNORMALS; i++ ) + { + Vector vEnd = vStart + g_anorms[i] * (COORD_EXTENT * 1.74); + + // Now that we've got a ray, see what surface we've hit + Vector lightStyleColors[MAX_LIGHTSTYLES]; + lightStyleColors[0].Init(); // We only care about light style 0 here. + CalcRayAmbientLighting( iThread, vStart, vEnd, tanTheta, lightStyleColors ); + + radcolor[i] = lightStyleColors[0]; + } + + // accumulate samples into radiant box + for ( int j = 6; --j >= 0; ) + { + float t = 0; + + lightBoxColor[j].Init(); + + for (int i = 0; i < NUMVERTEXNORMALS; i++) + { + float c = DotProduct( g_anorms[i], g_BoxDirections[j] ); + if (c > 0) + { + t += c; + lightBoxColor[j] += radcolor[i] * c; + } + } + + lightBoxColor[j] *= 1/t; + } + + // Now add direct light from the emit_surface lights. These go in the ambient cube because + // there are a ton of them and they are often so dim that they get filtered out by r_worldlightmin. + AddEmitSurfaceLights( vStart, lightBoxColor ); +} + + +bool IsLeafAmbientSurfaceLight( dworldlight_t *wl ) +{ + static const float g_flWorldLightMinEmitSurface = 0.005f; + static const float g_flWorldLightMinEmitSurfaceDistanceRatio = ( InvRSquared( Vector( 0, 0, 512 ) ) ); + + if ( wl->type != emit_surface ) + return false; + + if ( wl->style != 0 ) + return false; + + float intensity = max( wl->intensity[0], wl->intensity[1] ); + intensity = max( intensity, wl->intensity[2] ); + + return (intensity * g_flWorldLightMinEmitSurfaceDistanceRatio) < g_flWorldLightMinEmitSurface; +} + + +class CLeafSampler +{ +public: + CLeafSampler( int iThread ) : m_iThread(iThread) {} + + // Generate a random point in the leaf's bounding volume + // reject any points that aren't actually in the leaf + // do a couple of tracing heuristics to eliminate points that are inside detail brushes + // or underneath displacement surfaces in the leaf + // return once we have a valid point, use the center if one can't be computed quickly + void GenerateLeafSamplePosition( int leafIndex, const CUtlVector &leafPlanes, Vector &samplePosition ) + { + dleaf_t *pLeaf = dleafs + leafIndex; + + float dx = pLeaf->maxs[0] - pLeaf->mins[0]; + float dy = pLeaf->maxs[1] - pLeaf->mins[1]; + float dz = pLeaf->maxs[2] - pLeaf->mins[2]; + bool bValid = false; + for ( int i = 0; i < 1000 && !bValid; i++ ) + { + samplePosition.x = pLeaf->mins[0] + m_random.RandomFloat(0, dx); + samplePosition.y = pLeaf->mins[1] + m_random.RandomFloat(0, dy); + samplePosition.z = pLeaf->mins[2] + m_random.RandomFloat(0, dz); + bValid = true; + + for ( int j = leafPlanes.Count(); --j >= 0 && bValid; ) + { + float d = DotProduct(leafPlanes[j].normal, samplePosition) - leafPlanes[j].dist; + if ( d < DIST_EPSILON ) + { + // not inside the leaf, try again + bValid = false; + break; + } + } + if ( !bValid ) + continue; + + for ( int j = 0; j < 6; j++ ) + { + Vector start = samplePosition; + int axis = j%3; + start[axis] = (j<3) ? pLeaf->mins[axis] : pLeaf->maxs[axis]; + float t; + Vector normal; + CastRayInLeaf( m_iThread, samplePosition, start, leafIndex, &t, &normal ); + if ( t == 0.0f ) + { + // inside a func_detail, try again. + bValid = false; + break; + } + if ( t != 1.0f ) + { + Vector delta = start - samplePosition; + if ( DotProduct(delta, normal) > 0 ) + { + // hit backside of displacement, try again. + bValid = false; + break; + } + } + } + } + if ( !bValid ) + { + // didn't generate a valid sample point, just use the center of the leaf bbox + samplePosition = ( Vector( pLeaf->mins[0], pLeaf->mins[1], pLeaf->mins[2] ) + Vector( pLeaf->maxs[0], pLeaf->maxs[1], pLeaf->maxs[2] ) ) * 0.5f; + } + } + +private: + int m_iThread; + CUniformRandomStream m_random; +}; + +// gets a list of the planes pointing into a leaf +void GetLeafBoundaryPlanes( CUtlVector &list, int leafIndex ) +{ + list.RemoveAll(); + int nodeIndex = leafparents[leafIndex]; + int child = -(leafIndex + 1); + while ( nodeIndex >= 0 ) + { + dnode_t *pNode = dnodes + nodeIndex; + dplane_t *pNodePlane = dplanes + pNode->planenum; + if ( pNode->children[0] == child ) + { + // front side + list.AddToTail( *pNodePlane ); + } + else + { + // back side + int plane = list.AddToTail(); + list[plane].dist = -pNodePlane->dist; + list[plane].normal = -pNodePlane->normal; + list[plane].type = pNodePlane->type; + } + child = nodeIndex; + nodeIndex = nodeparents[child]; + } +} + +// this stores each sample of the ambient lighting +struct ambientsample_t +{ + Vector pos; + Vector cube[6]; +}; + +// add the sample to the list. If we exceed the maximum number of samples, the worst sample will +// be discarded. This has the effect of converging on the best samples when enough are added. +void AddSampleToList( CUtlVector &list, const Vector &samplePosition, Vector *pCube ) +{ + const int MAX_SAMPLES = 16; + + int index = list.AddToTail(); + list[index].pos = samplePosition; + for ( int i = 0; i < 6; i++ ) + { + list[index].cube[i] = pCube[i]; + } + + if ( list.Count() <= MAX_SAMPLES ) + return; + + int nearestNeighborIndex = 0; + float nearestNeighborDist = FLT_MAX; + float nearestNeighborTotal = 0; + for ( int i = 0; i < list.Count(); i++ ) + { + int closestIndex = 0; + float closestDist = FLT_MAX; + float totalDC = 0; + for ( int j = 0; j < list.Count(); j++ ) + { + if ( j == i ) + continue; + float dist = (list[i].pos - list[j].pos).Length(); + float maxDC = 0; + for ( int k = 0; k < 6; k++ ) + { + // color delta is computed per-component, per cube side + for (int s = 0; s < 3; s++ ) + { + float dc = fabs(list[i].cube[k][s] - list[j].cube[k][s]); + maxDC = max(maxDC,dc); + } + totalDC += maxDC; + } + // need a measurable difference in color or we'll just rely on position + if ( maxDC < 1e-4f ) + { + maxDC = 0; + } + else if ( maxDC > 1.0f ) + { + maxDC = 1.0f; + } + // selection criteria is 10% distance, 90% color difference + // choose samples that fill the space (large distance from each other) + // and have largest color variation + float distanceFactor = 0.1f + (maxDC * 0.9f); + dist *= distanceFactor; + + // find the "closest" sample to this one + if ( dist < closestDist ) + { + closestDist = dist; + closestIndex = j; + } + } + // the sample with the "closest" neighbor is rejected + if ( closestDist < nearestNeighborDist || (closestDist == nearestNeighborDist && totalDC < nearestNeighborTotal) ) + { + nearestNeighborDist = closestDist; + nearestNeighborIndex = i; + } + } + list.FastRemove( nearestNeighborIndex ); +} + +// max number of units in gamma space of per-side delta +int CubeDeltaGammaSpace( Vector *pCube0, Vector *pCube1 ) +{ + int maxDelta = 0; + // do this comparison in gamma space to try and get a perceptual basis for the compare + for ( int i = 0; i < 6; i++ ) + { + for ( int j = 0; j < 3; j++ ) + { + int val0 = LinearToScreenGamma( pCube0[i][j] ); + int val1 = LinearToScreenGamma( pCube1[i][j] ); + int delta = abs(val0-val1); + if ( delta > maxDelta ) + maxDelta = delta; + } + } + return maxDelta; +} +// reconstruct the ambient lighting for a leaf at the given position in worldspace +// optionally skip one of the entries in the list +void Mod_LeafAmbientColorAtPos( Vector *pOut, const Vector &pos, const CUtlVector &list, int skipIndex ) +{ + for ( int i = 0; i < 6; i++ ) + { + pOut[i].Init(); + } + float totalFactor = 0; + for ( int i = 0; i < list.Count(); i++ ) + { + if ( i == skipIndex ) + continue; + // do an inverse squared distance weighted average of the samples to reconstruct + // the original function + float dist = (list[i].pos - pos).LengthSqr(); + float factor = 1.0f / (dist + 1.0f); + totalFactor += factor; + for ( int j = 0; j < 6; j++ ) + { + pOut[j] += list[i].cube[j] * factor; + } + } + for ( int i = 0; i < 6; i++ ) + { + pOut[i] *= (1.0f / totalFactor); + } +} + +// this samples the lighting at each sample and removes any unnecessary samples +void CompressAmbientSampleList( CUtlVector &list ) +{ + Vector testCube[6]; + for ( int i = 0; i < list.Count(); i++ ) + { + if ( list.Count() > 1 ) + { + Mod_LeafAmbientColorAtPos( testCube, list[i].pos, list, i ); + if ( CubeDeltaGammaSpace(testCube, list[i].cube) < 3 ) + { + list.FastRemove(i); + i--; + } + } + } +} + +// basically this is an intersection routine that returns a distance between the boxes +float AABBDistance( const Vector &mins0, const Vector &maxs0, const Vector &mins1, const Vector &maxs1 ) +{ + Vector delta; + for ( int i = 0; i < 3; i++ ) + { + float greatestMin = max(mins0[i], mins1[i]); + float leastMax = min(maxs0[i], maxs1[i]); + delta[i] = (greatestMin < leastMax) ? 0 : (leastMax - greatestMin); + } + return delta.Length(); +} + +// build a list of leaves from a query +class CLeafList : public ISpatialLeafEnumerator +{ +public: + virtual bool EnumerateLeaf( int leaf, int context ) + { + m_list.AddToTail(leaf); + return true; + } + + CUtlVector m_list; +}; + +// conver short[3] to vector +static void LeafBounds( int leafIndex, Vector &mins, Vector &maxs ) +{ + for ( int i = 0; i < 3; i++ ) + { + mins[i] = dleafs[leafIndex].mins[i]; + maxs[i] = dleafs[leafIndex].maxs[i]; + } +} + +// returns the index of the nearest leaf with ambient samples +int NearestNeighborWithLight(int leafID) +{ + Vector mins, maxs; + LeafBounds( leafID, mins, maxs ); + Vector size = maxs - mins; + CLeafList leafList; + ToolBSPTree()->EnumerateLeavesInBox( mins-size, maxs+size, &leafList, 0 ); + float bestDist = FLT_MAX; + int bestIndex = leafID; + for ( int i = 0; i < leafList.m_list.Count(); i++ ) + { + int testIndex = leafList.m_list[i]; + if ( !g_pLeafAmbientIndex->Element(testIndex).ambientSampleCount ) + continue; + + Vector testMins, testMaxs; + LeafBounds( testIndex, testMins, testMaxs ); + float dist = AABBDistance( mins, maxs, testMins, testMaxs ); + if ( dist < bestDist ) + { + bestDist = dist; + bestIndex = testIndex; + } + } + return bestIndex; +} + +// maps a float to a byte fraction between min & max +static byte Fixed8Fraction( float t, float tMin, float tMax ) +{ + if ( tMax <= tMin ) + return 0; + + float frac = RemapValClamped( t, tMin, tMax, 0.0f, 255.0f ); + return byte(frac+0.5f); +} + +CUtlVector< CUtlVector > g_LeafAmbientSamples; + +void ComputeAmbientForLeaf( int iThread, int leafID, CUtlVector &list ) +{ + CUtlVector leafPlanes; + CLeafSampler sampler( iThread ); + + GetLeafBoundaryPlanes( leafPlanes, leafID ); + list.RemoveAll(); + // this heuristic tries to generate at least one sample per volume (chosen to be similar to the size of a player) in the space + int xSize = (dleafs[leafID].maxs[0] - dleafs[leafID].mins[0]) / 32; + int ySize = (dleafs[leafID].maxs[1] - dleafs[leafID].mins[1]) / 32; + int zSize = (dleafs[leafID].maxs[2] - dleafs[leafID].mins[2]) / 64; + xSize = max(xSize,1); + ySize = max(xSize,1); + zSize = max(xSize,1); + // generate update 128 candidate samples, always at least one sample + int volumeCount = xSize * ySize * zSize; + if ( g_bFastAmbient ) + { + // save compute time, only do one sample + volumeCount = 1; + } + int sampleCount = clamp( volumeCount, 1, 128 ); + if ( dleafs[leafID].contents & CONTENTS_SOLID ) + { + // don't generate any samples in solid leaves + // NOTE: We copy the nearest non-solid leaf sample pointers into this leaf at the end + return; + } + Vector cube[6]; + for ( int i = 0; i < sampleCount; i++ ) + { + // compute each candidate sample and add to the list + Vector samplePosition; + sampler.GenerateLeafSamplePosition( leafID, leafPlanes, samplePosition ); + ComputeAmbientFromSphericalSamples( iThread, samplePosition, cube ); + // note this will remove the least valuable sample once the limit is reached + AddSampleToList( list, samplePosition, cube ); + } + + // remove any samples that can be reconstructed with the remaining data + CompressAmbientSampleList( list ); +} + +static void ThreadComputeLeafAmbient( int iThread, void *pUserData ) +{ + CUtlVector list; + while (1) + { + int leafID = GetThreadWork (); + if (leafID == -1) + break; + list.RemoveAll(); + ComputeAmbientForLeaf(iThread, leafID, list); + // copy to the output array + g_LeafAmbientSamples[leafID].SetCount( list.Count() ); + for ( int i = 0; i < list.Count(); i++ ) + { + g_LeafAmbientSamples[leafID].Element(i) = list.Element(i); + } + } +} + +void VMPI_ProcessLeafAmbient( int iThread, uint64 iLeaf, MessageBuffer *pBuf ) +{ + CUtlVector list; + ComputeAmbientForLeaf(iThread, (int)iLeaf, list); + + VMPI_SetCurrentStage( "EncodeLeafAmbientResults" ); + + // Encode the results. + int nSamples = list.Count(); + pBuf->write( &nSamples, sizeof( nSamples ) ); + if ( nSamples ) + { + pBuf->write( list.Base(), list.Count() * sizeof( ambientsample_t ) ); + } +} + +//----------------------------------------------------------------------------- +// Called on the master when a worker finishes processing a static prop. +//----------------------------------------------------------------------------- +void VMPI_ReceiveLeafAmbientResults( uint64 leafID, MessageBuffer *pBuf, int iWorker ) +{ + // Decode the results. + int nSamples; + pBuf->read( &nSamples, sizeof( nSamples ) ); + + g_LeafAmbientSamples[leafID].SetCount( nSamples ); + if ( nSamples ) + { + pBuf->read(g_LeafAmbientSamples[leafID].Base(), nSamples * sizeof(ambientsample_t) ); + } +} + + +void ComputePerLeafAmbientLighting() +{ + // Figure out which lights should go in the per-leaf ambient cubes. + int nInAmbientCube = 0; + int nSurfaceLights = 0; + for ( int i=0; i < *pNumworldlights; i++ ) + { + dworldlight_t *wl = &dworldlights[i]; + + if ( IsLeafAmbientSurfaceLight( wl ) ) + wl->flags |= DWL_FLAGS_INAMBIENTCUBE; + else + wl->flags &= ~DWL_FLAGS_INAMBIENTCUBE; + + if ( wl->type == emit_surface ) + ++nSurfaceLights; + + if ( wl->flags & DWL_FLAGS_INAMBIENTCUBE ) + ++nInAmbientCube; + } + + Msg( "%d of %d (%d%% of) surface lights went in leaf ambient cubes.\n", nInAmbientCube, nSurfaceLights, nSurfaceLights ? ((nInAmbientCube*100) / nSurfaceLights) : 0 ); + + g_LeafAmbientSamples.SetCount(numleafs); + + if ( g_bUseMPI ) + { + // Distribute the work among the workers. + VMPI_SetCurrentStage( "ComputeLeafAmbientLighting" ); + DistributeWork( numleafs, VMPI_DISTRIBUTEWORK_PACKETID, VMPI_ProcessLeafAmbient, VMPI_ReceiveLeafAmbientResults ); + } + else + { + RunThreadsOn(numleafs, true, ThreadComputeLeafAmbient); + } + + // now write out the data + Msg("Writing leaf ambient..."); + g_pLeafAmbientIndex->RemoveAll(); + g_pLeafAmbientLighting->RemoveAll(); + g_pLeafAmbientIndex->SetCount( numleafs ); + g_pLeafAmbientLighting->EnsureCapacity( numleafs*4 ); + for ( int leafID = 0; leafID < numleafs; leafID++ ) + { + const CUtlVector &list = g_LeafAmbientSamples[leafID]; + g_pLeafAmbientIndex->Element(leafID).ambientSampleCount = list.Count(); + if ( !list.Count() ) + { + g_pLeafAmbientIndex->Element(leafID).firstAmbientSample = 0; + } + else + { + g_pLeafAmbientIndex->Element(leafID).firstAmbientSample = g_pLeafAmbientLighting->Count(); + // compute the samples in disk format. Encode the positions in 8-bits using leaf bounds fractions + for ( int i = 0; i < list.Count(); i++ ) + { + int outIndex = g_pLeafAmbientLighting->AddToTail(); + dleafambientlighting_t &light = g_pLeafAmbientLighting->Element(outIndex); + + light.x = Fixed8Fraction( list[i].pos.x, dleafs[leafID].mins[0], dleafs[leafID].maxs[0] ); + light.y = Fixed8Fraction( list[i].pos.y, dleafs[leafID].mins[1], dleafs[leafID].maxs[1] ); + light.z = Fixed8Fraction( list[i].pos.z, dleafs[leafID].mins[2], dleafs[leafID].maxs[2] ); + light.pad = 0; + for ( int side = 0; side < 6; side++ ) + { + VectorToColorRGBExp32( list[i].cube[side], light.cube.m_Color[side] ); + } + } + } + } + for ( int i = 0; i < numleafs; i++ ) + { + // UNDONE: Do this dynamically in the engine instead. This will allow us to sample across leaf + // boundaries always which should improve the quality of lighting in general + if ( g_pLeafAmbientIndex->Element(i).ambientSampleCount == 0 ) + { + if ( !(dleafs[i].contents & CONTENTS_SOLID) ) + { + Msg("Bad leaf ambient for leaf %d\n", i ); + } + + int refLeaf = NearestNeighborWithLight(i); + g_pLeafAmbientIndex->Element(i).ambientSampleCount = 0; + g_pLeafAmbientIndex->Element(i).firstAmbientSample = refLeaf; + } + } + Msg("done\n"); +} + diff --git a/mp/src/utils/vrad/leaf_ambient_lighting.h b/mp/src/utils/vrad/leaf_ambient_lighting.h index b6bb50eb..6dd94106 100644 --- a/mp/src/utils/vrad/leaf_ambient_lighting.h +++ b/mp/src/utils/vrad/leaf_ambient_lighting.h @@ -1,17 +1,17 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef LEAF_AMBIENT_LIGHTING_H -#define LEAF_AMBIENT_LIGHTING_H -#ifdef _WIN32 -#pragma once -#endif - - -void ComputePerLeafAmbientLighting(); - - -#endif // LEAF_AMBIENT_LIGHTING_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef LEAF_AMBIENT_LIGHTING_H +#define LEAF_AMBIENT_LIGHTING_H +#ifdef _WIN32 +#pragma once +#endif + + +void ComputePerLeafAmbientLighting(); + + +#endif // LEAF_AMBIENT_LIGHTING_H diff --git a/mp/src/utils/vrad/lightmap.cpp b/mp/src/utils/vrad/lightmap.cpp index bc3d254e..bfa4bd03 100644 --- a/mp/src/utils/vrad/lightmap.cpp +++ b/mp/src/utils/vrad/lightmap.cpp @@ -1,3577 +1,3577 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "vrad.h" -#include "lightmap.h" -#include "radial.h" -#include "mathlib/bumpvects.h" -#include "tier1/utlvector.h" -#include "vmpi.h" -#include "mathlib/anorms.h" -#include "map_utils.h" -#include "mathlib/halton.h" -#include "imagepacker.h" -#include "tier1/utlrbtree.h" -#include "tier1/utlbuffer.h" -#include "bitmap/tgawriter.h" -#include "mathlib/quantize.h" -#include "bitmap/imageformat.h" -#include "coordsize.h" - -enum -{ - AMBIENT_ONLY = 0x1, - NON_AMBIENT_ONLY = 0x2, -}; - -#define SMOOTHING_GROUP_HARD_EDGE 0xff000000 - -//==========================================================================// -// CNormalList. -//==========================================================================// - -// This class keeps a list of unique normals and provides a fast -class CNormalList -{ -public: - CNormalList(); - - // Adds the normal if unique. Otherwise, returns the normal's index into m_Normals. - int FindOrAddNormal( Vector const &vNormal ); - - -public: - - CUtlVector m_Normals; - - -private: - - // This represents a grid from (-1,-1,-1) to (1,1,1). - enum {NUM_SUBDIVS = 8}; - CUtlVector m_NormalGrid[NUM_SUBDIVS][NUM_SUBDIVS][NUM_SUBDIVS]; -}; - - -int g_iCurFace; -edgeshare_t edgeshare[MAX_MAP_EDGES]; - -Vector face_centroids[MAX_MAP_EDGES]; - -int vertexref[MAX_MAP_VERTS]; -int *vertexface[MAX_MAP_VERTS]; -faceneighbor_t faceneighbor[MAX_MAP_FACES]; - -static directlight_t *gSkyLight = NULL; -static directlight_t *gAmbient = NULL; - -//==========================================================================// -// CNormalList implementation. -//==========================================================================// - -CNormalList::CNormalList() : m_Normals( 128 ) -{ - for( int i=0; i < sizeof(m_NormalGrid)/sizeof(m_NormalGrid[0][0][0]); i++ ) - { - (&m_NormalGrid[0][0][0] + i)->SetGrowSize( 16 ); - } -} - -int CNormalList::FindOrAddNormal( Vector const &vNormal ) -{ - int gi[3]; - - // See which grid element it's in. - for( int iDim=0; iDim < 3; iDim++ ) - { - gi[iDim] = (int)( ((vNormal[iDim] + 1.0f) * 0.5f) * NUM_SUBDIVS - 0.000001f ); - gi[iDim] = min( gi[iDim], NUM_SUBDIVS ); - gi[iDim] = max( gi[iDim], 0 ); - } - - // Look for a matching vector in there. - CUtlVector *pGridElement = &m_NormalGrid[gi[0]][gi[1]][gi[2]]; - for( int i=0; i < pGridElement->Size(); i++ ) - { - int iNormal = pGridElement->Element(i); - - Vector *pVec = &m_Normals[iNormal]; - //if( pVec->DistToSqr(vNormal) < 0.00001f ) - if( *pVec == vNormal ) - return iNormal; - } - - // Ok, add a new one. - pGridElement->AddToTail( m_Normals.Size() ); - return m_Normals.AddToTail( vNormal ); -} - -// FIXME: HACK until the plane normals are made more happy -void GetBumpNormals( const float* sVect, const float* tVect, const Vector& flatNormal, - const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] ) -{ - Vector stmp( sVect[0], sVect[1], sVect[2] ); - Vector ttmp( tVect[0], tVect[1], tVect[2] ); - GetBumpNormals( stmp, ttmp, flatNormal, phongNormal, bumpNormals ); -} - -int EdgeVertex( dface_t *f, int edge ) -{ - int k; - - if (edge < 0) - edge += f->numedges; - else if (edge >= f->numedges) - edge = edge % f->numedges; - - k = dsurfedges[f->firstedge + edge]; - if (k < 0) - { - // Msg("(%d %d) ", dedges[-k].v[1], dedges[-k].v[0] ); - return dedges[-k].v[1]; - } - else - { - // Msg("(%d %d) ", dedges[k].v[0], dedges[k].v[1] ); - return dedges[k].v[0]; - } -} - - -/* - ============ - PairEdges - ============ -*/ -void PairEdges (void) -{ - int i, j, k, n, m; - dface_t *f; - int numneighbors; - int tmpneighbor[64]; - faceneighbor_t *fn; - - // count number of faces that reference each vertex - for (i=0, f = g_pFaces; inumedges ; j++) - { - // Store the count in vertexref - vertexref[EdgeVertex(f,j)]++; - } - } - - // allocate room - for (i = 0; i < numvertexes; i++) - { - // use the count from above to allocate a big enough array - vertexface[i] = ( int* )calloc( vertexref[i], sizeof( vertexface[0] ) ); - // clear the temporary data - vertexref[i] = 0; - } - - // store a list of every face that uses a particular vertex - for (i=0, f = g_pFaces ; inumedges ; j++) - { - n = EdgeVertex(f,j); - - for (k = 0; k < vertexref[n]; k++) - { - if (vertexface[n][k] == i) - break; - } - if (k >= vertexref[n]) - { - // add the face to the list - vertexface[n][k] = i; - vertexref[n]++; - } - } - } - - // calc normals and set displacement surface flag - for (i=0, f = g_pFaces; iplanenum].normal, fn->facenormal ); - - // set displacement surface flag - fn->bHasDisp = false; - if( ValidDispFace( f ) ) - { - fn->bHasDisp = true; - } - } - - // find neighbors - for (i=0, f = g_pFaces ; inormal = ( Vector* )calloc( f->numedges, sizeof( fn->normal[0] ) ); - - // look up all faces sharing vertices and add them to the list - for (j=0 ; jnumedges ; j++) - { - n = EdgeVertex(f,j); - - for (k = 0; k < vertexref[n]; k++) - { - double cos_normals_angle; - Vector *pNeighbornormal; - - // skip self - if (vertexface[n][k] == i) - continue; - - // if this face doens't have a displacement -- don't consider displacement neighbors - if( ( !fn->bHasDisp ) && ( faceneighbor[vertexface[n][k]].bHasDisp ) ) - continue; - - pNeighbornormal = &faceneighbor[vertexface[n][k]].facenormal; - cos_normals_angle = DotProduct( *pNeighbornormal, fn->facenormal ); - - // add normal if >= threshold or its a displacement surface (this is only if the original - // face is a displacement) - if ( fn->bHasDisp ) - { - // Always smooth with and against a displacement surface. - VectorAdd( fn->normal[j], *pNeighbornormal, fn->normal[j] ); - } - else - { - // No smoothing - use of method (backwards compatibility). - if ( ( f->smoothingGroups == 0 ) && ( g_pFaces[vertexface[n][k]].smoothingGroups == 0 ) ) - { - if ( cos_normals_angle >= smoothing_threshold ) - { - VectorAdd( fn->normal[j], *pNeighbornormal, fn->normal[j] ); - } - else - { - // not considered a neighbor - continue; - } - } - else - { - unsigned int smoothingGroup = ( f->smoothingGroups & g_pFaces[vertexface[n][k]].smoothingGroups ); - - // Hard edge. - if ( ( smoothingGroup & SMOOTHING_GROUP_HARD_EDGE ) != 0 ) - continue; - - if ( smoothingGroup != 0 ) - { - VectorAdd( fn->normal[j], *pNeighbornormal, fn->normal[j] ); - } - else - { - // not considered a neighbor - continue; - } - } - } - - // look to see if we've already added this one - for (m = 0; m < numneighbors; m++) - { - if (tmpneighbor[m] == vertexface[n][k]) - break; - } - - if (m >= numneighbors) - { - // add to neighbor list - tmpneighbor[m] = vertexface[n][k]; - numneighbors++; - if ( numneighbors > ARRAYSIZE(tmpneighbor) ) - { - Error("Stack overflow in neighbors\n"); - } - } - } - } - - if (numneighbors) - { - // copy over neighbor list - fn->numneighbors = numneighbors; - fn->neighbor = ( int* )calloc( numneighbors, sizeof( fn->neighbor[0] ) ); - for (m = 0; m < numneighbors; m++) - { - fn->neighbor[m] = tmpneighbor[m]; - } - } - - // fixup normals - for (j = 0; j < f->numedges; j++) - { - VectorAdd( fn->normal[j], fn->facenormal, fn->normal[j] ); - VectorNormalize( fn->normal[j] ); - } - } -} - - -void SaveVertexNormals( void ) -{ - faceneighbor_t *fn; - int i, j; - dface_t *f; - CNormalList normalList; - - g_numvertnormalindices = 0; - - for( i = 0 ;inumedges; j++ ) - { - Vector vNormal; - if( fn->normal ) - { - vNormal = fn->normal[j]; - } - else - { - // original faces don't have normals - vNormal.Init( 0, 0, 0 ); - } - - if( g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES ) - { - Error( "g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES" ); - } - - g_vertnormalindices[g_numvertnormalindices] = (unsigned short)normalList.FindOrAddNormal( vNormal ); - g_numvertnormalindices++; - } - } - - if( normalList.m_Normals.Size() > MAX_MAP_VERTNORMALS ) - { - Error( "g_numvertnormals > MAX_MAP_VERTNORMALS" ); - } - - // Copy the list of unique vert normals into g_vertnormals. - g_numvertnormals = normalList.m_Normals.Size(); - memcpy( g_vertnormals, normalList.m_Normals.Base(), sizeof(g_vertnormals[0]) * normalList.m_Normals.Size() ); -} - -/* - ================================================================= - - LIGHTMAP SAMPLE GENERATION - - ================================================================= -*/ - - -//----------------------------------------------------------------------------- -// Purpose: Spits out an error message with information about a lightinfo_t. -// Input : s - Error message string. -// l - lightmap info struct. -//----------------------------------------------------------------------------- -void ErrorLightInfo(const char *s, lightinfo_t *l) -{ - texinfo_t *tex = &texinfo[l->face->texinfo]; - winding_t *w = WindingFromFace(&g_pFaces[l->facenum], l->modelorg); - - // - // Show the face center and material name if possible. - // - if (w != NULL) - { - // Don't exit, we'll try to recover... - Vector vecCenter; - WindingCenter(w, vecCenter); -// FreeWinding(w); - - Warning("%s at (%g, %g, %g)\n\tmaterial=%s\n", s, (double)vecCenter.x, (double)vecCenter.y, (double)vecCenter.z, TexDataStringTable_GetString( dtexdata[tex->texdata].nameStringTableID ) ); - } - // - // If not, just show the material name. - // - else - { - Warning("%s at (degenerate face)\n\tmaterial=%s\n", s, TexDataStringTable_GetString( dtexdata[tex->texdata].nameStringTableID )); - } -} - - - -void CalcFaceVectors(lightinfo_t *l) -{ - texinfo_t *tex; - int i, j; - - tex = &texinfo[l->face->texinfo]; - - // move into lightinfo_t - for (i=0 ; i<2 ; i++) - { - for (j=0 ; j<3 ; j++) - { - l->worldToLuxelSpace[i][j] = tex->lightmapVecsLuxelsPerWorldUnits[i][j]; - } - } - - //Solve[ { x * w00 + y * w01 + z * w02 - s == 0, x * w10 + y * w11 + z * w12 - t == 0, A * x + B * y + C * z + D == 0 }, { x, y, z } ] - //Rule(x,( C*s*w11 - B*s*w12 + B*t*w02 - C*t*w01 + D*w02*w11 - D*w01*w12) / (+ A*w01*w12 - A*w02*w11 + B*w02*w10 - B*w00*w12 + C*w00*w11 - C*w01*w10 )), - //Rule(y,( A*s*w12 - C*s*w10 + C*t*w00 - A*t*w02 + D*w00*w12 - D*w02*w10) / (+ A*w01*w12 - A*w02*w11 + B*w02*w10 - B*w00*w12 + C*w00*w11 - C*w01*w10 )), - //Rule(z,( B*s*w10 - A*s*w11 + A*t*w01 - B*t*w00 + D*w01*w10 - D*w00*w11) / (+ A*w01*w12 - A*w02*w11 + B*w02*w10 - B*w00*w12 + C*w00*w11 - C*w01*w10 )))) - - Vector luxelSpaceCross; - - luxelSpaceCross[0] = - tex->lightmapVecsLuxelsPerWorldUnits[1][1] * tex->lightmapVecsLuxelsPerWorldUnits[0][2] - - tex->lightmapVecsLuxelsPerWorldUnits[1][2] * tex->lightmapVecsLuxelsPerWorldUnits[0][1]; - luxelSpaceCross[1] = - tex->lightmapVecsLuxelsPerWorldUnits[1][2] * tex->lightmapVecsLuxelsPerWorldUnits[0][0] - - tex->lightmapVecsLuxelsPerWorldUnits[1][0] * tex->lightmapVecsLuxelsPerWorldUnits[0][2]; - luxelSpaceCross[2] = - tex->lightmapVecsLuxelsPerWorldUnits[1][0] * tex->lightmapVecsLuxelsPerWorldUnits[0][1] - - tex->lightmapVecsLuxelsPerWorldUnits[1][1] * tex->lightmapVecsLuxelsPerWorldUnits[0][0]; - - float det = -DotProduct( l->facenormal, luxelSpaceCross ); - if ( fabs( det ) < 1.0e-20 ) - { - Warning(" warning - face vectors parallel to face normal. bad lighting will be produced\n" ); - l->luxelOrigin = vec3_origin; - } - else - { - // invert the matrix - l->luxelToWorldSpace[0][0] = (l->facenormal[2] * l->worldToLuxelSpace[1][1] - l->facenormal[1] * l->worldToLuxelSpace[1][2]) / det; - l->luxelToWorldSpace[1][0] = (l->facenormal[1] * l->worldToLuxelSpace[0][2] - l->facenormal[2] * l->worldToLuxelSpace[0][1]) / det; - l->luxelOrigin[0] = -(l->facedist * luxelSpaceCross[0]) / det; - l->luxelToWorldSpace[0][1] = (l->facenormal[0] * l->worldToLuxelSpace[1][2] - l->facenormal[2] * l->worldToLuxelSpace[1][0]) / det; - l->luxelToWorldSpace[1][1] = (l->facenormal[2] * l->worldToLuxelSpace[0][0] - l->facenormal[0] * l->worldToLuxelSpace[0][2]) / det; - l->luxelOrigin[1] = -(l->facedist * luxelSpaceCross[1]) / det; - l->luxelToWorldSpace[0][2] = (l->facenormal[1] * l->worldToLuxelSpace[1][0] - l->facenormal[0] * l->worldToLuxelSpace[1][1]) / det; - l->luxelToWorldSpace[1][2] = (l->facenormal[0] * l->worldToLuxelSpace[0][1] - l->facenormal[1] * l->worldToLuxelSpace[0][0]) / det; - l->luxelOrigin[2] = -(l->facedist * luxelSpaceCross[2]) / det; - - // adjust for luxel offset - VectorMA( l->luxelOrigin, -tex->lightmapVecsLuxelsPerWorldUnits[0][3], l->luxelToWorldSpace[0], l->luxelOrigin ); - VectorMA( l->luxelOrigin, -tex->lightmapVecsLuxelsPerWorldUnits[1][3], l->luxelToWorldSpace[1], l->luxelOrigin ); - } - // compensate for org'd bmodels - VectorAdd (l->luxelOrigin, l->modelorg, l->luxelOrigin); -} - - - -winding_t *LightmapCoordWindingForFace( lightinfo_t *l ) -{ - int i; - winding_t *w; - - w = WindingFromFace( l->face, l->modelorg ); - - for (i = 0; i < w->numpoints; i++) - { - Vector2D coord; - WorldToLuxelSpace( l, w->p[i], coord ); - w->p[i].x = coord.x; - w->p[i].y = coord.y; - w->p[i].z = 0; - } - - return w; -} - - -void WriteCoordWinding (FILE *out, lightinfo_t *l, winding_t *w, Vector& color ) -{ - int i; - Vector pos; - - fprintf (out, "%i\n", w->numpoints); - for (i=0 ; inumpoints ; i++) - { - LuxelSpaceToWorld( l, w->p[i][0], w->p[i][1], pos ); - fprintf (out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", - pos[0], - pos[1], - pos[2], - color[ 0 ] / 256, - color[ 1 ] / 256, - color[ 2 ] / 256 ); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void DumpFaces( lightinfo_t *pLightInfo, int ndxFace ) -{ - static FileHandle_t out; - - // get face data - faceneighbor_t *fn = &faceneighbor[ndxFace]; - Vector ¢roid = face_centroids[ndxFace]; - - // disable threading (not a multi-threadable function!) - ThreadLock(); - - if( !out ) - { - // open the file - out = g_pFileSystem->Open( "face.txt", "w" ); - if( !out ) - return; - } - - // - // write out face - // - for( int ndxEdge = 0; ndxEdge < pLightInfo->face->numedges; ndxEdge++ ) - { -// int edge = dsurfedges[pLightInfo->face->firstedge+ndxEdge]; - - Vector p1, p2; - VectorAdd( dvertexes[EdgeVertex( pLightInfo->face, ndxEdge )].point, pLightInfo->modelorg, p1 ); - VectorAdd( dvertexes[EdgeVertex( pLightInfo->face, ndxEdge+1 )].point, pLightInfo->modelorg, p2 ); - - Vector &n1 = fn->normal[ndxEdge]; - Vector &n2 = fn->normal[(ndxEdge+1)%pLightInfo->face->numedges]; - - CmdLib_FPrintf( out, "3\n"); - - CmdLib_FPrintf(out, "%f %f %f %f %f %f\n", p1[0], p1[1], p1[2], n1[0] * 0.5 + 0.5, n1[1] * 0.5 + 0.5, n1[2] * 0.5 + 0.5 ); - - CmdLib_FPrintf(out, "%f %f %f %f %f %f\n", p2[0], p2[1], p2[2], n2[0] * 0.5 + 0.5, n2[1] * 0.5 + 0.5, n2[2] * 0.5 + 0.5 ); - - CmdLib_FPrintf(out, "%f %f %f %f %f %f\n", centroid[0] + pLightInfo->modelorg[0], - centroid[1] + pLightInfo->modelorg[1], - centroid[2] + pLightInfo->modelorg[2], - fn->facenormal[0] * 0.5 + 0.5, - fn->facenormal[1] * 0.5 + 0.5, - fn->facenormal[2] * 0.5 + 0.5 ); - - } - - // enable threading - ThreadUnlock(); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool BuildFacesamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight ) -{ - // lightmap size - int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; - int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; - - // ratio of world area / lightmap area - texinfo_t *pTex = &texinfo[pLightInfo->face->texinfo]; - pFaceLight->worldAreaPerLuxel = 1.0 / ( sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[0], - pTex->lightmapVecsLuxelsPerWorldUnits[0] ) ) * - sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[1], - pTex->lightmapVecsLuxelsPerWorldUnits[1] ) ) ); - - // - // quickly create samples and luxels (copy over samples) - // - pFaceLight->numsamples = width * height; - pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); - if( !pFaceLight->sample ) - return false; - - pFaceLight->numluxels = width * height; - pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); - if( !pFaceLight->luxel ) - return false; - - sample_t *pSamples = pFaceLight->sample; - Vector *pLuxels = pFaceLight->luxel; - - for( int t = 0; t < height; t++ ) - { - for( int s = 0; s < width; s++ ) - { - pSamples->s = s; - pSamples->t = t; - pSamples->coord[0] = s; - pSamples->coord[1] = t; - // unused but initialized anyway - pSamples->mins[0] = s - 0.5; - pSamples->mins[1] = t - 0.5; - pSamples->maxs[0] = s + 0.5; - pSamples->maxs[1] = t + 0.5; - pSamples->area = pFaceLight->worldAreaPerLuxel; - LuxelSpaceToWorld( pLightInfo, pSamples->coord[0], pSamples->coord[1], pSamples->pos ); - VectorCopy( pSamples->pos, *pLuxels ); - - pSamples++; - pLuxels++; - } - } - - return true; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool BuildSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) -{ - // build samples for a "face" - if( pLightInfo->face->dispinfo == -1 ) - { - return BuildFacesamplesAndLuxels_DoFast( pLightInfo, pFaceLight ); - } - // build samples for a "displacement" - else - { - return StaticDispMgr()->BuildDispSamplesAndLuxels_DoFast( pLightInfo, pFaceLight, ndxFace ); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool BuildFacesamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight ) -{ - // lightmap size - int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; - int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; - - // ratio of world area / lightmap area - texinfo_t *pTex = &texinfo[pLightInfo->face->texinfo]; - pFaceLight->worldAreaPerLuxel = 1.0 / ( sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[0], - pTex->lightmapVecsLuxelsPerWorldUnits[0] ) ) * - sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[1], - pTex->lightmapVecsLuxelsPerWorldUnits[1] ) ) ); - - // allocate a large number of samples for creation -- get copied later! - CUtlVector sampleData; - sampleData.SetCount( SINGLE_BRUSH_MAP * 2 ); - sample_t *samples = sampleData.Base(); - sample_t *pSamples = samples; - - // lightmap space winding - winding_t *pLightmapWinding = LightmapCoordWindingForFace( pLightInfo ); - - // - // build vector pointing along the lightmap cutting planes - // - Vector sNorm( 1.0f, 0.0f, 0.0f ); - Vector tNorm( 0.0f, 1.0f, 0.0f ); - - // sample center offset - float sampleOffset = ( do_centersamples ) ? 0.5 : 1.0; - - // - // clip the lightmap "spaced" winding by the lightmap cutting planes - // - winding_t *pWindingT1, *pWindingT2; - winding_t *pWindingS1, *pWindingS2; - float dist; - - for( int t = 0; t < height && pLightmapWinding; t++ ) - { - dist = t + sampleOffset; - - // lop off a sample in the t dimension - // hack - need a separate epsilon for lightmap space since ON_EPSILON is for texture space - ClipWindingEpsilon( pLightmapWinding, tNorm, dist, ON_EPSILON / 16.0f, &pWindingT1, &pWindingT2 ); - - for( int s = 0; s < width && pWindingT2; s++ ) - { - dist = s + sampleOffset; - - // lop off a sample in the s dimension, and put it in ws2 - // hack - need a separate epsilon for lightmap space since ON_EPSILON is for texture space - ClipWindingEpsilon( pWindingT2, sNorm, dist, ON_EPSILON / 16.0f, &pWindingS1, &pWindingS2 ); - - // - // s2 winding is a single sample worth of winding - // - if( pWindingS2 ) - { - // save the s, t positions - pSamples->s = s; - pSamples->t = t; - - // get the lightmap space area of ws2 and convert to world area - // and find the center (then convert it to 2D) - Vector center; - pSamples->area = WindingAreaAndBalancePoint( pWindingS2, center ) * pFaceLight->worldAreaPerLuxel; - pSamples->coord[0] = center.x; - pSamples->coord[1] = center.y; - - // find winding bounds (then convert it to 2D) - Vector minbounds, maxbounds; - WindingBounds( pWindingS2, minbounds, maxbounds ); - pSamples->mins[0] = minbounds.x; - pSamples->mins[1] = minbounds.y; - pSamples->maxs[0] = maxbounds.x; - pSamples->maxs[1] = maxbounds.y; - - // convert from lightmap space to world space - LuxelSpaceToWorld( pLightInfo, pSamples->coord[0], pSamples->coord[1], pSamples->pos ); - - if (g_bDumpPatches || (do_extra && pSamples->area < pFaceLight->worldAreaPerLuxel - EQUAL_EPSILON)) - { - // - // convert the winding from lightmaps space to world for debug rendering and sub-sampling - // - Vector worldPos; - for( int ndxPt = 0; ndxPt < pWindingS2->numpoints; ndxPt++ ) - { - LuxelSpaceToWorld( pLightInfo, pWindingS2->p[ndxPt].x, pWindingS2->p[ndxPt].y, worldPos ); - VectorCopy( worldPos, pWindingS2->p[ndxPt] ); - } - pSamples->w = pWindingS2; - } - else - { - // winding isn't needed, free it. - pSamples->w = NULL; - FreeWinding( pWindingS2 ); - } - - pSamples++; - } - - // - // if winding T2 still exists free it and set it equal S1 (the rest of the row minus the sample just created) - // - if( pWindingT2 ) - { - FreeWinding( pWindingT2 ); - } - - // clip the rest of "s" - pWindingT2 = pWindingS1; - } - - // - // if the original lightmap winding exists free it and set it equal to T1 (the rest of the winding not cut into samples) - // - if( pLightmapWinding ) - { - FreeWinding( pLightmapWinding ); - } - - if( pWindingT2 ) - { - FreeWinding( pWindingT2 ); - } - - pLightmapWinding = pWindingT1; - } - - // - // copy over samples - // - pFaceLight->numsamples = pSamples - samples; - pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); - if( !pFaceLight->sample ) - return false; - - memcpy( pFaceLight->sample, samples, pFaceLight->numsamples * sizeof( *pFaceLight->sample ) ); - - // supply a default sample normal (face normal - assumed flat) - for( int ndxSample = 0; ndxSample < pFaceLight->numsamples; ndxSample++ ) - { - Assert ( VectorLength ( pLightInfo->facenormal ) > 1.0e-20); - pFaceLight->sample[ndxSample].normal = pLightInfo->facenormal; - } - - // statistics - warning?! - if( pFaceLight->numsamples == 0 ) - { - Msg( "no samples %d\n", pLightInfo->face - g_pFaces ); - } - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: Free any windings used by this facelight. It's currently assumed they're not needed again -//----------------------------------------------------------------------------- -void FreeSampleWindings( facelight_t *fl ) -{ - int i; - for (i = 0; i < fl->numsamples; i++) - { - if (fl->sample[i].w) - { - FreeWinding( fl->sample[i].w ); - fl->sample[i].w = NULL; - } - } -} - - - -//----------------------------------------------------------------------------- -// Purpose: build the sample data for each lightmapped primitive type -//----------------------------------------------------------------------------- -bool BuildSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) -{ - // build samples for a "face" - if( pLightInfo->face->dispinfo == -1 ) - { - return BuildFacesamples( pLightInfo, pFaceLight ); - } - // build samples for a "displacement" - else - { - return StaticDispMgr()->BuildDispSamples( pLightInfo, pFaceLight, ndxFace ); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool BuildFaceLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight ) -{ - // lightmap size - int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; - int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; - - // calcuate actual luxel points - pFaceLight->numluxels = width * height; - pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); - if( !pFaceLight->luxel ) - return false; - - for( int t = 0; t < height; t++ ) - { - for( int s = 0; s < width; s++ ) - { - LuxelSpaceToWorld( pLightInfo, s, t, pFaceLight->luxel[s+t*width] ); - } - } - - return true; -} - - -//----------------------------------------------------------------------------- -// Purpose: build the luxels (find the luxel centers) for each lightmapped -// primitive type -//----------------------------------------------------------------------------- -bool BuildLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) -{ - // build luxels for a "face" - if( pLightInfo->face->dispinfo == -1 ) - { - return BuildFaceLuxels( pLightInfo, pFaceLight ); - } - // build luxels for a "displacement" - else - { - return StaticDispMgr()->BuildDispLuxels( pLightInfo, pFaceLight, ndxFace ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: for each face, find the center of each luxel; for each texture -// aligned grid point, back project onto the plane and get the world -// xyz value of the sample point -// NOTE: ndxFace = facenum -//----------------------------------------------------------------------------- -void CalcPoints( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) -{ - // debugging! - if( g_bDumpPatches ) - { - DumpFaces( pLightInfo, ndxFace ); - } - - // quick and dirty! - if( do_fast ) - { - if( !BuildSamplesAndLuxels_DoFast( pLightInfo, pFaceLight, ndxFace ) ) - { - Msg( "Face %d: (Fast)Error Building Samples and Luxels\n", ndxFace ); - } - return; - } - - // build the samples - if( !BuildSamples( pLightInfo, pFaceLight, ndxFace ) ) - { - Msg( "Face %d: Error Building Samples\n", ndxFace ); - } - - // build the luxels - if( !BuildLuxels( pLightInfo, pFaceLight, ndxFace ) ) - { - Msg( "Face %d: Error Building Luxels\n", ndxFace ); - } -} - - -//============================================================== - -directlight_t *activelights; -directlight_t *freelights; - -facelight_t facelight[MAX_MAP_FACES]; -int numdlights; - -/* - ================== - FindTargetEntity - ================== -*/ -entity_t *FindTargetEntity (char *target) -{ - int i; - char *n; - - for (i=0 ; iindex = numdlights++; - - VectorCopy( origin, dl->light.origin ); - - dl->light.cluster = ClusterFromPoint(dl->light.origin); - SetDLightVis( dl, dl->light.cluster ); - - dl->facenum = -1; - - if ( bAddToList ) - { - dl->next = activelights; - activelights = dl; - } - - return dl; -} - -void AddDLightToActiveList( directlight_t *dl ) -{ - dl->next = activelights; - activelights = dl; -} - -void FreeDLights() -{ - gSkyLight = NULL; - gAmbient = NULL; - - directlight_t *pNext; - for( directlight_t *pCur=activelights; pCur; pCur=pNext ) - { - pNext = pCur->next; - free( pCur ); - } - activelights = 0; -} - - -void SetDLightVis( directlight_t *dl, int cluster ) -{ - if (dl->pvs == NULL) - { - dl->pvs = (byte *)calloc( 1, (dvis->numclusters / 8) + 1 ); - } - - GetVisCache( -1, cluster, dl->pvs ); -} - -void MergeDLightVis( directlight_t *dl, int cluster ) -{ - if (dl->pvs == NULL) - { - SetDLightVis( dl, cluster ); - } - else - { - byte pvs[MAX_MAP_CLUSTERS/8]; - GetVisCache( -1, cluster, pvs ); - - // merge both vis graphs - for (int i = 0; i < (dvis->numclusters / 8) + 1; i++) - { - dl->pvs[i] |= pvs[i]; - } - } -} - - -/* - ============= - LightForKey - ============= -*/ -int LightForKey (entity_t *ent, char *key, Vector& intensity ) -{ - char *pLight; - - pLight = ValueForKey( ent, key ); - - return LightForString( pLight, intensity ); -} - -int LightForString( char *pLight, Vector& intensity ) -{ - double r, g, b, scaler; - int argCnt; - - VectorFill( intensity, 0 ); - - // scanf into doubles, then assign, so it is vec_t size independent - r = g = b = scaler = 0; - double r_hdr,g_hdr,b_hdr,scaler_hdr; - argCnt = sscanf ( pLight, "%lf %lf %lf %lf %lf %lf %lf %lf", - &r, &g, &b, &scaler, &r_hdr,&g_hdr,&b_hdr,&scaler_hdr ); - - if (argCnt==8) // 2 4-tuples - { - if (g_bHDR) - { - r=r_hdr; - g=g_hdr; - b=b_hdr; - scaler=scaler_hdr; - } - argCnt=4; - } - - // make sure light is legal - if( r < 0.0f || g < 0.0f || b < 0.0f || scaler < 0.0f ) - { - intensity.Init( 0.0f, 0.0f, 0.0f ); - return false; - } - - intensity[0] = pow( r / 255.0, 2.2 ) * 255; // convert to linear - - switch( argCnt) - { - case 1: - // The R,G,B values are all equal. - intensity[1] = intensity[2] = intensity[0]; - break; - - case 3: - case 4: - // Save the other two G,B values. - intensity[1] = pow( g / 255.0, 2.2 ) * 255; - intensity[2] = pow( b / 255.0, 2.2 ) * 255; - - // Did we also get an "intensity" scaler value too? - if ( argCnt == 4 ) - { - // Scale the normalized 0-255 R,G,B values by the intensity scaler - VectorScale( intensity, scaler / 255.0, intensity ); - } - break; - - default: - printf("unknown light specifier type - %s\n",pLight); - return false; - } - // scale up source lights by scaling factor - VectorScale( intensity, lightscale, intensity ); - return true; -} - -//----------------------------------------------------------------------------- -// Various parsing methods -//----------------------------------------------------------------------------- - -static void ParseLightGeneric( entity_t *e, directlight_t *dl ) -{ - entity_t *e2; - char *target; - Vector dest; - - dl->light.style = (int)FloatForKey (e, "style"); - - // get intenfsity - if( g_bHDR && LightForKey( e, "_lightHDR", dl->light.intensity ) ) - { - } - else - { - LightForKey( e, "_light", dl->light.intensity ); - } - - // check angle, targets - target = ValueForKey (e, "target"); - if (target[0]) - { // point towards target - e2 = FindTargetEntity (target); - if (!e2) - Warning("WARNING: light at (%i %i %i) has missing target\n", - (int)dl->light.origin[0], (int)dl->light.origin[1], (int)dl->light.origin[2]); - else - { - GetVectorForKey (e2, "origin", dest); - VectorSubtract (dest, dl->light.origin, dl->light.normal); - VectorNormalize (dl->light.normal); - } - } - else - { - // point down angle - Vector angles; - GetVectorForKey( e, "angles", angles ); - float pitch = FloatForKey (e, "pitch"); - float angle = FloatForKey (e, "angle"); - SetupLightNormalFromProps( QAngle( angles.x, angles.y, angles.z ), angle, pitch, dl->light.normal ); - } - if ( g_bHDR ) - VectorScale( dl->light.intensity, - FloatForKeyWithDefault( e, "_lightscaleHDR", 1.0 ), - dl->light.intensity ); -} - -static void SetLightFalloffParams( entity_t * e, directlight_t * dl ) -{ - float d50=FloatForKey( e, "_fifty_percent_distance" ); - dl->m_flStartFadeDistance = 0; - dl->m_flEndFadeDistance = - 1; - dl->m_flCapDist = 1.0e22; - if ( d50 ) - { - float d0 = FloatForKey( e, "_zero_percent_distance" ); - if ( d0 < d50 ) - { - Warning( "light has _fifty_percent_distance of %f but _zero_percent_distance of %f\n", d50, d0); - d0 = 2.0 * d50; - } - float a = 0, b = 1, c = 0; - if ( ! SolveInverseQuadraticMonotonic( 0, 1.0, d50, 2.0, d0, 256.0, a, b, c )) - { - Warning( "can't solve quadratic for light %f %f\n", d50, d0 ); - } - // it it possible that the parameters couldn't be used because of enforing monoticity. If so, rescale so at - // least the 50 percent value is right -// printf("50 percent=%f 0 percent=%f\n",d50,d0); -// printf("a=%f b=%f c=%f\n",a,b,c); - float v50 = c + d50 * ( b + d50 * a ); - float scale = 2.0 / v50; - a *= scale; - b *= scale; - c *= scale; -// printf("scaled=%f a=%f b=%f c=%f\n",scale,a,b,c); -// for(float d=0;d<1000;d+=20) -// printf("at %f, %f\n",d,1.0/(c+d*(b+d*a))); - dl->light.quadratic_attn = a; - dl->light.linear_attn = b; - dl->light.constant_attn = c; - - - - if ( IntForKey(e, "_hardfalloff" ) ) - { - dl->m_flEndFadeDistance = d0; - dl->m_flStartFadeDistance = 0.75 * d0 + 0.25 * d50; // start fading 3/4 way between 50 and 0. could allow adjust - } - else - { - // now, we will find the point at which the 1/x term reaches its maximum value, and - // prevent the light from going past there. If a user specifes an extreme falloff, the - // quadratic will start making the light brighter at some distance. We handle this by - // fading it from the minimum brightess point down to zero at 10x the minimum distance - if ( fabs( a ) > 0. ) - { - float flMax = b / ( - 2.0 * a ); // where f' = 0 - if ( flMax > 0.0 ) - { - dl->m_flCapDist = flMax; - dl->m_flStartFadeDistance = flMax; - dl->m_flEndFadeDistance = 10.0 * flMax; - } - } - } - } - else - { - dl->light.constant_attn = FloatForKey (e, "_constant_attn" ); - dl->light.linear_attn = FloatForKey (e, "_linear_attn" ); - dl->light.quadratic_attn = FloatForKey (e, "_quadratic_attn" ); - - dl->light.radius = FloatForKey (e, "_distance"); - - // clamp values to >= 0 - if ( dl->light.constant_attn < EQUAL_EPSILON ) - dl->light.constant_attn = 0; - - if ( dl->light.linear_attn < EQUAL_EPSILON ) - dl->light.linear_attn = 0; - - if ( dl->light.quadratic_attn < EQUAL_EPSILON ) - dl->light.quadratic_attn = 0; - - if ( dl->light.constant_attn < EQUAL_EPSILON && dl->light.linear_attn < EQUAL_EPSILON && dl->light.quadratic_attn < EQUAL_EPSILON ) - dl->light.constant_attn = 1; - - // scale intensity for unit 100 distance - float ratio = ( dl->light.constant_attn + 100 * dl->light.linear_attn + 100 * 100 * dl->light.quadratic_attn ); - if ( ratio > 0 ) - { - VectorScale( dl->light.intensity, ratio, dl->light.intensity ); - } - } -} - -static void ParseLightSpot( entity_t* e, directlight_t* dl ) -{ - Vector dest; - GetVectorForKey (e, "origin", dest ); - dl = AllocDLight( dest, true ); - - ParseLightGeneric( e, dl ); - - dl->light.type = emit_spotlight; - - dl->light.stopdot = FloatForKey (e, "_inner_cone"); - if (!dl->light.stopdot) - dl->light.stopdot = 10; - - dl->light.stopdot2 = FloatForKey (e, "_cone"); - if (!dl->light.stopdot2) - dl->light.stopdot2 = dl->light.stopdot; - if (dl->light.stopdot2 < dl->light.stopdot) - dl->light.stopdot2 = dl->light.stopdot; - - // This is a point light if stop dots are 180... - if ((dl->light.stopdot == 180) && (dl->light.stopdot2 == 180)) - { - dl->light.stopdot = dl->light.stopdot2 = 0; - dl->light.type = emit_point; - dl->light.exponent = 0; - } - else - { - // Clamp to 90, that's all DX8 can handle! - if (dl->light.stopdot > 90) - { - Warning("WARNING: light_spot at (%i %i %i) has inner angle larger than 90 degrees! Clamping to 90...\n", - (int)dl->light.origin[0], (int)dl->light.origin[1], (int)dl->light.origin[2]); - dl->light.stopdot = 90; - } - - if (dl->light.stopdot2 > 90) - { - Warning("WARNING: light_spot at (%i %i %i) has outer angle larger than 90 degrees! Clamping to 90...\n", - (int)dl->light.origin[0], (int)dl->light.origin[1], (int)dl->light.origin[2]); - dl->light.stopdot2 = 90; - } - - dl->light.stopdot2 = (float)cos(dl->light.stopdot2/180*M_PI); - dl->light.stopdot = (float)cos(dl->light.stopdot/180*M_PI); - dl->light.exponent = FloatForKey (e, "_exponent"); - } - - SetLightFalloffParams(e,dl); -} - -// NOTE: This is just a heuristic. It traces a finite number of rays to find sky -// NOTE: Full vis is necessary to make this 100% correct. -bool CanLeafTraceToSky( int iLeaf ) -{ - // UNDONE: Really want a point inside the leaf here. Center is a guess, may not be in the leaf - // UNDONE: Clip this to each plane bounding the leaf to guarantee - Vector center = vec3_origin; - for ( int i = 0; i < 3; i++ ) - { - center[i] = ( (float)(dleafs[iLeaf].mins[i] + dleafs[iLeaf].maxs[i]) ) * 0.5f; - } - - FourVectors center4, delta; - fltx4 fractionVisible; - for ( int j = 0; j < NUMVERTEXNORMALS; j+=4 ) - { - // search back to see if we can hit a sky brush - delta.LoadAndSwizzle( g_anorms[j], g_anorms[min( j+1, NUMVERTEXNORMALS-1 )], - g_anorms[min( j+2, NUMVERTEXNORMALS-1 )], g_anorms[min( j+3, NUMVERTEXNORMALS-1 )] ); - delta *= -MAX_TRACE_LENGTH; - delta += center4; - - // return true if any hits sky - TestLine_DoesHitSky ( center4, delta, &fractionVisible ); - if ( TestSignSIMD ( CmpGtSIMD ( fractionVisible, Four_Zeros ) ) ) - return true; - } - - return false; -} - -void BuildVisForLightEnvironment( void ) -{ - // Create the vis. - for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) - { - dleafs[iLeaf].flags &= ~( LEAF_FLAGS_SKY | LEAF_FLAGS_SKY2D ); - unsigned int iFirstFace = dleafs[iLeaf].firstleafface; - for ( int iLeafFace = 0; iLeafFace < dleafs[iLeaf].numleaffaces; ++iLeafFace ) - { - unsigned int iFace = dleaffaces[iFirstFace+iLeafFace]; - - texinfo_t &tex = texinfo[g_pFaces[iFace].texinfo]; - if ( tex.flags & SURF_SKY ) - { - if ( tex.flags & SURF_SKY2D ) - { - dleafs[iLeaf].flags |= LEAF_FLAGS_SKY2D; - } - else - { - dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; - } - MergeDLightVis( gSkyLight, dleafs[iLeaf].cluster ); - MergeDLightVis( gAmbient, dleafs[iLeaf].cluster ); - break; - } - } - } - - // Second pass to set flags on leaves that don't contain sky, but touch leaves that - // contain sky. - byte pvs[MAX_MAP_CLUSTERS / 8]; - - int nLeafBytes = (numleafs >> 3) + 1; - unsigned char *pLeafBits = (unsigned char *)stackalloc( nLeafBytes * sizeof(unsigned char) ); - unsigned char *pLeaf2DBits = (unsigned char *)stackalloc( nLeafBytes * sizeof(unsigned char) ); - memset( pLeafBits, 0, nLeafBytes ); - memset( pLeaf2DBits, 0, nLeafBytes ); - - for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) - { - // If this leaf has light (3d skybox) in it, then don't bother - if ( dleafs[iLeaf].flags & LEAF_FLAGS_SKY ) - continue; - - // Don't bother with this leaf if it's solid - if ( dleafs[iLeaf].contents & CONTENTS_SOLID ) - continue; - - // See what other leaves are visible from this leaf - GetVisCache( -1, dleafs[iLeaf].cluster, pvs ); - - // Now check out all other leaves - int nByte = iLeaf >> 3; - int nBit = 1 << ( iLeaf & 0x7 ); - for ( int iLeaf2 = 0; iLeaf2 < numleafs; ++iLeaf2 ) - { - if ( iLeaf2 == iLeaf ) - continue; - - if ( !(dleafs[iLeaf2].flags & ( LEAF_FLAGS_SKY | LEAF_FLAGS_SKY2D ) ) ) - continue; - - // Can this leaf see into the leaf with the sky in it? - if ( !PVSCheck( pvs, dleafs[iLeaf2].cluster ) ) - continue; - - if ( dleafs[iLeaf2].flags & LEAF_FLAGS_SKY2D ) - { - pLeaf2DBits[ nByte ] |= nBit; - } - if ( dleafs[iLeaf2].flags & LEAF_FLAGS_SKY ) - { - pLeafBits[ nByte ] |= nBit; - - // As soon as we know this leaf needs to draw the 3d skybox, we're done - break; - } - } - } - - // Must set the bits in a separate pass so as to not flood-fill LEAF_FLAGS_SKY everywhere - // pLeafbits is a bit array of all leaves that need to be marked as seeing sky - for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) - { - // If this leaf has light (3d skybox) in it, then don't bother - if ( dleafs[iLeaf].flags & LEAF_FLAGS_SKY ) - continue; - - // Don't bother with this leaf if it's solid - if ( dleafs[iLeaf].contents & CONTENTS_SOLID ) - continue; - - // Check to see if this is a 2D skybox leaf - if ( pLeaf2DBits[ iLeaf >> 3 ] & (1 << ( iLeaf & 0x7 )) ) - { - dleafs[iLeaf].flags |= LEAF_FLAGS_SKY2D; - } - - // If this is a 3D skybox leaf, then we don't care if it was previously a 2D skybox leaf - if ( pLeafBits[ iLeaf >> 3 ] & (1 << ( iLeaf & 0x7 )) ) - { - dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; - dleafs[iLeaf].flags &= ~LEAF_FLAGS_SKY2D; - } - else - { - // if radial vis was used on this leaf some of the portals leading - // to sky may have been culled. Try tracing to find sky. - if ( dleafs[iLeaf].flags & LEAF_FLAGS_RADIAL ) - { - if ( CanLeafTraceToSky(iLeaf) ) - { - // FIXME: Should make a version that checks if we hit 2D skyboxes.. oh well. - dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; - } - } - } - } -} - -static char *ValueForKeyWithDefault (entity_t *ent, char *key, char *default_value = NULL) -{ - epair_t *ep; - - for (ep=ent->epairs ; ep ; ep=ep->next) - if (!strcmp (ep->key, key) ) - return ep->value; - return default_value; -} - -static void ParseLightEnvironment( entity_t* e, directlight_t* dl ) -{ - Vector dest; - GetVectorForKey (e, "origin", dest ); - dl = AllocDLight( dest, false ); - - ParseLightGeneric( e, dl ); - - char *angle_str=ValueForKeyWithDefault( e, "SunSpreadAngle" ); - if (angle_str) - { - g_SunAngularExtent=atof(angle_str); - g_SunAngularExtent=sin((M_PI/180.0)*g_SunAngularExtent); - printf("sun extent from map=%f\n",g_SunAngularExtent); - } - if ( !gSkyLight ) - { - // Sky light. - gSkyLight = dl; - dl->light.type = emit_skylight; - - // Sky ambient light. - gAmbient = AllocDLight( dl->light.origin, false ); - gAmbient->light.type = emit_skyambient; - if( g_bHDR && LightForKey( e, "_ambientHDR", gAmbient->light.intensity ) ) - { - // we have a valid HDR ambient light value - } - else if ( !LightForKey( e, "_ambient", gAmbient->light.intensity ) ) - { - VectorScale( dl->light.intensity, 0.5, gAmbient->light.intensity ); - } - if ( g_bHDR ) - { - VectorScale( gAmbient->light.intensity, - FloatForKeyWithDefault( e, "_AmbientScaleHDR", 1.0 ), - gAmbient->light.intensity ); - } - - BuildVisForLightEnvironment(); - - // Add sky and sky ambient lights to the list. - AddDLightToActiveList( gSkyLight ); - AddDLightToActiveList( gAmbient ); - } -} - -static void ParseLightPoint( entity_t* e, directlight_t* dl ) -{ - Vector dest; - GetVectorForKey (e, "origin", dest ); - dl = AllocDLight( dest, true ); - - ParseLightGeneric( e, dl ); - - dl->light.type = emit_point; - - SetLightFalloffParams(e,dl); -} - -/* - ============= - CreateDirectLights - ============= -*/ -#define DIRECT_SCALE (100.0*100.0) -void CreateDirectLights (void) -{ - unsigned i; - CPatch *p = NULL; - directlight_t *dl = NULL; - entity_t *e = NULL; - char *name; - Vector dest; - - numdlights = 0; - - FreeDLights(); - - // - // surfaces - // - unsigned int uiPatchCount = g_Patches.Count(); - for (i=0; i< uiPatchCount; i++) - { - p = &g_Patches.Element( i ); - - // skip parent patches - if (p->child1 != g_Patches.InvalidIndex() ) - continue; - - if (p->basearea < 1e-6) - continue; - - if( VectorAvg( p->baselight ) >= dlight_threshold ) - { - dl = AllocDLight( p->origin, true ); - - dl->light.type = emit_surface; - VectorCopy (p->normal, dl->light.normal); - Assert( VectorLength( p->normal ) > 1.0e-20 ); - // scale intensity by number of texture instances - VectorScale( p->baselight, lightscale * p->area * p->scale[0] * p->scale[1] / p->basearea, dl->light.intensity ); - - // scale to a range that results in actual light - VectorScale( dl->light.intensity, DIRECT_SCALE, dl->light.intensity ); - } - } - - // - // entities - // - for (i=0 ; i<(unsigned)num_entities ; i++) - { - e = &entities[i]; - name = ValueForKey (e, "classname"); - if (strncmp (name, "light", 5)) - continue; - - // Light_dynamic is actually a real entity; not to be included here... - if (!strcmp (name, "light_dynamic")) - continue; - - if (!strcmp (name, "light_spot")) - { - ParseLightSpot( e, dl ); - } - else if (!strcmp(name, "light_environment")) - { - ParseLightEnvironment( e, dl ); - } - else if (!strcmp(name, "light")) - { - ParseLightPoint( e, dl ); - } - else - { - qprintf( "unsupported light entity: \"%s\"\n", name ); - } - } - - qprintf ("%i direct lights\n", numdlights); - // exit(1); -} - -/* - ============= - ExportDirectLightsToWorldLights - ============= -*/ - -void ExportDirectLightsToWorldLights() -{ - directlight_t *dl; - - // In case the level has already been VRADed. - *pNumworldlights = 0; - - for (dl = activelights; dl != NULL; dl = dl->next ) - { - dworldlight_t *wl = &dworldlights[(*pNumworldlights)++]; - - if (*pNumworldlights > MAX_MAP_WORLDLIGHTS) - { - Error("too many lights %d / %d\n", *pNumworldlights, MAX_MAP_WORLDLIGHTS ); - } - - wl->cluster = dl->light.cluster; - wl->type = dl->light.type; - wl->style = dl->light.style; - VectorCopy( dl->light.origin, wl->origin ); - // FIXME: why does vrad want 0 to 255 and not 0 to 1?? - VectorScale( dl->light.intensity, (1.0 / 255.0), wl->intensity ); - VectorCopy( dl->light.normal, wl->normal ); - wl->stopdot = dl->light.stopdot; - wl->stopdot2 = dl->light.stopdot2; - wl->exponent = dl->light.exponent; - wl->radius = dl->light.radius; - wl->constant_attn = dl->light.constant_attn; - wl->linear_attn = dl->light.linear_attn; - wl->quadratic_attn = dl->light.quadratic_attn; - wl->flags = 0; - } -} - -/* - ============= - GatherSampleLight - ============= -*/ -#define NORMALFORMFACTOR 40.156979 // accumuated dot products for hemisphere - -#define CONSTANT_DOT (.7/2) - -#define NSAMPLES_SUN_AREA_LIGHT 30 // number of samples to take for an - // non-point sun light - -// Helper function - gathers light from sun (emit_skylight) -void GatherSampleSkyLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, - FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, - int nLFlags, int static_prop_index_to_ignore, - float flEpsilon ) -{ - bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; - bool force_fast = ( nLFlags & GATHERLFLAGS_FORCE_FAST ) != 0; - - fltx4 dot; - - if ( bIgnoreNormals ) - dot = ReplicateX4( CONSTANT_DOT ); - else - dot = NegSIMD( pNormals[0] * dl->light.normal ); - - dot = MaxSIMD( dot, Four_Zeros ); - int zeroMask = TestSignSIMD ( CmpEqSIMD( dot, Four_Zeros ) ); - if (zeroMask == 0xF) - return; - - int nsamples = 1; - if ( g_SunAngularExtent > 0.0f ) - { - nsamples = NSAMPLES_SUN_AREA_LIGHT; - if ( do_fast || force_fast ) - nsamples /= 4; - } - - fltx4 totalFractionVisible = Four_Zeros; - fltx4 fractionVisible = Four_Zeros; - - DirectionalSampler_t sampler; - - for ( int d = 0; d < nsamples; d++ ) - { - // determine visibility of skylight - // serach back to see if we can hit a sky brush - Vector delta; - VectorScale( dl->light.normal, -MAX_TRACE_LENGTH, delta ); - if ( d ) - { - // jitter light source location - Vector ofs = sampler.NextValue(); - ofs *= MAX_TRACE_LENGTH * g_SunAngularExtent; - delta += ofs; - } - FourVectors delta4; - delta4.DuplicateVector ( delta ); - delta4 += pos; - - TestLine_DoesHitSky ( pos, delta4, &fractionVisible, true, static_prop_index_to_ignore ); - - totalFractionVisible = AddSIMD ( totalFractionVisible, fractionVisible ); - } - - fltx4 seeAmount = MulSIMD ( totalFractionVisible, ReplicateX4 ( 1.0f / nsamples ) ); - out.m_flDot[0] = MulSIMD ( dot, seeAmount ); - out.m_flFalloff = Four_Ones; - out.m_flSunAmount = MulSIMD ( seeAmount, ReplicateX4( 10000.0f ) ); - for ( int i = 1; i < normalCount; i++ ) - { - if ( bIgnoreNormals ) - out.m_flDot[i] = ReplicateX4 ( CONSTANT_DOT ); - else - { - out.m_flDot[i] = NegSIMD( pNormals[i] * dl->light.normal ); - out.m_flDot[i] = MulSIMD( out.m_flDot[i], seeAmount ); - } - } -} - -// Helper function - gathers light from ambient sky light -void GatherSampleAmbientSkySSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, - FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, - int nLFlags, int static_prop_index_to_ignore, - float flEpsilon ) -{ - - bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; - bool force_fast = ( nLFlags & GATHERLFLAGS_FORCE_FAST ) != 0; - - fltx4 sumdot = Four_Zeros; - fltx4 ambient_intensity[NUM_BUMP_VECTS+1]; - fltx4 possibleHitCount[NUM_BUMP_VECTS+1]; - fltx4 dots[NUM_BUMP_VECTS+1]; - - for ( int i = 0; i < normalCount; i++ ) - { - ambient_intensity[i] = Four_Zeros; - possibleHitCount[i] = Four_Zeros; - } - - DirectionalSampler_t sampler; - int nsky_samples = NUMVERTEXNORMALS; - if (do_fast || force_fast ) - nsky_samples /= 4; - else - nsky_samples *= g_flSkySampleScale; - - for (int j = 0; j < nsky_samples; j++) - { - FourVectors anorm; - anorm.DuplicateVector( sampler.NextValue() ); - - if ( bIgnoreNormals ) - dots[0] = ReplicateX4( CONSTANT_DOT ); - else - dots[0] = NegSIMD( pNormals[0] * anorm ); - - fltx4 validity = CmpGtSIMD( dots[0], ReplicateX4( EQUAL_EPSILON ) ); - - // No possibility of anybody getting lit - if ( !TestSignSIMD( validity ) ) - continue; - - dots[0] = AndSIMD( validity, dots[0] ); - sumdot = AddSIMD( dots[0], sumdot ); - possibleHitCount[0] = AddSIMD( AndSIMD( validity, Four_Ones ), possibleHitCount[0] ); - - for ( int i = 1; i < normalCount; i++ ) - { - if ( bIgnoreNormals ) - dots[i] = ReplicateX4( CONSTANT_DOT ); - else - dots[i] = NegSIMD( pNormals[i] * anorm ); - fltx4 validity2 = CmpGtSIMD( dots[i], ReplicateX4 ( EQUAL_EPSILON ) ); - dots[i] = AndSIMD( validity2, dots[i] ); - possibleHitCount[i] = AddSIMD( AndSIMD( AndSIMD( validity, validity2 ), Four_Ones ), possibleHitCount[i] ); - } - - // search back to see if we can hit a sky brush - FourVectors delta = anorm; - delta *= -MAX_TRACE_LENGTH; - delta += pos; - FourVectors surfacePos = pos; - FourVectors offset = anorm; - offset *= -flEpsilon; - surfacePos -= offset; - - fltx4 fractionVisible = Four_Ones; - TestLine_DoesHitSky( surfacePos, delta, &fractionVisible, true, static_prop_index_to_ignore ); - for ( int i = 0; i < normalCount; i++ ) - { - fltx4 addedAmount = MulSIMD( fractionVisible, dots[i] ); - ambient_intensity[i] = AddSIMD( ambient_intensity[i], addedAmount ); - } - - } - - out.m_flFalloff = Four_Ones; - for ( int i = 0; i < normalCount; i++ ) - { - // now scale out the missing parts of the hemisphere of this bump basis vector - fltx4 factor = ReciprocalSIMD( possibleHitCount[0] ); - factor = MulSIMD( factor, possibleHitCount[i] ); - out.m_flDot[i] = MulSIMD( factor, sumdot ); - out.m_flDot[i] = ReciprocalSIMD( out.m_flDot[i] ); - out.m_flDot[i] = MulSIMD( ambient_intensity[i], out.m_flDot[i] ); - } - -} - -// Helper function - gathers light from area lights, spot lights, and point lights -void GatherSampleStandardLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, - FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, - int nLFlags, int static_prop_index_to_ignore, - float flEpsilon ) -{ - bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; - - FourVectors src; - src.DuplicateVector( vec3_origin ); - - if (dl->facenum == -1) - { - src.DuplicateVector( dl->light.origin ); - } - - // Find light vector - FourVectors delta; - delta = src; - delta -= pos; - fltx4 dist2 = delta.length2(); - fltx4 rpcDist = ReciprocalSqrtSIMD( dist2 ); - delta *= rpcDist; - fltx4 dist = SqrtEstSIMD( dist2 );//delta.VectorNormalize(); - - // Compute dot - fltx4 dot = ReplicateX4( (float) CONSTANT_DOT ); - if ( !bIgnoreNormals ) - dot = delta * pNormals[0]; - dot = MaxSIMD( Four_Zeros, dot ); - - // Affix dot to zero if past fade distz - bool bHasHardFalloff = ( dl->m_flEndFadeDistance > dl->m_flStartFadeDistance ); - if ( bHasHardFalloff ) - { - fltx4 notPastFadeDist = CmpLeSIMD ( dist, ReplicateX4 ( dl->m_flEndFadeDistance ) ); - dot = AndSIMD( dot, notPastFadeDist ); // dot = 0 if past fade distance - if ( !TestSignSIMD ( notPastFadeDist ) ) - return; - } - - dist = MaxSIMD( dist, Four_Ones ); - fltx4 falloffEvalDist = MinSIMD( dist, ReplicateX4( dl->m_flCapDist ) ); - - fltx4 constant, linear, quadratic; - fltx4 dot2, inCone, inFringe, mult; - FourVectors offset; - - switch (dl->light.type) - { - case emit_point: - constant = ReplicateX4( dl->light.constant_attn ); - linear = ReplicateX4( dl->light.linear_attn ); - quadratic = ReplicateX4( dl->light.quadratic_attn ); - - out.m_flFalloff = MulSIMD( falloffEvalDist, falloffEvalDist ); - out.m_flFalloff = MulSIMD( out.m_flFalloff, quadratic ); - out.m_flFalloff = AddSIMD( out.m_flFalloff, MulSIMD( linear, falloffEvalDist ) ); - out.m_flFalloff = AddSIMD( out.m_flFalloff, constant ); - out.m_flFalloff = ReciprocalSIMD( out.m_flFalloff ); - break; - - case emit_surface: - dot2 = delta * dl->light.normal; - dot2 = NegSIMD( dot2 ); - - // Light behind surface yields zero dot - dot2 = MaxSIMD( Four_Zeros, dot2 ); - if ( TestSignSIMD( CmpEqSIMD( Four_Zeros, dot ) ) == 0xF ) - return; - - out.m_flFalloff = ReciprocalSIMD ( dist2 ); - out.m_flFalloff = MulSIMD( out.m_flFalloff, dot2 ); - - // move the endpoint away from the surface by epsilon to prevent hitting the surface with the trace - offset.DuplicateVector ( dl->light.normal ); - offset *= DIST_EPSILON; - src += offset; - break; - - case emit_spotlight: - dot2 = delta * dl->light.normal; - dot2 = NegSIMD( dot2 ); - - // Affix dot2 to zero if outside light cone - inCone = CmpGtSIMD( dot2, ReplicateX4( dl->light.stopdot2 ) ); - if ( !TestSignSIMD ( inCone ) ) - return; - dot = AndSIMD( inCone, dot ); - - constant = ReplicateX4( dl->light.constant_attn ); - linear = ReplicateX4( dl->light.linear_attn ); - quadratic = ReplicateX4( dl->light.quadratic_attn ); - - out.m_flFalloff = MulSIMD( falloffEvalDist, falloffEvalDist ); - out.m_flFalloff = MulSIMD( out.m_flFalloff, quadratic ); - out.m_flFalloff = AddSIMD( out.m_flFalloff, MulSIMD( linear, falloffEvalDist ) ); - out.m_flFalloff = AddSIMD( out.m_flFalloff, constant ); - out.m_flFalloff = ReciprocalSIMD( out.m_flFalloff ); - out.m_flFalloff = MulSIMD( out.m_flFalloff, dot2 ); - - // outside the inner cone - inFringe = CmpLeSIMD( dot2, ReplicateX4( dl->light.stopdot ) ); - mult = ReplicateX4( dl->light.stopdot - dl->light.stopdot2 ); - mult = ReciprocalSIMD( mult ); - mult = MulSIMD( mult, SubSIMD( dot2, ReplicateX4( dl->light.stopdot2 ) ) ); - mult = MinSIMD( mult, Four_Ones ); - mult = MaxSIMD( mult, Four_Zeros ); - - // pow is fixed point, so this isn't the most accurate, but it doesn't need to be - if ( (dl->light.exponent != 0.0f ) && ( dl->light.exponent != 1.0f ) ) - mult = PowSIMD( mult, dl->light.exponent ); - - // if not in between inner and outer cones, mult by 1 - mult = AndSIMD( inFringe, mult ); - mult = AddSIMD( mult, AndNotSIMD( inFringe, Four_Ones ) ); - out.m_flFalloff = MulSIMD( mult, out.m_flFalloff ); - break; - - } - - // we may be in the fade region - modulate lighting by the fade curve - //float t = ( dist - dl->m_flStartFadeDistance ) / - // ( dl->m_flEndFadeDistance - dl->m_flStartFadeDistance ); - if ( bHasHardFalloff ) - { - fltx4 t = ReplicateX4( dl->m_flEndFadeDistance - dl->m_flStartFadeDistance ); - t = ReciprocalSIMD( t ); - t = MulSIMD( t, SubSIMD( dist, ReplicateX4( dl->m_flStartFadeDistance ) ) ); - - // clamp t to [0...1] - t = MinSIMD( t, Four_Ones ); - t = MaxSIMD( t, Four_Zeros ); - t = SubSIMD( Four_Ones, t ); - - // Using QuinticInterpolatingPolynomial, SSE-ified - // t * t * t *( t * ( t* 6.0 - 15.0 ) + 10.0 ) - mult = SubSIMD( MulSIMD( ReplicateX4( 6.0f ), t ), ReplicateX4( 15.0f ) ); - mult = AddSIMD( MulSIMD( mult, t ), ReplicateX4( 10.0f ) ); - mult = MulSIMD( MulSIMD( t, t), mult ); - mult = MulSIMD( t, mult ); - out.m_flFalloff = MulSIMD( mult, out.m_flFalloff ); - } - - // Raytrace for visibility function - fltx4 fractionVisible = Four_Ones; - TestLine( pos, src, &fractionVisible, static_prop_index_to_ignore); - dot = MulSIMD( fractionVisible, dot ); - out.m_flDot[0] = dot; - - for ( int i = 1; i < normalCount; i++ ) - { - if ( bIgnoreNormals ) - out.m_flDot[i] = ReplicateX4( (float) CONSTANT_DOT ); - else - { - out.m_flDot[i] = pNormals[i] * delta; - out.m_flDot[i] = MaxSIMD( Four_Zeros, out.m_flDot[i] ); - } - } -} - -// returns dot product with normal and delta -// dl - light -// pos - position of sample -// normal - surface normal of sample -// out.m_flDot[] - returned dot products with light vector and each normal -// out.m_flFalloff - amount of light falloff -void GatherSampleLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, - FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, - int nLFlags, - int static_prop_index_to_ignore, - float flEpsilon ) -{ - for ( int b = 0; b < normalCount; b++ ) - out.m_flDot[b] = Four_Zeros; - out.m_flFalloff = Four_Zeros; - out.m_flSunAmount = Four_Zeros; - Assert( normalCount <= (NUM_BUMP_VECTS+1) ); - - // skylights work fundamentally differently than normal lights - switch( dl->light.type ) - { - case emit_skylight: - GatherSampleSkyLightSSE( out, dl, facenum, pos, pNormals, normalCount, - iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); - break; - case emit_skyambient: - GatherSampleAmbientSkySSE( out, dl, facenum, pos, pNormals, normalCount, - iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); - break; - case emit_point: - case emit_surface: - case emit_spotlight: - GatherSampleStandardLightSSE( out, dl, facenum, pos, pNormals, normalCount, - iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); - break; - default: - Error ("Bad dl->light.type"); - return; - } - - // NOTE: Notice here that if the light is on the back side of the face - // (tested by checking the dot product of the face normal and the light position) - // we don't want it to contribute to *any* of the bumped lightmaps. It glows - // in disturbing ways if we don't do this. - out.m_flDot[0] = MaxSIMD ( out.m_flDot[0], Four_Zeros ); - fltx4 notZero = CmpGtSIMD( out.m_flDot[0], Four_Zeros ); - for ( int n = 1; n < normalCount; n++ ) - { - out.m_flDot[n] = MaxSIMD( out.m_flDot[n], Four_Zeros ); - out.m_flDot[n] = AndSIMD( out.m_flDot[n], notZero ); - } - -} - -/* - ============= - AddSampleToPatch - - Take the sample's collected light and - add it back into the apropriate patch - for the radiosity pass. - ============= -*/ -void AddSampleToPatch (sample_t *s, LightingValue_t& light, int facenum) -{ - CPatch *patch; - Vector mins, maxs; - int i; - - if (numbounce == 0) - return; - if( VectorAvg( light.m_vecLighting ) < 1) - return; - - // - // fixed the sample position and normal -- need to find the equiv pos, etc to set up - // patches - // - if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) - return; - - float radius = sqrt( s->area ) / 2.0; - - CPatch *pNextPatch = NULL; - for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) - { - // next patch - pNextPatch = NULL; - if( patch->ndxNext != g_Patches.InvalidIndex() ) - { - pNextPatch = &g_Patches.Element( patch->ndxNext ); - } - - if (patch->sky) - continue; - - // skip patches with children - if ( patch->child1 != g_Patches.InvalidIndex() ) - continue; - - // see if the point is in this patch (roughly) - WindingBounds (patch->winding, mins, maxs); - - for (i=0 ; i<3 ; i++) - { - if (mins[i] > s->pos[i] + radius) - goto nextpatch; - if (maxs[i] < s->pos[i] - radius) - goto nextpatch; - } - - // add the sample to the patch - patch->samplearea += s->area; - VectorMA( patch->samplelight, s->area, light.m_vecLighting, patch->samplelight ); - - nextpatch:; - } - // don't worry if some samples don't find a patch -} - - -void GetPhongNormal( int facenum, Vector const& spot, Vector& phongnormal ) -{ - int j; - dface_t *f = &g_pFaces[facenum]; -// dplane_t *p = &dplanes[f->planenum]; - Vector facenormal, vspot; - - VectorCopy( dplanes[f->planenum].normal, facenormal ); - VectorCopy( facenormal, phongnormal ); - - if ( smoothing_threshold != 1 ) - { - faceneighbor_t *fn = &faceneighbor[facenum]; - - // Calculate modified point normal for surface - // Use the edge normals iff they are defined. Bend the surface towards the edge normal(s) - // Crude first attempt: find nearest edge normal and do a simple interpolation with facenormal. - // Second attempt: find edge points+center that bound the point and do a three-point triangulation(baricentric) - // Better third attempt: generate the point normals for all vertices and do baricentric triangulation. - - for (j=0 ; jnumedges ; j++) - { - Vector v1, v2; - //int e = dsurfedges[f->firstedge + j]; - //int e1 = dsurfedges[f->firstedge + ((j+f->numedges-1)%f->numedges)]; - //int e2 = dsurfedges[f->firstedge + ((j+1)%f->numedges)]; - - //edgeshare_t *es = &edgeshare[abs(e)]; - //edgeshare_t *es1 = &edgeshare[abs(e1)]; - //edgeshare_t *es2 = &edgeshare[abs(e2)]; - // dface_t *f2; - float a1, a2, aa, bb, ab; - int vert1, vert2; - - Vector& n1 = fn->normal[j]; - Vector& n2 = fn->normal[(j+1)%f->numedges]; - - /* - if (VectorCompare( n1, fn->facenormal ) - && VectorCompare( n2, fn->facenormal) ) - continue; - */ - - vert1 = EdgeVertex( f, j ); - vert2 = EdgeVertex( f, j+1 ); - - Vector& p1 = dvertexes[vert1].point; - Vector& p2 = dvertexes[vert2].point; - - // Build vectors from the middle of the face to the edge vertexes and the sample pos. - VectorSubtract( p1, face_centroids[facenum], v1 ); - VectorSubtract( p2, face_centroids[facenum], v2 ); - VectorSubtract( spot, face_centroids[facenum], vspot ); - aa = DotProduct( v1, v1 ); - bb = DotProduct( v2, v2 ); - ab = DotProduct( v1, v2 ); - a1 = (bb * DotProduct( v1, vspot ) - ab * DotProduct( vspot, v2 )) / (aa * bb - ab * ab); - a2 = (DotProduct( vspot, v2 ) - a1 * ab) / bb; - - // Test center to sample vector for inclusion between center to vertex vectors (Use dot product of vectors) - if ( a1 >= 0.0 && a2 >= 0.0) - { - // calculate distance from edge to pos - Vector temp; - float scale; - - // Interpolate between the center and edge normals based on sample position - scale = 1.0 - a1 - a2; - VectorScale( fn->facenormal, scale, phongnormal ); - VectorScale( n1, a1, temp ); - VectorAdd( phongnormal, temp, phongnormal ); - VectorScale( n2, a2, temp ); - VectorAdd( phongnormal, temp, phongnormal ); - Assert( VectorLength( phongnormal ) > 1.0e-20 ); - VectorNormalize( phongnormal ); - - /* - if (a1 > 1 || a2 > 1 || a1 + a2 > 1) - { - Msg("\n%.2f %.2f\n", a1, a2 ); - Msg("%.2f %.2f %.2f\n", v1[0], v1[1], v1[2] ); - Msg("%.2f %.2f %.2f\n", v2[0], v2[1], v2[2] ); - Msg("%.2f %.2f %.2f\n", vspot[0], vspot[1], vspot[2] ); - exit(1); - - a1 = 0; - } - */ - /* - phongnormal[0] = (((j + 1) & 4) != 0) * 255; - phongnormal[1] = (((j + 1) & 2) != 0) * 255; - phongnormal[2] = (((j + 1) & 1) != 0) * 255; - */ - return; - } - } - } -} - -void GetPhongNormal( int facenum, FourVectors const& spot, FourVectors& phongnormal ) -{ - int j; - dface_t *f = &g_pFaces[facenum]; - // dplane_t *p = &dplanes[f->planenum]; - Vector facenormal; - FourVectors vspot; - - VectorCopy( dplanes[f->planenum].normal, facenormal ); - phongnormal.DuplicateVector( facenormal ); - - FourVectors faceCentroid; - faceCentroid.DuplicateVector( face_centroids[facenum] ); - - if ( smoothing_threshold != 1 ) - { - faceneighbor_t *fn = &faceneighbor[facenum]; - - // Calculate modified point normal for surface - // Use the edge normals iff they are defined. Bend the surface towards the edge normal(s) - // Crude first attempt: find nearest edge normal and do a simple interpolation with facenormal. - // Second attempt: find edge points+center that bound the point and do a three-point triangulation(baricentric) - // Better third attempt: generate the point normals for all vertices and do baricentric triangulation. - - for ( j = 0; j < f->numedges; ++j ) - { - Vector v1, v2; - fltx4 a1, a2; - float aa, bb, ab; - int vert1, vert2; - - Vector& n1 = fn->normal[j]; - Vector& n2 = fn->normal[(j+1)%f->numedges]; - - vert1 = EdgeVertex( f, j ); - vert2 = EdgeVertex( f, j+1 ); - - Vector& p1 = dvertexes[vert1].point; - Vector& p2 = dvertexes[vert2].point; - - // Build vectors from the middle of the face to the edge vertexes and the sample pos. - VectorSubtract( p1, face_centroids[facenum], v1 ); - VectorSubtract( p2, face_centroids[facenum], v2 ); - //VectorSubtract( spot, face_centroids[facenum], vspot ); - vspot = spot; - vspot -= faceCentroid; - aa = DotProduct( v1, v1 ); - bb = DotProduct( v2, v2 ); - ab = DotProduct( v1, v2 ); - //a1 = (bb * DotProduct( v1, vspot ) - ab * DotProduct( vspot, v2 )) / (aa * bb - ab * ab); - a1 = ReciprocalSIMD( ReplicateX4( aa * bb - ab * ab ) ); - a1 = MulSIMD( a1, SubSIMD( MulSIMD( ReplicateX4( bb ), vspot * v1 ), MulSIMD( ReplicateX4( ab ), vspot * v2 ) ) ); - //a2 = (DotProduct( vspot, v2 ) - a1 * ab) / bb; - a2 = ReciprocalSIMD( ReplicateX4( bb ) ); - a2 = MulSIMD( a2, SubSIMD( vspot * v2, MulSIMD( a1, ReplicateX4( ab ) ) ) ); - - fltx4 resultMask = AndSIMD( CmpGeSIMD( a1, Four_Zeros ), CmpGeSIMD( a2, Four_Zeros ) ); - - if ( !TestSignSIMD( resultMask ) ) - continue; - - // Store the old phong normal to avoid overwriting already computed phong normals - FourVectors oldPhongNormal = phongnormal; - - // calculate distance from edge to pos - FourVectors temp; - fltx4 scale; - - // Interpolate between the center and edge normals based on sample position - scale = SubSIMD( SubSIMD( Four_Ones, a1 ), a2 ); - phongnormal.DuplicateVector( fn->facenormal ); - phongnormal *= scale; - temp.DuplicateVector( n1 ); - temp *= a1; - phongnormal += temp; - temp.DuplicateVector( n2 ); - temp *= a2; - phongnormal += temp; - - // restore the old phong normals - phongnormal.x = AddSIMD( AndSIMD( resultMask, phongnormal.x ), AndNotSIMD( resultMask, oldPhongNormal.x ) ); - phongnormal.y = AddSIMD( AndSIMD( resultMask, phongnormal.y ), AndNotSIMD( resultMask, oldPhongNormal.y ) ); - phongnormal.z = AddSIMD( AndSIMD( resultMask, phongnormal.z ), AndNotSIMD( resultMask, oldPhongNormal.z ) ); - } - - phongnormal.VectorNormalize(); - } -} - - - -int GetVisCache( int lastoffset, int cluster, byte *pvs ) -{ - // get the PVS for the pos to limit the number of checks - if ( !visdatasize ) - { - memset (pvs, 255, (dvis->numclusters+7)/8 ); - lastoffset = -1; - } - else - { - if (cluster < 0) - { - // Error, point embedded in wall - // sampled[0][1] = 255; - memset (pvs, 255, (dvis->numclusters+7)/8 ); - lastoffset = -1; - } - else - { - int thisoffset = dvis->bitofs[ cluster ][DVIS_PVS]; - if ( thisoffset != lastoffset ) - { - if ( thisoffset == -1 ) - { - Error ("visofs == -1"); - } - - DecompressVis (&dvisdata[thisoffset], pvs); - } - lastoffset = thisoffset; - } - } - return lastoffset; -} - - -void BuildPatchLights( int facenum ); - -void DumpSamples( int ndxFace, facelight_t *pFaceLight ) -{ - ThreadLock(); - - dface_t *pFace = &g_pFaces[ndxFace]; - if( pFace ) - { - bool bBumpped = ( ( texinfo[pFace->texinfo].flags & SURF_BUMPLIGHT ) != 0 ); - - for( int iStyle = 0; iStyle < 4; ++iStyle ) - { - if( pFace->styles[iStyle] != 255 ) - { - for ( int iBump = 0; iBump < 4; ++iBump ) - { - if ( iBump == 0 || ( iBump > 0 && bBumpped ) ) - { - for( int iSample = 0; iSample < pFaceLight->numsamples; ++iSample ) - { - sample_t *pSample = &pFaceLight->sample[iSample]; - WriteWinding( pFileSamples[iStyle][iBump], pSample->w, pFaceLight->light[iStyle][iBump][iSample].m_vecLighting ); - if( bDumpNormals ) - { - WriteNormal( pFileSamples[iStyle][iBump], pSample->pos, pSample->normal, 15.0f, pSample->normal * 255.0f ); - } - } - } - } - } - } - } - - ThreadUnlock(); -} - - -//----------------------------------------------------------------------------- -// Allocates light sample data -//----------------------------------------------------------------------------- -static inline void AllocateLightstyleSamples( facelight_t* fl, int styleIndex, int numnormals ) -{ - for (int n = 0; n < numnormals; ++n) - { - fl->light[styleIndex][n] = ( LightingValue_t* )calloc( fl->numsamples, sizeof(LightingValue_t ) ); - } -} - - -//----------------------------------------------------------------------------- -// Used to find an existing lightstyle on a face -//----------------------------------------------------------------------------- -static inline int FindLightstyle( dface_t* f, int lightstyle ) -{ - for (int k = 0; k < MAXLIGHTMAPS; k++) - { - if (f->styles[k] == lightstyle) - return k; - } - - return -1; -} - -static int FindOrAllocateLightstyleSamples( dface_t* f, facelight_t *fl, int lightstyle, int numnormals ) -{ - // Search the lightstyles associated with the face for a match - int k; - for (k = 0; k < MAXLIGHTMAPS; k++) - { - if (f->styles[k] == lightstyle) - break; - - // Found an empty entry, we can use it for a new lightstyle - if (f->styles[k] == 255) - { - AllocateLightstyleSamples( fl, k, numnormals ); - f->styles[k] = lightstyle; - break; - } - } - - // Check for overflow - if (k >= MAXLIGHTMAPS) - return -1; - - return k; -} - - -//----------------------------------------------------------------------------- -// Compute the illumination point + normal for the sample -//----------------------------------------------------------------------------- -static void ComputeIlluminationPointAndNormalsSSE( lightinfo_t const& l, FourVectors const &pos, FourVectors const &norm, SSE_SampleInfo_t* pInfo, int numSamples ) -{ - - Vector v[4]; - - pInfo->m_Points = pos; - bool computeNormals = ( pInfo->m_NormalCount > 1 && ( pInfo->m_IsDispFace || !l.isflat ) ); - - // FIXME: move sample point off the surface a bit, this is done so that - // light sampling will not be affected by a bug where raycasts will - // intersect with the face being lit. We really should just have that - // logic in GatherSampleLight - FourVectors faceNormal; - faceNormal.DuplicateVector( l.facenormal ); - pInfo->m_Points += faceNormal; - - if ( pInfo->m_IsDispFace ) - { - pInfo->m_PointNormals[0] = norm; - } - else if ( !l.isflat ) - { - // If the face isn't flat, use a phong-based normal instead - FourVectors modelorg; - modelorg.DuplicateVector( l.modelorg ); - FourVectors vecSample = pos; - vecSample -= modelorg; - GetPhongNormal( pInfo->m_FaceNum, vecSample, pInfo->m_PointNormals[0] ); - } - - if ( computeNormals ) - { - Vector bv[4][NUM_BUMP_VECTS]; - for ( int i = 0; i < 4; ++i ) - { - // TODO: using Vec may slow things down a bit - GetBumpNormals( pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[0], - pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[1], - l.facenormal, pInfo->m_PointNormals[0].Vec( i ), bv[i] ); - } - for ( int b = 0; b < NUM_BUMP_VECTS; ++b ) - { - pInfo->m_PointNormals[b+1].LoadAndSwizzle ( bv[0][b], bv[1][b], bv[2][b], bv[3][b] ); - } - } - - // TODO: this may slow things down a bit ( using Vec ) - for ( int i = 0; i < 4; ++i ) - pInfo->m_Clusters[i] = ClusterFromPoint( pos.Vec( i ) ); -} - -//----------------------------------------------------------------------------- -// Iterates over all lights and computes lighting at up to 4 sample points -//----------------------------------------------------------------------------- -static void GatherSampleLightAt4Points( SSE_SampleInfo_t& info, int sampleIdx, int numSamples ) -{ - SSE_sampleLightOutput_t out; - - // Iterate over all direct lights and add them to the particular sample - for (directlight_t *dl = activelights; dl != NULL; dl = dl->next) - { - // is this lights cluster visible? - fltx4 dotMask = Four_Zeros; - bool skipLight = true; - for( int s = 0; s < numSamples; s++ ) - { - if( PVSCheck( dl->pvs, info.m_Clusters[s] ) ) - { - dotMask = SetComponentSIMD( dotMask, s, 1.0f ); - skipLight = false; - } - } - if ( skipLight ) - continue; - - GatherSampleLightSSE( out, dl, info.m_FaceNum, info.m_Points, info.m_PointNormals, info.m_NormalCount, info.m_iThread ); - - // Apply the PVS check filter and compute falloff x dot - fltx4 fxdot[NUM_BUMP_VECTS + 1]; - skipLight = true; - for ( int b = 0; b < info.m_NormalCount; b++ ) - { - fxdot[b] = MulSIMD( out.m_flDot[b], dotMask ); - fxdot[b] = MulSIMD( fxdot[b], out.m_flFalloff ); - if ( !IsAllZeros( fxdot[b] ) ) - { - skipLight = false; - } - } - if ( skipLight ) - continue; - - // Figure out the lightstyle for this particular sample - int lightStyleIndex = FindOrAllocateLightstyleSamples( info.m_pFace, info.m_pFaceLight, - dl->light.style, info.m_NormalCount ); - if (lightStyleIndex < 0) - { - if (info.m_WarnFace != info.m_FaceNum) - { - Warning ("\nWARNING: Too many light styles on a face at (%f, %f, %f)\n", - info.m_Points.x.m128_f32[0], info.m_Points.y.m128_f32[0], info.m_Points.z.m128_f32[0] ); - info.m_WarnFace = info.m_FaceNum; - } - continue; - } - - // pLightmaps is an array of the lightmaps for each normal direction, - // here's where the result of the sample gathering goes - LightingValue_t** pLightmaps = info.m_pFaceLight->light[lightStyleIndex]; - - // Incremental lighting only cares about lightstyle zero - if( g_pIncremental && (dl->light.style == 0) ) - { - for ( int i = 0; i < numSamples; i++ ) - { - g_pIncremental->AddLightToFace( dl->m_IncrementalID, info.m_FaceNum, sampleIdx + i, - info.m_LightmapSize, SubFloat( fxdot[0], i ), info.m_iThread ); - } - } - - for( int n = 0; n < info.m_NormalCount; ++n ) - { - for ( int i = 0; i < numSamples; i++ ) - { - pLightmaps[n][sampleIdx + i].AddLight( SubFloat( fxdot[n], i ), dl->light.intensity, SubFloat( out.m_flSunAmount, i ) ); - } - } - } -} - - - -//----------------------------------------------------------------------------- -// Iterates over all lights and computes lighting at a sample point -//----------------------------------------------------------------------------- -static void ResampleLightAt4Points( SSE_SampleInfo_t& info, int lightStyleIndex, int flags, LightingValue_t pLightmap[4][NUM_BUMP_VECTS+1] ) -{ - SSE_sampleLightOutput_t out; - - // Clear result - for ( int i = 0; i < 4; ++i ) - { - for ( int n = 0; n < info.m_NormalCount; ++n ) - { - pLightmap[i][n].Zero(); - } - } - - // Iterate over all direct lights and add them to the particular sample - for (directlight_t *dl = activelights; dl != NULL; dl = dl->next) - { - if ((flags & AMBIENT_ONLY) && (dl->light.type != emit_skyambient)) - continue; - - if ((flags & NON_AMBIENT_ONLY) && (dl->light.type == emit_skyambient)) - continue; - - // Only add contributions that match the lightstyle - Assert( lightStyleIndex <= MAXLIGHTMAPS ); - Assert( info.m_pFace->styles[lightStyleIndex] != 255 ); - if (dl->light.style != info.m_pFace->styles[lightStyleIndex]) - continue; - - // is this lights cluster visible? - fltx4 dotMask = Four_Zeros; - bool skipLight = true; - for( int s = 0; s < 4; s++ ) - { - if( PVSCheck( dl->pvs, info.m_Clusters[s] ) ) - { - dotMask = SetComponentSIMD( dotMask, s, 1.0f ); - skipLight = false; - } - } - if ( skipLight ) - continue; - - // NOTE: Notice here that if the light is on the back side of the face - // (tested by checking the dot product of the face normal and the light position) - // we don't want it to contribute to *any* of the bumped lightmaps. It glows - // in disturbing ways if we don't do this. - GatherSampleLightSSE( out, dl, info.m_FaceNum, info.m_Points, info.m_PointNormals, info.m_NormalCount, info.m_iThread ); - - // Apply the PVS check filter and compute falloff x dot - fltx4 fxdot[NUM_BUMP_VECTS + 1]; - for ( int b = 0; b < info.m_NormalCount; b++ ) - { - fxdot[b] = MulSIMD( out.m_flFalloff, out.m_flDot[b] ); - fxdot[b] = MulSIMD( fxdot[b], dotMask ); - } - - // Compute the contributions to each of the bumped lightmaps - // The first sample is for non-bumped lighting. - // The other sample are for bumpmapping. - for( int i = 0; i < 4; ++i ) - { - for( int n = 0; n < info.m_NormalCount; ++n ) - { - pLightmap[i][n].AddLight( SubFloat( fxdot[n], i ), dl->light.intensity, SubFloat( out.m_flSunAmount, i ) ); - } - } - } -} - -bool PointsInWinding ( FourVectors const & point, winding_t *w, int &invalidBits ) -{ - FourVectors edge, toPt, cross, testCross, p0, p1; - fltx4 invalidMask; - - // - // get the first normal to test - // - p0.DuplicateVector( w->p[0] ); - p1.DuplicateVector( w->p[1] ); - toPt = point; - toPt -= p0; - edge = p1; - edge -= p0; - testCross = edge ^ toPt; - testCross.VectorNormalizeFast(); - - for( int ndxPt = 1; ndxPt < w->numpoints; ndxPt++ ) - { - p0.DuplicateVector( w->p[ndxPt] ); - p1.DuplicateVector( w->p[(ndxPt+1)%w->numpoints] ); - toPt = point; - toPt -= p0; - edge = p1; - edge -= p0; - cross = edge ^ toPt; - cross.VectorNormalizeFast(); - - fltx4 dot = cross * testCross; - invalidMask = OrSIMD( invalidMask, CmpLtSIMD( dot, Four_Zeros ) ); - - invalidBits = TestSignSIMD ( invalidMask ); - if ( invalidBits == 0xF ) - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Perform supersampling at a particular point -//----------------------------------------------------------------------------- -static int SupersampleLightAtPoint( lightinfo_t& l, SSE_SampleInfo_t& info, - int sampleIndex, int lightStyleIndex, LightingValue_t *pLight, int flags ) -{ - sample_t& sample = info.m_pFaceLight->sample[sampleIndex]; - - // Get the position of the original sample in lightmapspace - Vector2D temp; - WorldToLuxelSpace( &l, sample.pos, temp ); - Vector sampleLightOrigin( temp[0], temp[1], 0.0f ); - - // Some parameters related to supersampling - float sampleWidth = ( flags & NON_AMBIENT_ONLY ) ? 4 : 2; - float cscale = 1.0f / sampleWidth; - float csshift = -((sampleWidth - 1) * cscale) / 2.0; - - // Clear out the light values - for (int i = 0; i < info.m_NormalCount; ++i ) - pLight[i].Zero(); - - int subsampleCount = 0; - - FourVectors superSampleNormal; - superSampleNormal.DuplicateVector( sample.normal ); - - FourVectors superSampleLightCoord; - FourVectors superSamplePosition; - - if ( flags & NON_AMBIENT_ONLY ) - { - float aRow[4]; - for ( int coord = 0; coord < 4; ++coord ) - aRow[coord] = csshift + coord * cscale; - fltx4 sseRow = LoadUnalignedSIMD( aRow ); - - for (int s = 0; s < 4; ++s) - { - // make sure the coordinate is inside of the sample's winding and when normalizing - // below use the number of samples used, not just numsamples and some of them - // will be skipped if they are not inside of the winding - superSampleLightCoord.DuplicateVector( sampleLightOrigin ); - superSampleLightCoord.x = AddSIMD( superSampleLightCoord.x, ReplicateX4( aRow[s] ) ); - superSampleLightCoord.y = AddSIMD( superSampleLightCoord.y, sseRow ); - - // Figure out where the supersample exists in the world, and make sure - // it lies within the sample winding - LuxelSpaceToWorld( &l, superSampleLightCoord[0], superSampleLightCoord[1], superSamplePosition ); - - // A winding should exist only if the sample wasn't a uniform luxel, or if g_bDumpPatches is true. - int invalidBits = 0; - if ( sample.w && !PointsInWinding( superSamplePosition, sample.w, invalidBits ) ) - continue; - - // Compute the super-sample illumination point and normal - // We're assuming the flat normal is the same for all supersamples - ComputeIlluminationPointAndNormalsSSE( l, superSamplePosition, superSampleNormal, &info, 4 ); - - // Resample the non-ambient light at this point... - LightingValue_t result[4][NUM_BUMP_VECTS+1]; - ResampleLightAt4Points( info, lightStyleIndex, NON_AMBIENT_ONLY, result ); - - // Got more subsamples - for ( int i = 0; i < 4; i++ ) - { - if ( !( ( invalidBits >> i ) & 0x1 ) ) - { - for ( int n = 0; n < info.m_NormalCount; ++n ) - { - pLight[n].AddLight( result[i][n] ); - } - ++subsampleCount; - } - } - } - } - else - { - FourVectors superSampleOffsets; - superSampleOffsets.LoadAndSwizzle( Vector( csshift, csshift, 0 ), Vector( csshift, csshift + cscale, 0), - Vector( csshift + cscale, csshift, 0 ), Vector( csshift + cscale, csshift + cscale, 0 ) ); - superSampleLightCoord.DuplicateVector( sampleLightOrigin ); - superSampleLightCoord += superSampleOffsets; - - LuxelSpaceToWorld( &l, superSampleLightCoord[0], superSampleLightCoord[1], superSamplePosition ); - - int invalidBits = 0; - if ( sample.w && !PointsInWinding( superSamplePosition, sample.w, invalidBits ) ) - return 0; - - ComputeIlluminationPointAndNormalsSSE( l, superSamplePosition, superSampleNormal, &info, 4 ); - - LightingValue_t result[4][NUM_BUMP_VECTS+1]; - ResampleLightAt4Points( info, lightStyleIndex, AMBIENT_ONLY, result ); - - // Got more subsamples - for ( int i = 0; i < 4; i++ ) - { - if ( !( ( invalidBits >> i ) & 0x1 ) ) - { - for ( int n = 0; n < info.m_NormalCount; ++n ) - { - pLight[n].AddLight( result[i][n] ); - } - ++subsampleCount; - } - } - } - - return subsampleCount; -} - - -//----------------------------------------------------------------------------- -// Compute gradients of a lightmap -//----------------------------------------------------------------------------- -static void ComputeLightmapGradients( SSE_SampleInfo_t& info, bool const* pHasProcessedSample, - float* pIntensity, float* gradient ) -{ - int w = info.m_LightmapWidth; - int h = info.m_LightmapHeight; - facelight_t* fl = info.m_pFaceLight; - - for (int i=0 ; inumsamples ; i++) - { - // Don't supersample the same sample twice - if (pHasProcessedSample[i]) - continue; - - gradient[i] = 0.0f; - sample_t& sample = fl->sample[i]; - - // Choose the maximum gradient of all bumped lightmap intensities - for ( int n = 0; n < info.m_NormalCount; ++n ) - { - int j = n * info.m_LightmapSize + sample.s + sample.t * w; - - if (sample.t > 0) - { - if (sample.s > 0) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-1-w] ) ); - gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-w] ) ); - if (sample.s < w-1) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+1-w] ) ); - } - if (sample.t < h-1) - { - if (sample.s > 0) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-1+w] ) ); - gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+w] ) ); - if (sample.s < w-1) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+1+w] ) ); - } - if (sample.s > 0) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-1] ) ); - if (sample.s < w-1) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+1] ) ); - } - } -} - -//----------------------------------------------------------------------------- -// ComputeLuxelIntensity... -//----------------------------------------------------------------------------- -static inline void ComputeLuxelIntensity( SSE_SampleInfo_t& info, int sampleIdx, - LightingValue_t **ppLightSamples, float* pSampleIntensity ) -{ - // Compute a separate intensity for each - sample_t& sample = info.m_pFaceLight->sample[sampleIdx]; - int destIdx = sample.s + sample.t * info.m_LightmapWidth; - for (int n = 0; n < info.m_NormalCount; ++n) - { - float intensity = ppLightSamples[n][sampleIdx].Intensity(); - - // convert to a linear perception space - pSampleIntensity[n * info.m_LightmapSize + destIdx] = pow( intensity / 256.0, 1.0 / 2.2 ); - } -} - -//----------------------------------------------------------------------------- -// Compute the maximum intensity based on all bumped lighting -//----------------------------------------------------------------------------- -static void ComputeSampleIntensities( SSE_SampleInfo_t& info, LightingValue_t **ppLightSamples, float* pSampleIntensity ) -{ - for (int i=0; inumsamples; i++) - { - ComputeLuxelIntensity( info, i, ppLightSamples, pSampleIntensity ); - } -} - -//----------------------------------------------------------------------------- -// Perform supersampling on a particular lightstyle -//----------------------------------------------------------------------------- -static void BuildSupersampleFaceLights( lightinfo_t& l, SSE_SampleInfo_t& info, int lightstyleIndex ) -{ - LightingValue_t pAmbientLight[NUM_BUMP_VECTS+1]; - LightingValue_t pDirectLight[NUM_BUMP_VECTS+1]; - - // This is used to make sure we don't supersample a light sample more than once - int processedSampleSize = info.m_LightmapSize * sizeof(bool); - bool* pHasProcessedSample = (bool*)stackalloc( processedSampleSize ); - memset( pHasProcessedSample, 0, processedSampleSize ); - - // This is used to compute a simple gradient computation of the light samples - // We're going to store the maximum intensity of all bumped samples at each sample location - float* pGradient = (float*)stackalloc( info.m_pFaceLight->numsamples * sizeof(float) ); - float* pSampleIntensity = (float*)stackalloc( info.m_NormalCount * info.m_LightmapSize * sizeof(float) ); - - // Compute the maximum intensity of all lighting associated with this lightstyle - // for all bumped lighting - LightingValue_t **ppLightSamples = info.m_pFaceLight->light[lightstyleIndex]; - ComputeSampleIntensities( info, ppLightSamples, pSampleIntensity ); - - Vector *pVisualizePass = NULL; - if (debug_extra) - { - int visualizationSize = info.m_pFaceLight->numsamples * sizeof(Vector); - pVisualizePass = (Vector*)stackalloc( visualizationSize ); - memset( pVisualizePass, 0, visualizationSize ); - } - - // What's going on here is that we're looking for large lighting discontinuities - // (large light intensity gradients) as a clue that we should probably be supersampling - // in that area. Because the supersampling operation will cause lighting changes, - // we've found that it's good to re-check the gradients again and see if any other - // areas should be supersampled as a result of the previous pass. Keep going - // until all the gradients are reasonable or until we hit a max number of passes - bool do_anotherpass = true; - int pass = 1; - while (do_anotherpass && pass <= extrapasses) - { - // Look for lighting discontinuities to see what we should be supersampling - ComputeLightmapGradients( info, pHasProcessedSample, pSampleIntensity, pGradient ); - - do_anotherpass = false; - - // Now check all of the samples and supersample those which we have - // marked as having high gradients - for (int i=0 ; inumsamples; ++i) - { - // Don't supersample the same sample twice - if (pHasProcessedSample[i]) - continue; - - // Don't supersample if the lighting is pretty uniform near the sample - if (pGradient[i] < 0.0625) - continue; - - // Joy! We're supersampling now, and we therefore must do another pass - // Also, we need never bother with this sample again - pHasProcessedSample[i] = true; - do_anotherpass = true; - - if (debug_extra) - { - // Mark the little visualization bitmap with a color indicating - // which pass it was updated on. - pVisualizePass[i][0] = (pass & 1) * 255; - pVisualizePass[i][1] = (pass & 2) * 128; - pVisualizePass[i][2] = (pass & 4) * 64; - } - - // Supersample the ambient light for each bump direction vector - int ambientSupersampleCount = SupersampleLightAtPoint( l, info, i, lightstyleIndex, pAmbientLight, AMBIENT_ONLY ); - - // Supersample the non-ambient light for each bump direction vector - int directSupersampleCount = SupersampleLightAtPoint( l, info, i, lightstyleIndex, pDirectLight, NON_AMBIENT_ONLY ); - - // Because of sampling problems, small area triangles may have no samples. - // In this case, just use what we already have - if ( ambientSupersampleCount > 0 && directSupersampleCount > 0 ) - { - // Add the ambient + directional terms together, stick it back into the lightmap - for (int n = 0; n < info.m_NormalCount; ++n) - { - ppLightSamples[n][i].Zero(); - ppLightSamples[n][i].AddWeighted( pDirectLight[n],1.0f / directSupersampleCount ); - ppLightSamples[n][i].AddWeighted( pAmbientLight[n], 1.0f / ambientSupersampleCount ); - } - - // Recompute the luxel intensity based on the supersampling - ComputeLuxelIntensity( info, i, ppLightSamples, pSampleIntensity ); - } - - } - - // We've finished another pass - pass++; - } - - if (debug_extra) - { - // Copy colors representing which supersample pass the sample was messed with - // into the actual lighting values so we can visualize it - for (int i=0 ; inumsamples ; ++i) - { - for (int j = 0; j facenum = facenum; - - pl->face = f; - - // - // rotate plane - // - VectorCopy (dplanes[f->planenum].normal, pl->facenormal); - pl->facedist = dplanes[f->planenum].dist; - - // get the origin offset for rotating bmodels - VectorCopy (face_offset[facenum], pl->modelorg); - - CalcFaceVectors( pl ); - - // figure out if the surface is flat - pl->isflat = true; - if (smoothing_threshold != 1) - { - faceneighbor_t *fn = &faceneighbor[facenum]; - - for (int j=0 ; jnumedges ; j++) - { - float dot = DotProduct( pl->facenormal, fn->normal[j] ); - if (dot < 1.0 - EQUAL_EPSILON) - { - pl->isflat = false; - break; - } - } - } -} - -static void InitSampleInfo( lightinfo_t const& l, int iThread, SSE_SampleInfo_t& info ) -{ - info.m_LightmapWidth = l.face->m_LightmapTextureSizeInLuxels[0]+1; - info.m_LightmapHeight = l.face->m_LightmapTextureSizeInLuxels[1]+1; - info.m_LightmapSize = info.m_LightmapWidth * info.m_LightmapHeight; - - // How many lightmaps are we going to need? - info.m_pTexInfo = &texinfo[l.face->texinfo]; - info.m_NormalCount = (info.m_pTexInfo->flags & SURF_BUMPLIGHT) ? NUM_BUMP_VECTS + 1 : 1; - info.m_FaceNum = l.facenum; - info.m_pFace = l.face; - info.m_pFaceLight = &facelight[info.m_FaceNum]; - info.m_IsDispFace = ValidDispFace( info.m_pFace ); - info.m_iThread = iThread; - info.m_WarnFace = -1; - - info.m_NumSamples = info.m_pFaceLight->numsamples; - info.m_NumSampleGroups = ( info.m_NumSamples & 0x3) ? ( info.m_NumSamples / 4 ) + 1 : ( info.m_NumSamples / 4 ); - - // initialize normals if the surface is flat - if (l.isflat) - { - info.m_PointNormals[0].DuplicateVector( l.facenormal ); - - // use facenormal along with the smooth normal to build the three bump map vectors - if( info.m_NormalCount > 1 ) - { - Vector bumpVects[NUM_BUMP_VECTS]; - GetBumpNormals( info.m_pTexInfo->textureVecsTexelsPerWorldUnits[0], - info.m_pTexInfo->textureVecsTexelsPerWorldUnits[1], l.facenormal, - l.facenormal, bumpVects );//&info.m_PointNormal[1] ); - - for ( int b = 0; b < NUM_BUMP_VECTS; ++b ) - { - info.m_PointNormals[b + 1].DuplicateVector( bumpVects[b] ); - } - } - } -} - -void BuildFacelights (int iThread, int facenum) -{ - int i, j; - - lightinfo_t l; - dface_t *f; - facelight_t *fl; - SSE_SampleInfo_t sampleInfo; - directlight_t *dl; - Vector spot; - Vector v[4], n[4]; - - if( g_bInterrupt ) - return; - - // FIXME: Is there a better way to do this? Like, in RunThreadsOn, for instance? - // Don't pay this cost unless we have to; this is super perf-critical code. - if (g_pIncremental) - { - // Both threads will be accessing this so it needs to be protected or else thread A - // will load it in and thread B will increment it but its increment will be - // overwritten by thread A when thread A writes it back. - ThreadLock(); - ++g_iCurFace; - ThreadUnlock(); - } - - // some surfaces don't need lightmaps - f = &g_pFaces[facenum]; - f->lightofs = -1; - for (j=0 ; jstyles[j] = 255; - - // Trivial-reject the whole face? - if( !( g_FacesVisibleToLights[facenum>>3] & (1 << (facenum & 7)) ) ) - return; - - if ( texinfo[f->texinfo].flags & TEX_SPECIAL) - return; // non-lit texture - - // check for patches for this face. If none it must be degenerate. Ignore. - if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) - return; - - fl = &facelight[facenum]; - - InitLightinfo( &l, facenum ); - CalcPoints( &l, fl, facenum ); - InitSampleInfo( l, iThread, sampleInfo ); - - // Allocate sample positions/normals to SSE - int numGroups = ( fl->numsamples & 0x3) ? ( fl->numsamples / 4 ) + 1 : ( fl->numsamples / 4 ); - - // always allocate style 0 lightmap - f->styles[0] = 0; - AllocateLightstyleSamples( fl, 0, sampleInfo.m_NormalCount ); - - // sample the lights at each sample location - for ( int grp = 0; grp < numGroups; ++grp ) - { - int nSample = 4 * grp; - - sample_t *sample = sampleInfo.m_pFaceLight->sample + nSample; - int numSamples = min ( 4, sampleInfo.m_pFaceLight->numsamples - nSample ); - - FourVectors positions; - FourVectors normals; - - for ( int i = 0; i < 4; i++ ) - { - v[i] = ( i < numSamples ) ? sample[i].pos : sample[numSamples - 1].pos; - n[i] = ( i < numSamples ) ? sample[i].normal : sample[numSamples - 1].normal; - } - positions.LoadAndSwizzle( v[0], v[1], v[2], v[3] ); - normals.LoadAndSwizzle( n[0], n[1], n[2], n[3] ); - - ComputeIlluminationPointAndNormalsSSE( l, positions, normals, &sampleInfo, numSamples ); - - // Fixup sample normals in case of smooth faces - if ( !l.isflat ) - { - for ( int i = 0; i < numSamples; i++ ) - sample[i].normal = sampleInfo.m_PointNormals[0].Vec( i ); - } - - // Iterate over all the lights and add their contribution to this group of spots - GatherSampleLightAt4Points( sampleInfo, nSample, numSamples ); - } - - // Tell the incremental light manager that we're done with this face. - if( g_pIncremental ) - { - for (dl = activelights; dl != NULL; dl = dl->next) - { - // Only deal with lightstyle 0 for incremental lighting - if (dl->light.style == 0) - g_pIncremental->FinishFace( dl->m_IncrementalID, facenum, iThread ); - } - - // Don't have to deal with patch lights (only direct lighting is used) - // or supersampling - return; - } - - // get rid of the -extra functionality on displacement surfaces - if (do_extra && !sampleInfo.m_IsDispFace) - { - // For each lightstyle, perform a supersampling pass - for ( i = 0; i < MAXLIGHTMAPS; ++i ) - { - // Stop when we run out of lightstyles - if (f->styles[i] == 255) - break; - - BuildSupersampleFaceLights( l, sampleInfo, i ); - } - } - - if (!g_bUseMPI) - { - // - // This is done on the master node when MPI is used - // - BuildPatchLights( facenum ); - } - - if( g_bDumpPatches ) - { - DumpSamples( facenum, fl ); - } - else - { - FreeSampleWindings( fl ); - } - -} - -void BuildPatchLights( int facenum ) -{ - int i, k; - - CPatch *patch; - - dface_t *f = &g_pFaces[facenum]; - facelight_t *fl = &facelight[facenum]; - - for( k = 0; k < MAXLIGHTMAPS; k++ ) - { - if (f->styles[k] == 0) - break; - } - - if (k >= MAXLIGHTMAPS) - return; - - for (i = 0; i < fl->numsamples; i++) - { - AddSampleToPatch( &fl->sample[i], fl->light[k][0][i], facenum); - } - - // check for a valid face - if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) - return; - - // push up sampled light to parents (children always exist first in the list) - CPatch *pNextPatch; - for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) - { - // next patch - pNextPatch = NULL; - if( patch->ndxNext != g_Patches.InvalidIndex() ) - { - pNextPatch = &g_Patches.Element( patch->ndxNext ); - } - - // skip patches without parents - if( patch->parent == g_Patches.InvalidIndex() ) -// if (patch->parent == -1) - continue; - - CPatch *parent = &g_Patches.Element( patch->parent ); - - parent->samplearea += patch->samplearea; - VectorAdd( parent->samplelight, patch->samplelight, parent->samplelight ); - } - - // average up the direct light on each patch for radiosity - if (numbounce > 0) - { - for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) - { - // next patch - pNextPatch = NULL; - if( patch->ndxNext != g_Patches.InvalidIndex() ) - { - pNextPatch = &g_Patches.Element( patch->ndxNext ); - } - - if (patch->samplearea) - { - float scale; - Vector v; - scale = 1.0 / patch->samplearea; - - VectorScale( patch->samplelight, scale, v ); - VectorAdd( patch->totallight.light[0], v, patch->totallight.light[0] ); - VectorAdd( patch->directlight, v, patch->directlight ); - } - } - } - - // pull totallight from children (children always exist first in the list) - for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) - { - // next patch - pNextPatch = NULL; - if( patch->ndxNext != g_Patches.InvalidIndex() ) - { - pNextPatch = &g_Patches.Element( patch->ndxNext ); - } - - if ( patch->child1 != g_Patches.InvalidIndex() ) - { - float s1, s2; - CPatch *child1; - CPatch *child2; - - child1 = &g_Patches.Element( patch->child1 ); - child2 = &g_Patches.Element( patch->child2 ); - - s1 = child1->area / (child1->area + child2->area); - s2 = child2->area / (child1->area + child2->area); - - VectorScale( child1->totallight.light[0], s1, patch->totallight.light[0] ); - VectorMA( patch->totallight.light[0], s2, child2->totallight.light[0], patch->totallight.light[0] ); - - VectorCopy( patch->totallight.light[0], patch->directlight ); - } - } - - bool needsBumpmap = false; - if( texinfo[f->texinfo].flags & SURF_BUMPLIGHT ) - { - needsBumpmap = true; - } - - // add an ambient term if desired - if (ambient[0] || ambient[1] || ambient[2]) - { - for( int j=0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) - { - if ( f->styles[j] == 0 ) - { - for (i = 0; i < fl->numsamples; i++) - { - fl->light[j][0][i].m_vecLighting += ambient; - if( needsBumpmap ) - { - fl->light[j][1][i].m_vecLighting += ambient; - fl->light[j][2][i].m_vecLighting += ambient; - fl->light[j][3][i].m_vecLighting += ambient; - } - } - break; - } - } - } - - // light from dlight_threshold and above is sent out, but the - // texture itself should still be full bright - -#if 0 - // if( VectorAvg( g_FacePatches[facenum]->baselight ) >= dlight_threshold) // Now all lighted surfaces glow - { - for( j=0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) - { - if ( f->styles[j] == 0 ) - { - // BUG: shouldn't this be done for all patches on the face? - for (i=0 ; inumsamples ; i++) - { - // garymctchange - VectorAdd( fl->light[j][0][i], g_FacePatches[facenum]->baselight, fl->light[j][0][i] ); - if( needsBumpmap ) - { - for( bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) - { - VectorAdd( fl->light[j][bumpSample][i], g_FacePatches[facenum]->baselight, fl->light[j][bumpSample][i] ); - } - } - } - break; - } - } - } -#endif -} - - -/* - ============= - PrecompLightmapOffsets - ============= -*/ - -void PrecompLightmapOffsets() -{ - int facenum; - dface_t *f; - int lightstyles; - int lightdatasize = 0; - - // NOTE: We store avg face light data in this lump *before* the lightmap data itself - // in *reverse order* of the way the lightstyles appear in the styles array. - for( facenum = 0; facenum < numfaces; facenum++ ) - { - f = &g_pFaces[facenum]; - - if ( texinfo[f->texinfo].flags & TEX_SPECIAL) - continue; // non-lit texture - - if ( dlight_map != 0 ) - f->styles[1] = 0; - - for (lightstyles=0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) - { - if ( f->styles[lightstyles] == 255 ) - break; - } - - if ( !lightstyles ) - continue; - - // Reserve room for the avg light color data - lightdatasize += lightstyles * 4; - - f->lightofs = lightdatasize; - - bool needsBumpmap = false; - if( texinfo[f->texinfo].flags & SURF_BUMPLIGHT ) - { - needsBumpmap = true; - } - - int nLuxels = (f->m_LightmapTextureSizeInLuxels[0]+1) * (f->m_LightmapTextureSizeInLuxels[1]+1); - if( needsBumpmap ) - { - lightdatasize += nLuxels * 4 * lightstyles * ( NUM_BUMP_VECTS + 1 ); - } - else - { - lightdatasize += nLuxels * 4 * lightstyles; - } - } - - // The incremental lighting code needs us to preserve the contents of dlightdata - // since it only recomposites lighting for faces that have lights that touch them. - if( g_pIncremental && pdlightdata->Count() ) - return; - - pdlightdata->SetSize( lightdatasize ); -} - -// Clamp the three values for bumped lighting such that we trade off directionality for brightness. -static void ColorClampBumped( Vector& color1, Vector& color2, Vector& color3 ) -{ - Vector maxs; - Vector *colors[3] = { &color1, &color2, &color3 }; - maxs[0] = VectorMaximum( color1 ); - maxs[1] = VectorMaximum( color2 ); - maxs[2] = VectorMaximum( color3 ); - - // HACK! Clean this up, and add some else statements -#define CONDITION(a,b,c) do { if( maxs[a] >= maxs[b] && maxs[b] >= maxs[c] ) { order[0] = a; order[1] = b; order[2] = c; } } while( 0 ) - - int order[3]; - CONDITION(0,1,2); - CONDITION(0,2,1); - CONDITION(1,0,2); - CONDITION(1,2,0); - CONDITION(2,0,1); - CONDITION(2,1,0); - - int i; - for( i = 0; i < 3; i++ ) - { - float max = VectorMaximum( *colors[order[i]] ); - if( max <= 1.0f ) - { - continue; - } - // This channel is too bright. . take half of the amount that we are over and - // add it to the other two channel. - float factorToRedist = ( max - 1.0f ) / max; - Vector colorToRedist = factorToRedist * *colors[order[i]]; - *colors[order[i]] -= colorToRedist; - colorToRedist *= 0.5f; - *colors[order[(i+1)%3]] += colorToRedist; - *colors[order[(i+2)%3]] += colorToRedist; - } - - ColorClamp( color1 ); - ColorClamp( color2 ); - ColorClamp( color3 ); - - if( color1[0] < 0.f ) color1[0] = 0.f; - if( color1[1] < 0.f ) color1[1] = 0.f; - if( color1[2] < 0.f ) color1[2] = 0.f; - if( color2[0] < 0.f ) color2[0] = 0.f; - if( color2[1] < 0.f ) color2[1] = 0.f; - if( color2[2] < 0.f ) color2[2] = 0.f; - if( color3[0] < 0.f ) color3[0] = 0.f; - if( color3[1] < 0.f ) color3[1] = 0.f; - if( color3[2] < 0.f ) color3[2] = 0.f; -} - -static void LinearToBumpedLightmap( - const float *linearColor, - const float *linearBumpColor1, - const float *linearBumpColor2, - const float *linearBumpColor3, - unsigned char *ret, - unsigned char *retBump1, - unsigned char *retBump2, - unsigned char *retBump3 ) -{ - const Vector &linearBump1 = *( ( const Vector * )linearBumpColor1 ); - const Vector &linearBump2 = *( ( const Vector * )linearBumpColor2 ); - const Vector &linearBump3 = *( ( const Vector * )linearBumpColor3 ); - - Vector gammaGoal; - // gammaGoal is premultiplied by 1/overbright, which we want - gammaGoal[0] = LinearToVertexLight( linearColor[0] ); - gammaGoal[1] = LinearToVertexLight( linearColor[1] ); - gammaGoal[2] = LinearToVertexLight( linearColor[2] ); - Vector bumpAverage = linearBump1; - bumpAverage += linearBump2; - bumpAverage += linearBump3; - bumpAverage *= ( 1.0f / 3.0f ); - - Vector correctionScale; - if( *( int * )&bumpAverage[0] != 0 && *( int * )&bumpAverage[1] != 0 && *( int * )&bumpAverage[2] != 0 ) - { - // fast path when we know that we don't have to worry about divide by zero. - VectorDivide( gammaGoal, bumpAverage, correctionScale ); -// correctionScale = gammaGoal / bumpSum; - } - else - { - correctionScale.Init( 0.0f, 0.0f, 0.0f ); - if( bumpAverage[0] != 0.0f ) - { - correctionScale[0] = gammaGoal[0] / bumpAverage[0]; - } - if( bumpAverage[1] != 0.0f ) - { - correctionScale[1] = gammaGoal[1] / bumpAverage[1]; - } - if( bumpAverage[2] != 0.0f ) - { - correctionScale[2] = gammaGoal[2] / bumpAverage[2]; - } - } - Vector correctedBumpColor1; - Vector correctedBumpColor2; - Vector correctedBumpColor3; - VectorMultiply( linearBump1, correctionScale, correctedBumpColor1 ); - VectorMultiply( linearBump2, correctionScale, correctedBumpColor2 ); - VectorMultiply( linearBump3, correctionScale, correctedBumpColor3 ); - - Vector check = ( correctedBumpColor1 + correctedBumpColor2 + correctedBumpColor3 ) / 3.0f; - - ColorClampBumped( correctedBumpColor1, correctedBumpColor2, correctedBumpColor3 ); - - ret[0] = RoundFloatToByte( gammaGoal[0] * 255.0f ); - ret[1] = RoundFloatToByte( gammaGoal[1] * 255.0f ); - ret[2] = RoundFloatToByte( gammaGoal[2] * 255.0f ); - retBump1[0] = RoundFloatToByte( correctedBumpColor1[0] * 255.0f ); - retBump1[1] = RoundFloatToByte( correctedBumpColor1[1] * 255.0f ); - retBump1[2] = RoundFloatToByte( correctedBumpColor1[2] * 255.0f ); - retBump2[0] = RoundFloatToByte( correctedBumpColor2[0] * 255.0f ); - retBump2[1] = RoundFloatToByte( correctedBumpColor2[1] * 255.0f ); - retBump2[2] = RoundFloatToByte( correctedBumpColor2[2] * 255.0f ); - retBump3[0] = RoundFloatToByte( correctedBumpColor3[0] * 255.0f ); - retBump3[1] = RoundFloatToByte( correctedBumpColor3[1] * 255.0f ); - retBump3[2] = RoundFloatToByte( correctedBumpColor3[2] * 255.0f ); -} - -//----------------------------------------------------------------------------- -// Convert a RGBExp32 to a RGBA8888 -// This matches the engine's conversion, so the lighting result is consistent. -//----------------------------------------------------------------------------- -void ConvertRGBExp32ToRGBA8888( const ColorRGBExp32 *pSrc, unsigned char *pDst ) -{ - Vector linearColor; - Vector vertexColor; - - // convert from ColorRGBExp32 to linear space - linearColor[0] = TexLightToLinear( ((ColorRGBExp32 *)pSrc)->r, ((ColorRGBExp32 *)pSrc)->exponent ); - linearColor[1] = TexLightToLinear( ((ColorRGBExp32 *)pSrc)->g, ((ColorRGBExp32 *)pSrc)->exponent ); - linearColor[2] = TexLightToLinear( ((ColorRGBExp32 *)pSrc)->b, ((ColorRGBExp32 *)pSrc)->exponent ); - - // convert from linear space to lightmap space - // cannot use mathlib routine directly because it doesn't match - // the colorspace version found in the engine, which *is* the same sequence here - vertexColor[0] = LinearToVertexLight( linearColor[0] ); - vertexColor[1] = LinearToVertexLight( linearColor[1] ); - vertexColor[2] = LinearToVertexLight( linearColor[2] ); - - // this is really a color normalization with a floor - ColorClamp( vertexColor ); - - // final [0..255] scale - pDst[0] = RoundFloatToByte( vertexColor[0] * 255.0f ); - pDst[1] = RoundFloatToByte( vertexColor[1] * 255.0f ); - pDst[2] = RoundFloatToByte( vertexColor[2] * 255.0f ); - pDst[3] = 255; -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vrad.h" +#include "lightmap.h" +#include "radial.h" +#include "mathlib/bumpvects.h" +#include "tier1/utlvector.h" +#include "vmpi.h" +#include "mathlib/anorms.h" +#include "map_utils.h" +#include "mathlib/halton.h" +#include "imagepacker.h" +#include "tier1/utlrbtree.h" +#include "tier1/utlbuffer.h" +#include "bitmap/tgawriter.h" +#include "mathlib/quantize.h" +#include "bitmap/imageformat.h" +#include "coordsize.h" + +enum +{ + AMBIENT_ONLY = 0x1, + NON_AMBIENT_ONLY = 0x2, +}; + +#define SMOOTHING_GROUP_HARD_EDGE 0xff000000 + +//==========================================================================// +// CNormalList. +//==========================================================================// + +// This class keeps a list of unique normals and provides a fast +class CNormalList +{ +public: + CNormalList(); + + // Adds the normal if unique. Otherwise, returns the normal's index into m_Normals. + int FindOrAddNormal( Vector const &vNormal ); + + +public: + + CUtlVector m_Normals; + + +private: + + // This represents a grid from (-1,-1,-1) to (1,1,1). + enum {NUM_SUBDIVS = 8}; + CUtlVector m_NormalGrid[NUM_SUBDIVS][NUM_SUBDIVS][NUM_SUBDIVS]; +}; + + +int g_iCurFace; +edgeshare_t edgeshare[MAX_MAP_EDGES]; + +Vector face_centroids[MAX_MAP_EDGES]; + +int vertexref[MAX_MAP_VERTS]; +int *vertexface[MAX_MAP_VERTS]; +faceneighbor_t faceneighbor[MAX_MAP_FACES]; + +static directlight_t *gSkyLight = NULL; +static directlight_t *gAmbient = NULL; + +//==========================================================================// +// CNormalList implementation. +//==========================================================================// + +CNormalList::CNormalList() : m_Normals( 128 ) +{ + for( int i=0; i < sizeof(m_NormalGrid)/sizeof(m_NormalGrid[0][0][0]); i++ ) + { + (&m_NormalGrid[0][0][0] + i)->SetGrowSize( 16 ); + } +} + +int CNormalList::FindOrAddNormal( Vector const &vNormal ) +{ + int gi[3]; + + // See which grid element it's in. + for( int iDim=0; iDim < 3; iDim++ ) + { + gi[iDim] = (int)( ((vNormal[iDim] + 1.0f) * 0.5f) * NUM_SUBDIVS - 0.000001f ); + gi[iDim] = min( gi[iDim], NUM_SUBDIVS ); + gi[iDim] = max( gi[iDim], 0 ); + } + + // Look for a matching vector in there. + CUtlVector *pGridElement = &m_NormalGrid[gi[0]][gi[1]][gi[2]]; + for( int i=0; i < pGridElement->Size(); i++ ) + { + int iNormal = pGridElement->Element(i); + + Vector *pVec = &m_Normals[iNormal]; + //if( pVec->DistToSqr(vNormal) < 0.00001f ) + if( *pVec == vNormal ) + return iNormal; + } + + // Ok, add a new one. + pGridElement->AddToTail( m_Normals.Size() ); + return m_Normals.AddToTail( vNormal ); +} + +// FIXME: HACK until the plane normals are made more happy +void GetBumpNormals( const float* sVect, const float* tVect, const Vector& flatNormal, + const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] ) +{ + Vector stmp( sVect[0], sVect[1], sVect[2] ); + Vector ttmp( tVect[0], tVect[1], tVect[2] ); + GetBumpNormals( stmp, ttmp, flatNormal, phongNormal, bumpNormals ); +} + +int EdgeVertex( dface_t *f, int edge ) +{ + int k; + + if (edge < 0) + edge += f->numedges; + else if (edge >= f->numedges) + edge = edge % f->numedges; + + k = dsurfedges[f->firstedge + edge]; + if (k < 0) + { + // Msg("(%d %d) ", dedges[-k].v[1], dedges[-k].v[0] ); + return dedges[-k].v[1]; + } + else + { + // Msg("(%d %d) ", dedges[k].v[0], dedges[k].v[1] ); + return dedges[k].v[0]; + } +} + + +/* + ============ + PairEdges + ============ +*/ +void PairEdges (void) +{ + int i, j, k, n, m; + dface_t *f; + int numneighbors; + int tmpneighbor[64]; + faceneighbor_t *fn; + + // count number of faces that reference each vertex + for (i=0, f = g_pFaces; inumedges ; j++) + { + // Store the count in vertexref + vertexref[EdgeVertex(f,j)]++; + } + } + + // allocate room + for (i = 0; i < numvertexes; i++) + { + // use the count from above to allocate a big enough array + vertexface[i] = ( int* )calloc( vertexref[i], sizeof( vertexface[0] ) ); + // clear the temporary data + vertexref[i] = 0; + } + + // store a list of every face that uses a particular vertex + for (i=0, f = g_pFaces ; inumedges ; j++) + { + n = EdgeVertex(f,j); + + for (k = 0; k < vertexref[n]; k++) + { + if (vertexface[n][k] == i) + break; + } + if (k >= vertexref[n]) + { + // add the face to the list + vertexface[n][k] = i; + vertexref[n]++; + } + } + } + + // calc normals and set displacement surface flag + for (i=0, f = g_pFaces; iplanenum].normal, fn->facenormal ); + + // set displacement surface flag + fn->bHasDisp = false; + if( ValidDispFace( f ) ) + { + fn->bHasDisp = true; + } + } + + // find neighbors + for (i=0, f = g_pFaces ; inormal = ( Vector* )calloc( f->numedges, sizeof( fn->normal[0] ) ); + + // look up all faces sharing vertices and add them to the list + for (j=0 ; jnumedges ; j++) + { + n = EdgeVertex(f,j); + + for (k = 0; k < vertexref[n]; k++) + { + double cos_normals_angle; + Vector *pNeighbornormal; + + // skip self + if (vertexface[n][k] == i) + continue; + + // if this face doens't have a displacement -- don't consider displacement neighbors + if( ( !fn->bHasDisp ) && ( faceneighbor[vertexface[n][k]].bHasDisp ) ) + continue; + + pNeighbornormal = &faceneighbor[vertexface[n][k]].facenormal; + cos_normals_angle = DotProduct( *pNeighbornormal, fn->facenormal ); + + // add normal if >= threshold or its a displacement surface (this is only if the original + // face is a displacement) + if ( fn->bHasDisp ) + { + // Always smooth with and against a displacement surface. + VectorAdd( fn->normal[j], *pNeighbornormal, fn->normal[j] ); + } + else + { + // No smoothing - use of method (backwards compatibility). + if ( ( f->smoothingGroups == 0 ) && ( g_pFaces[vertexface[n][k]].smoothingGroups == 0 ) ) + { + if ( cos_normals_angle >= smoothing_threshold ) + { + VectorAdd( fn->normal[j], *pNeighbornormal, fn->normal[j] ); + } + else + { + // not considered a neighbor + continue; + } + } + else + { + unsigned int smoothingGroup = ( f->smoothingGroups & g_pFaces[vertexface[n][k]].smoothingGroups ); + + // Hard edge. + if ( ( smoothingGroup & SMOOTHING_GROUP_HARD_EDGE ) != 0 ) + continue; + + if ( smoothingGroup != 0 ) + { + VectorAdd( fn->normal[j], *pNeighbornormal, fn->normal[j] ); + } + else + { + // not considered a neighbor + continue; + } + } + } + + // look to see if we've already added this one + for (m = 0; m < numneighbors; m++) + { + if (tmpneighbor[m] == vertexface[n][k]) + break; + } + + if (m >= numneighbors) + { + // add to neighbor list + tmpneighbor[m] = vertexface[n][k]; + numneighbors++; + if ( numneighbors > ARRAYSIZE(tmpneighbor) ) + { + Error("Stack overflow in neighbors\n"); + } + } + } + } + + if (numneighbors) + { + // copy over neighbor list + fn->numneighbors = numneighbors; + fn->neighbor = ( int* )calloc( numneighbors, sizeof( fn->neighbor[0] ) ); + for (m = 0; m < numneighbors; m++) + { + fn->neighbor[m] = tmpneighbor[m]; + } + } + + // fixup normals + for (j = 0; j < f->numedges; j++) + { + VectorAdd( fn->normal[j], fn->facenormal, fn->normal[j] ); + VectorNormalize( fn->normal[j] ); + } + } +} + + +void SaveVertexNormals( void ) +{ + faceneighbor_t *fn; + int i, j; + dface_t *f; + CNormalList normalList; + + g_numvertnormalindices = 0; + + for( i = 0 ;inumedges; j++ ) + { + Vector vNormal; + if( fn->normal ) + { + vNormal = fn->normal[j]; + } + else + { + // original faces don't have normals + vNormal.Init( 0, 0, 0 ); + } + + if( g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES ) + { + Error( "g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES" ); + } + + g_vertnormalindices[g_numvertnormalindices] = (unsigned short)normalList.FindOrAddNormal( vNormal ); + g_numvertnormalindices++; + } + } + + if( normalList.m_Normals.Size() > MAX_MAP_VERTNORMALS ) + { + Error( "g_numvertnormals > MAX_MAP_VERTNORMALS" ); + } + + // Copy the list of unique vert normals into g_vertnormals. + g_numvertnormals = normalList.m_Normals.Size(); + memcpy( g_vertnormals, normalList.m_Normals.Base(), sizeof(g_vertnormals[0]) * normalList.m_Normals.Size() ); +} + +/* + ================================================================= + + LIGHTMAP SAMPLE GENERATION + + ================================================================= +*/ + + +//----------------------------------------------------------------------------- +// Purpose: Spits out an error message with information about a lightinfo_t. +// Input : s - Error message string. +// l - lightmap info struct. +//----------------------------------------------------------------------------- +void ErrorLightInfo(const char *s, lightinfo_t *l) +{ + texinfo_t *tex = &texinfo[l->face->texinfo]; + winding_t *w = WindingFromFace(&g_pFaces[l->facenum], l->modelorg); + + // + // Show the face center and material name if possible. + // + if (w != NULL) + { + // Don't exit, we'll try to recover... + Vector vecCenter; + WindingCenter(w, vecCenter); +// FreeWinding(w); + + Warning("%s at (%g, %g, %g)\n\tmaterial=%s\n", s, (double)vecCenter.x, (double)vecCenter.y, (double)vecCenter.z, TexDataStringTable_GetString( dtexdata[tex->texdata].nameStringTableID ) ); + } + // + // If not, just show the material name. + // + else + { + Warning("%s at (degenerate face)\n\tmaterial=%s\n", s, TexDataStringTable_GetString( dtexdata[tex->texdata].nameStringTableID )); + } +} + + + +void CalcFaceVectors(lightinfo_t *l) +{ + texinfo_t *tex; + int i, j; + + tex = &texinfo[l->face->texinfo]; + + // move into lightinfo_t + for (i=0 ; i<2 ; i++) + { + for (j=0 ; j<3 ; j++) + { + l->worldToLuxelSpace[i][j] = tex->lightmapVecsLuxelsPerWorldUnits[i][j]; + } + } + + //Solve[ { x * w00 + y * w01 + z * w02 - s == 0, x * w10 + y * w11 + z * w12 - t == 0, A * x + B * y + C * z + D == 0 }, { x, y, z } ] + //Rule(x,( C*s*w11 - B*s*w12 + B*t*w02 - C*t*w01 + D*w02*w11 - D*w01*w12) / (+ A*w01*w12 - A*w02*w11 + B*w02*w10 - B*w00*w12 + C*w00*w11 - C*w01*w10 )), + //Rule(y,( A*s*w12 - C*s*w10 + C*t*w00 - A*t*w02 + D*w00*w12 - D*w02*w10) / (+ A*w01*w12 - A*w02*w11 + B*w02*w10 - B*w00*w12 + C*w00*w11 - C*w01*w10 )), + //Rule(z,( B*s*w10 - A*s*w11 + A*t*w01 - B*t*w00 + D*w01*w10 - D*w00*w11) / (+ A*w01*w12 - A*w02*w11 + B*w02*w10 - B*w00*w12 + C*w00*w11 - C*w01*w10 )))) + + Vector luxelSpaceCross; + + luxelSpaceCross[0] = + tex->lightmapVecsLuxelsPerWorldUnits[1][1] * tex->lightmapVecsLuxelsPerWorldUnits[0][2] - + tex->lightmapVecsLuxelsPerWorldUnits[1][2] * tex->lightmapVecsLuxelsPerWorldUnits[0][1]; + luxelSpaceCross[1] = + tex->lightmapVecsLuxelsPerWorldUnits[1][2] * tex->lightmapVecsLuxelsPerWorldUnits[0][0] - + tex->lightmapVecsLuxelsPerWorldUnits[1][0] * tex->lightmapVecsLuxelsPerWorldUnits[0][2]; + luxelSpaceCross[2] = + tex->lightmapVecsLuxelsPerWorldUnits[1][0] * tex->lightmapVecsLuxelsPerWorldUnits[0][1] - + tex->lightmapVecsLuxelsPerWorldUnits[1][1] * tex->lightmapVecsLuxelsPerWorldUnits[0][0]; + + float det = -DotProduct( l->facenormal, luxelSpaceCross ); + if ( fabs( det ) < 1.0e-20 ) + { + Warning(" warning - face vectors parallel to face normal. bad lighting will be produced\n" ); + l->luxelOrigin = vec3_origin; + } + else + { + // invert the matrix + l->luxelToWorldSpace[0][0] = (l->facenormal[2] * l->worldToLuxelSpace[1][1] - l->facenormal[1] * l->worldToLuxelSpace[1][2]) / det; + l->luxelToWorldSpace[1][0] = (l->facenormal[1] * l->worldToLuxelSpace[0][2] - l->facenormal[2] * l->worldToLuxelSpace[0][1]) / det; + l->luxelOrigin[0] = -(l->facedist * luxelSpaceCross[0]) / det; + l->luxelToWorldSpace[0][1] = (l->facenormal[0] * l->worldToLuxelSpace[1][2] - l->facenormal[2] * l->worldToLuxelSpace[1][0]) / det; + l->luxelToWorldSpace[1][1] = (l->facenormal[2] * l->worldToLuxelSpace[0][0] - l->facenormal[0] * l->worldToLuxelSpace[0][2]) / det; + l->luxelOrigin[1] = -(l->facedist * luxelSpaceCross[1]) / det; + l->luxelToWorldSpace[0][2] = (l->facenormal[1] * l->worldToLuxelSpace[1][0] - l->facenormal[0] * l->worldToLuxelSpace[1][1]) / det; + l->luxelToWorldSpace[1][2] = (l->facenormal[0] * l->worldToLuxelSpace[0][1] - l->facenormal[1] * l->worldToLuxelSpace[0][0]) / det; + l->luxelOrigin[2] = -(l->facedist * luxelSpaceCross[2]) / det; + + // adjust for luxel offset + VectorMA( l->luxelOrigin, -tex->lightmapVecsLuxelsPerWorldUnits[0][3], l->luxelToWorldSpace[0], l->luxelOrigin ); + VectorMA( l->luxelOrigin, -tex->lightmapVecsLuxelsPerWorldUnits[1][3], l->luxelToWorldSpace[1], l->luxelOrigin ); + } + // compensate for org'd bmodels + VectorAdd (l->luxelOrigin, l->modelorg, l->luxelOrigin); +} + + + +winding_t *LightmapCoordWindingForFace( lightinfo_t *l ) +{ + int i; + winding_t *w; + + w = WindingFromFace( l->face, l->modelorg ); + + for (i = 0; i < w->numpoints; i++) + { + Vector2D coord; + WorldToLuxelSpace( l, w->p[i], coord ); + w->p[i].x = coord.x; + w->p[i].y = coord.y; + w->p[i].z = 0; + } + + return w; +} + + +void WriteCoordWinding (FILE *out, lightinfo_t *l, winding_t *w, Vector& color ) +{ + int i; + Vector pos; + + fprintf (out, "%i\n", w->numpoints); + for (i=0 ; inumpoints ; i++) + { + LuxelSpaceToWorld( l, w->p[i][0], w->p[i][1], pos ); + fprintf (out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + pos[0], + pos[1], + pos[2], + color[ 0 ] / 256, + color[ 1 ] / 256, + color[ 2 ] / 256 ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void DumpFaces( lightinfo_t *pLightInfo, int ndxFace ) +{ + static FileHandle_t out; + + // get face data + faceneighbor_t *fn = &faceneighbor[ndxFace]; + Vector ¢roid = face_centroids[ndxFace]; + + // disable threading (not a multi-threadable function!) + ThreadLock(); + + if( !out ) + { + // open the file + out = g_pFileSystem->Open( "face.txt", "w" ); + if( !out ) + return; + } + + // + // write out face + // + for( int ndxEdge = 0; ndxEdge < pLightInfo->face->numedges; ndxEdge++ ) + { +// int edge = dsurfedges[pLightInfo->face->firstedge+ndxEdge]; + + Vector p1, p2; + VectorAdd( dvertexes[EdgeVertex( pLightInfo->face, ndxEdge )].point, pLightInfo->modelorg, p1 ); + VectorAdd( dvertexes[EdgeVertex( pLightInfo->face, ndxEdge+1 )].point, pLightInfo->modelorg, p2 ); + + Vector &n1 = fn->normal[ndxEdge]; + Vector &n2 = fn->normal[(ndxEdge+1)%pLightInfo->face->numedges]; + + CmdLib_FPrintf( out, "3\n"); + + CmdLib_FPrintf(out, "%f %f %f %f %f %f\n", p1[0], p1[1], p1[2], n1[0] * 0.5 + 0.5, n1[1] * 0.5 + 0.5, n1[2] * 0.5 + 0.5 ); + + CmdLib_FPrintf(out, "%f %f %f %f %f %f\n", p2[0], p2[1], p2[2], n2[0] * 0.5 + 0.5, n2[1] * 0.5 + 0.5, n2[2] * 0.5 + 0.5 ); + + CmdLib_FPrintf(out, "%f %f %f %f %f %f\n", centroid[0] + pLightInfo->modelorg[0], + centroid[1] + pLightInfo->modelorg[1], + centroid[2] + pLightInfo->modelorg[2], + fn->facenormal[0] * 0.5 + 0.5, + fn->facenormal[1] * 0.5 + 0.5, + fn->facenormal[2] * 0.5 + 0.5 ); + + } + + // enable threading + ThreadUnlock(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool BuildFacesamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight ) +{ + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // ratio of world area / lightmap area + texinfo_t *pTex = &texinfo[pLightInfo->face->texinfo]; + pFaceLight->worldAreaPerLuxel = 1.0 / ( sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[0], + pTex->lightmapVecsLuxelsPerWorldUnits[0] ) ) * + sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[1], + pTex->lightmapVecsLuxelsPerWorldUnits[1] ) ) ); + + // + // quickly create samples and luxels (copy over samples) + // + pFaceLight->numsamples = width * height; + pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); + if( !pFaceLight->sample ) + return false; + + pFaceLight->numluxels = width * height; + pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); + if( !pFaceLight->luxel ) + return false; + + sample_t *pSamples = pFaceLight->sample; + Vector *pLuxels = pFaceLight->luxel; + + for( int t = 0; t < height; t++ ) + { + for( int s = 0; s < width; s++ ) + { + pSamples->s = s; + pSamples->t = t; + pSamples->coord[0] = s; + pSamples->coord[1] = t; + // unused but initialized anyway + pSamples->mins[0] = s - 0.5; + pSamples->mins[1] = t - 0.5; + pSamples->maxs[0] = s + 0.5; + pSamples->maxs[1] = t + 0.5; + pSamples->area = pFaceLight->worldAreaPerLuxel; + LuxelSpaceToWorld( pLightInfo, pSamples->coord[0], pSamples->coord[1], pSamples->pos ); + VectorCopy( pSamples->pos, *pLuxels ); + + pSamples++; + pLuxels++; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool BuildSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // build samples for a "face" + if( pLightInfo->face->dispinfo == -1 ) + { + return BuildFacesamplesAndLuxels_DoFast( pLightInfo, pFaceLight ); + } + // build samples for a "displacement" + else + { + return StaticDispMgr()->BuildDispSamplesAndLuxels_DoFast( pLightInfo, pFaceLight, ndxFace ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool BuildFacesamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight ) +{ + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // ratio of world area / lightmap area + texinfo_t *pTex = &texinfo[pLightInfo->face->texinfo]; + pFaceLight->worldAreaPerLuxel = 1.0 / ( sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[0], + pTex->lightmapVecsLuxelsPerWorldUnits[0] ) ) * + sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[1], + pTex->lightmapVecsLuxelsPerWorldUnits[1] ) ) ); + + // allocate a large number of samples for creation -- get copied later! + CUtlVector sampleData; + sampleData.SetCount( SINGLE_BRUSH_MAP * 2 ); + sample_t *samples = sampleData.Base(); + sample_t *pSamples = samples; + + // lightmap space winding + winding_t *pLightmapWinding = LightmapCoordWindingForFace( pLightInfo ); + + // + // build vector pointing along the lightmap cutting planes + // + Vector sNorm( 1.0f, 0.0f, 0.0f ); + Vector tNorm( 0.0f, 1.0f, 0.0f ); + + // sample center offset + float sampleOffset = ( do_centersamples ) ? 0.5 : 1.0; + + // + // clip the lightmap "spaced" winding by the lightmap cutting planes + // + winding_t *pWindingT1, *pWindingT2; + winding_t *pWindingS1, *pWindingS2; + float dist; + + for( int t = 0; t < height && pLightmapWinding; t++ ) + { + dist = t + sampleOffset; + + // lop off a sample in the t dimension + // hack - need a separate epsilon for lightmap space since ON_EPSILON is for texture space + ClipWindingEpsilon( pLightmapWinding, tNorm, dist, ON_EPSILON / 16.0f, &pWindingT1, &pWindingT2 ); + + for( int s = 0; s < width && pWindingT2; s++ ) + { + dist = s + sampleOffset; + + // lop off a sample in the s dimension, and put it in ws2 + // hack - need a separate epsilon for lightmap space since ON_EPSILON is for texture space + ClipWindingEpsilon( pWindingT2, sNorm, dist, ON_EPSILON / 16.0f, &pWindingS1, &pWindingS2 ); + + // + // s2 winding is a single sample worth of winding + // + if( pWindingS2 ) + { + // save the s, t positions + pSamples->s = s; + pSamples->t = t; + + // get the lightmap space area of ws2 and convert to world area + // and find the center (then convert it to 2D) + Vector center; + pSamples->area = WindingAreaAndBalancePoint( pWindingS2, center ) * pFaceLight->worldAreaPerLuxel; + pSamples->coord[0] = center.x; + pSamples->coord[1] = center.y; + + // find winding bounds (then convert it to 2D) + Vector minbounds, maxbounds; + WindingBounds( pWindingS2, minbounds, maxbounds ); + pSamples->mins[0] = minbounds.x; + pSamples->mins[1] = minbounds.y; + pSamples->maxs[0] = maxbounds.x; + pSamples->maxs[1] = maxbounds.y; + + // convert from lightmap space to world space + LuxelSpaceToWorld( pLightInfo, pSamples->coord[0], pSamples->coord[1], pSamples->pos ); + + if (g_bDumpPatches || (do_extra && pSamples->area < pFaceLight->worldAreaPerLuxel - EQUAL_EPSILON)) + { + // + // convert the winding from lightmaps space to world for debug rendering and sub-sampling + // + Vector worldPos; + for( int ndxPt = 0; ndxPt < pWindingS2->numpoints; ndxPt++ ) + { + LuxelSpaceToWorld( pLightInfo, pWindingS2->p[ndxPt].x, pWindingS2->p[ndxPt].y, worldPos ); + VectorCopy( worldPos, pWindingS2->p[ndxPt] ); + } + pSamples->w = pWindingS2; + } + else + { + // winding isn't needed, free it. + pSamples->w = NULL; + FreeWinding( pWindingS2 ); + } + + pSamples++; + } + + // + // if winding T2 still exists free it and set it equal S1 (the rest of the row minus the sample just created) + // + if( pWindingT2 ) + { + FreeWinding( pWindingT2 ); + } + + // clip the rest of "s" + pWindingT2 = pWindingS1; + } + + // + // if the original lightmap winding exists free it and set it equal to T1 (the rest of the winding not cut into samples) + // + if( pLightmapWinding ) + { + FreeWinding( pLightmapWinding ); + } + + if( pWindingT2 ) + { + FreeWinding( pWindingT2 ); + } + + pLightmapWinding = pWindingT1; + } + + // + // copy over samples + // + pFaceLight->numsamples = pSamples - samples; + pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); + if( !pFaceLight->sample ) + return false; + + memcpy( pFaceLight->sample, samples, pFaceLight->numsamples * sizeof( *pFaceLight->sample ) ); + + // supply a default sample normal (face normal - assumed flat) + for( int ndxSample = 0; ndxSample < pFaceLight->numsamples; ndxSample++ ) + { + Assert ( VectorLength ( pLightInfo->facenormal ) > 1.0e-20); + pFaceLight->sample[ndxSample].normal = pLightInfo->facenormal; + } + + // statistics - warning?! + if( pFaceLight->numsamples == 0 ) + { + Msg( "no samples %d\n", pLightInfo->face - g_pFaces ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Free any windings used by this facelight. It's currently assumed they're not needed again +//----------------------------------------------------------------------------- +void FreeSampleWindings( facelight_t *fl ) +{ + int i; + for (i = 0; i < fl->numsamples; i++) + { + if (fl->sample[i].w) + { + FreeWinding( fl->sample[i].w ); + fl->sample[i].w = NULL; + } + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: build the sample data for each lightmapped primitive type +//----------------------------------------------------------------------------- +bool BuildSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // build samples for a "face" + if( pLightInfo->face->dispinfo == -1 ) + { + return BuildFacesamples( pLightInfo, pFaceLight ); + } + // build samples for a "displacement" + else + { + return StaticDispMgr()->BuildDispSamples( pLightInfo, pFaceLight, ndxFace ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool BuildFaceLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight ) +{ + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // calcuate actual luxel points + pFaceLight->numluxels = width * height; + pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); + if( !pFaceLight->luxel ) + return false; + + for( int t = 0; t < height; t++ ) + { + for( int s = 0; s < width; s++ ) + { + LuxelSpaceToWorld( pLightInfo, s, t, pFaceLight->luxel[s+t*width] ); + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: build the luxels (find the luxel centers) for each lightmapped +// primitive type +//----------------------------------------------------------------------------- +bool BuildLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // build luxels for a "face" + if( pLightInfo->face->dispinfo == -1 ) + { + return BuildFaceLuxels( pLightInfo, pFaceLight ); + } + // build luxels for a "displacement" + else + { + return StaticDispMgr()->BuildDispLuxels( pLightInfo, pFaceLight, ndxFace ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: for each face, find the center of each luxel; for each texture +// aligned grid point, back project onto the plane and get the world +// xyz value of the sample point +// NOTE: ndxFace = facenum +//----------------------------------------------------------------------------- +void CalcPoints( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // debugging! + if( g_bDumpPatches ) + { + DumpFaces( pLightInfo, ndxFace ); + } + + // quick and dirty! + if( do_fast ) + { + if( !BuildSamplesAndLuxels_DoFast( pLightInfo, pFaceLight, ndxFace ) ) + { + Msg( "Face %d: (Fast)Error Building Samples and Luxels\n", ndxFace ); + } + return; + } + + // build the samples + if( !BuildSamples( pLightInfo, pFaceLight, ndxFace ) ) + { + Msg( "Face %d: Error Building Samples\n", ndxFace ); + } + + // build the luxels + if( !BuildLuxels( pLightInfo, pFaceLight, ndxFace ) ) + { + Msg( "Face %d: Error Building Luxels\n", ndxFace ); + } +} + + +//============================================================== + +directlight_t *activelights; +directlight_t *freelights; + +facelight_t facelight[MAX_MAP_FACES]; +int numdlights; + +/* + ================== + FindTargetEntity + ================== +*/ +entity_t *FindTargetEntity (char *target) +{ + int i; + char *n; + + for (i=0 ; iindex = numdlights++; + + VectorCopy( origin, dl->light.origin ); + + dl->light.cluster = ClusterFromPoint(dl->light.origin); + SetDLightVis( dl, dl->light.cluster ); + + dl->facenum = -1; + + if ( bAddToList ) + { + dl->next = activelights; + activelights = dl; + } + + return dl; +} + +void AddDLightToActiveList( directlight_t *dl ) +{ + dl->next = activelights; + activelights = dl; +} + +void FreeDLights() +{ + gSkyLight = NULL; + gAmbient = NULL; + + directlight_t *pNext; + for( directlight_t *pCur=activelights; pCur; pCur=pNext ) + { + pNext = pCur->next; + free( pCur ); + } + activelights = 0; +} + + +void SetDLightVis( directlight_t *dl, int cluster ) +{ + if (dl->pvs == NULL) + { + dl->pvs = (byte *)calloc( 1, (dvis->numclusters / 8) + 1 ); + } + + GetVisCache( -1, cluster, dl->pvs ); +} + +void MergeDLightVis( directlight_t *dl, int cluster ) +{ + if (dl->pvs == NULL) + { + SetDLightVis( dl, cluster ); + } + else + { + byte pvs[MAX_MAP_CLUSTERS/8]; + GetVisCache( -1, cluster, pvs ); + + // merge both vis graphs + for (int i = 0; i < (dvis->numclusters / 8) + 1; i++) + { + dl->pvs[i] |= pvs[i]; + } + } +} + + +/* + ============= + LightForKey + ============= +*/ +int LightForKey (entity_t *ent, char *key, Vector& intensity ) +{ + char *pLight; + + pLight = ValueForKey( ent, key ); + + return LightForString( pLight, intensity ); +} + +int LightForString( char *pLight, Vector& intensity ) +{ + double r, g, b, scaler; + int argCnt; + + VectorFill( intensity, 0 ); + + // scanf into doubles, then assign, so it is vec_t size independent + r = g = b = scaler = 0; + double r_hdr,g_hdr,b_hdr,scaler_hdr; + argCnt = sscanf ( pLight, "%lf %lf %lf %lf %lf %lf %lf %lf", + &r, &g, &b, &scaler, &r_hdr,&g_hdr,&b_hdr,&scaler_hdr ); + + if (argCnt==8) // 2 4-tuples + { + if (g_bHDR) + { + r=r_hdr; + g=g_hdr; + b=b_hdr; + scaler=scaler_hdr; + } + argCnt=4; + } + + // make sure light is legal + if( r < 0.0f || g < 0.0f || b < 0.0f || scaler < 0.0f ) + { + intensity.Init( 0.0f, 0.0f, 0.0f ); + return false; + } + + intensity[0] = pow( r / 255.0, 2.2 ) * 255; // convert to linear + + switch( argCnt) + { + case 1: + // The R,G,B values are all equal. + intensity[1] = intensity[2] = intensity[0]; + break; + + case 3: + case 4: + // Save the other two G,B values. + intensity[1] = pow( g / 255.0, 2.2 ) * 255; + intensity[2] = pow( b / 255.0, 2.2 ) * 255; + + // Did we also get an "intensity" scaler value too? + if ( argCnt == 4 ) + { + // Scale the normalized 0-255 R,G,B values by the intensity scaler + VectorScale( intensity, scaler / 255.0, intensity ); + } + break; + + default: + printf("unknown light specifier type - %s\n",pLight); + return false; + } + // scale up source lights by scaling factor + VectorScale( intensity, lightscale, intensity ); + return true; +} + +//----------------------------------------------------------------------------- +// Various parsing methods +//----------------------------------------------------------------------------- + +static void ParseLightGeneric( entity_t *e, directlight_t *dl ) +{ + entity_t *e2; + char *target; + Vector dest; + + dl->light.style = (int)FloatForKey (e, "style"); + + // get intenfsity + if( g_bHDR && LightForKey( e, "_lightHDR", dl->light.intensity ) ) + { + } + else + { + LightForKey( e, "_light", dl->light.intensity ); + } + + // check angle, targets + target = ValueForKey (e, "target"); + if (target[0]) + { // point towards target + e2 = FindTargetEntity (target); + if (!e2) + Warning("WARNING: light at (%i %i %i) has missing target\n", + (int)dl->light.origin[0], (int)dl->light.origin[1], (int)dl->light.origin[2]); + else + { + GetVectorForKey (e2, "origin", dest); + VectorSubtract (dest, dl->light.origin, dl->light.normal); + VectorNormalize (dl->light.normal); + } + } + else + { + // point down angle + Vector angles; + GetVectorForKey( e, "angles", angles ); + float pitch = FloatForKey (e, "pitch"); + float angle = FloatForKey (e, "angle"); + SetupLightNormalFromProps( QAngle( angles.x, angles.y, angles.z ), angle, pitch, dl->light.normal ); + } + if ( g_bHDR ) + VectorScale( dl->light.intensity, + FloatForKeyWithDefault( e, "_lightscaleHDR", 1.0 ), + dl->light.intensity ); +} + +static void SetLightFalloffParams( entity_t * e, directlight_t * dl ) +{ + float d50=FloatForKey( e, "_fifty_percent_distance" ); + dl->m_flStartFadeDistance = 0; + dl->m_flEndFadeDistance = - 1; + dl->m_flCapDist = 1.0e22; + if ( d50 ) + { + float d0 = FloatForKey( e, "_zero_percent_distance" ); + if ( d0 < d50 ) + { + Warning( "light has _fifty_percent_distance of %f but _zero_percent_distance of %f\n", d50, d0); + d0 = 2.0 * d50; + } + float a = 0, b = 1, c = 0; + if ( ! SolveInverseQuadraticMonotonic( 0, 1.0, d50, 2.0, d0, 256.0, a, b, c )) + { + Warning( "can't solve quadratic for light %f %f\n", d50, d0 ); + } + // it it possible that the parameters couldn't be used because of enforing monoticity. If so, rescale so at + // least the 50 percent value is right +// printf("50 percent=%f 0 percent=%f\n",d50,d0); +// printf("a=%f b=%f c=%f\n",a,b,c); + float v50 = c + d50 * ( b + d50 * a ); + float scale = 2.0 / v50; + a *= scale; + b *= scale; + c *= scale; +// printf("scaled=%f a=%f b=%f c=%f\n",scale,a,b,c); +// for(float d=0;d<1000;d+=20) +// printf("at %f, %f\n",d,1.0/(c+d*(b+d*a))); + dl->light.quadratic_attn = a; + dl->light.linear_attn = b; + dl->light.constant_attn = c; + + + + if ( IntForKey(e, "_hardfalloff" ) ) + { + dl->m_flEndFadeDistance = d0; + dl->m_flStartFadeDistance = 0.75 * d0 + 0.25 * d50; // start fading 3/4 way between 50 and 0. could allow adjust + } + else + { + // now, we will find the point at which the 1/x term reaches its maximum value, and + // prevent the light from going past there. If a user specifes an extreme falloff, the + // quadratic will start making the light brighter at some distance. We handle this by + // fading it from the minimum brightess point down to zero at 10x the minimum distance + if ( fabs( a ) > 0. ) + { + float flMax = b / ( - 2.0 * a ); // where f' = 0 + if ( flMax > 0.0 ) + { + dl->m_flCapDist = flMax; + dl->m_flStartFadeDistance = flMax; + dl->m_flEndFadeDistance = 10.0 * flMax; + } + } + } + } + else + { + dl->light.constant_attn = FloatForKey (e, "_constant_attn" ); + dl->light.linear_attn = FloatForKey (e, "_linear_attn" ); + dl->light.quadratic_attn = FloatForKey (e, "_quadratic_attn" ); + + dl->light.radius = FloatForKey (e, "_distance"); + + // clamp values to >= 0 + if ( dl->light.constant_attn < EQUAL_EPSILON ) + dl->light.constant_attn = 0; + + if ( dl->light.linear_attn < EQUAL_EPSILON ) + dl->light.linear_attn = 0; + + if ( dl->light.quadratic_attn < EQUAL_EPSILON ) + dl->light.quadratic_attn = 0; + + if ( dl->light.constant_attn < EQUAL_EPSILON && dl->light.linear_attn < EQUAL_EPSILON && dl->light.quadratic_attn < EQUAL_EPSILON ) + dl->light.constant_attn = 1; + + // scale intensity for unit 100 distance + float ratio = ( dl->light.constant_attn + 100 * dl->light.linear_attn + 100 * 100 * dl->light.quadratic_attn ); + if ( ratio > 0 ) + { + VectorScale( dl->light.intensity, ratio, dl->light.intensity ); + } + } +} + +static void ParseLightSpot( entity_t* e, directlight_t* dl ) +{ + Vector dest; + GetVectorForKey (e, "origin", dest ); + dl = AllocDLight( dest, true ); + + ParseLightGeneric( e, dl ); + + dl->light.type = emit_spotlight; + + dl->light.stopdot = FloatForKey (e, "_inner_cone"); + if (!dl->light.stopdot) + dl->light.stopdot = 10; + + dl->light.stopdot2 = FloatForKey (e, "_cone"); + if (!dl->light.stopdot2) + dl->light.stopdot2 = dl->light.stopdot; + if (dl->light.stopdot2 < dl->light.stopdot) + dl->light.stopdot2 = dl->light.stopdot; + + // This is a point light if stop dots are 180... + if ((dl->light.stopdot == 180) && (dl->light.stopdot2 == 180)) + { + dl->light.stopdot = dl->light.stopdot2 = 0; + dl->light.type = emit_point; + dl->light.exponent = 0; + } + else + { + // Clamp to 90, that's all DX8 can handle! + if (dl->light.stopdot > 90) + { + Warning("WARNING: light_spot at (%i %i %i) has inner angle larger than 90 degrees! Clamping to 90...\n", + (int)dl->light.origin[0], (int)dl->light.origin[1], (int)dl->light.origin[2]); + dl->light.stopdot = 90; + } + + if (dl->light.stopdot2 > 90) + { + Warning("WARNING: light_spot at (%i %i %i) has outer angle larger than 90 degrees! Clamping to 90...\n", + (int)dl->light.origin[0], (int)dl->light.origin[1], (int)dl->light.origin[2]); + dl->light.stopdot2 = 90; + } + + dl->light.stopdot2 = (float)cos(dl->light.stopdot2/180*M_PI); + dl->light.stopdot = (float)cos(dl->light.stopdot/180*M_PI); + dl->light.exponent = FloatForKey (e, "_exponent"); + } + + SetLightFalloffParams(e,dl); +} + +// NOTE: This is just a heuristic. It traces a finite number of rays to find sky +// NOTE: Full vis is necessary to make this 100% correct. +bool CanLeafTraceToSky( int iLeaf ) +{ + // UNDONE: Really want a point inside the leaf here. Center is a guess, may not be in the leaf + // UNDONE: Clip this to each plane bounding the leaf to guarantee + Vector center = vec3_origin; + for ( int i = 0; i < 3; i++ ) + { + center[i] = ( (float)(dleafs[iLeaf].mins[i] + dleafs[iLeaf].maxs[i]) ) * 0.5f; + } + + FourVectors center4, delta; + fltx4 fractionVisible; + for ( int j = 0; j < NUMVERTEXNORMALS; j+=4 ) + { + // search back to see if we can hit a sky brush + delta.LoadAndSwizzle( g_anorms[j], g_anorms[min( j+1, NUMVERTEXNORMALS-1 )], + g_anorms[min( j+2, NUMVERTEXNORMALS-1 )], g_anorms[min( j+3, NUMVERTEXNORMALS-1 )] ); + delta *= -MAX_TRACE_LENGTH; + delta += center4; + + // return true if any hits sky + TestLine_DoesHitSky ( center4, delta, &fractionVisible ); + if ( TestSignSIMD ( CmpGtSIMD ( fractionVisible, Four_Zeros ) ) ) + return true; + } + + return false; +} + +void BuildVisForLightEnvironment( void ) +{ + // Create the vis. + for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) + { + dleafs[iLeaf].flags &= ~( LEAF_FLAGS_SKY | LEAF_FLAGS_SKY2D ); + unsigned int iFirstFace = dleafs[iLeaf].firstleafface; + for ( int iLeafFace = 0; iLeafFace < dleafs[iLeaf].numleaffaces; ++iLeafFace ) + { + unsigned int iFace = dleaffaces[iFirstFace+iLeafFace]; + + texinfo_t &tex = texinfo[g_pFaces[iFace].texinfo]; + if ( tex.flags & SURF_SKY ) + { + if ( tex.flags & SURF_SKY2D ) + { + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY2D; + } + else + { + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; + } + MergeDLightVis( gSkyLight, dleafs[iLeaf].cluster ); + MergeDLightVis( gAmbient, dleafs[iLeaf].cluster ); + break; + } + } + } + + // Second pass to set flags on leaves that don't contain sky, but touch leaves that + // contain sky. + byte pvs[MAX_MAP_CLUSTERS / 8]; + + int nLeafBytes = (numleafs >> 3) + 1; + unsigned char *pLeafBits = (unsigned char *)stackalloc( nLeafBytes * sizeof(unsigned char) ); + unsigned char *pLeaf2DBits = (unsigned char *)stackalloc( nLeafBytes * sizeof(unsigned char) ); + memset( pLeafBits, 0, nLeafBytes ); + memset( pLeaf2DBits, 0, nLeafBytes ); + + for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) + { + // If this leaf has light (3d skybox) in it, then don't bother + if ( dleafs[iLeaf].flags & LEAF_FLAGS_SKY ) + continue; + + // Don't bother with this leaf if it's solid + if ( dleafs[iLeaf].contents & CONTENTS_SOLID ) + continue; + + // See what other leaves are visible from this leaf + GetVisCache( -1, dleafs[iLeaf].cluster, pvs ); + + // Now check out all other leaves + int nByte = iLeaf >> 3; + int nBit = 1 << ( iLeaf & 0x7 ); + for ( int iLeaf2 = 0; iLeaf2 < numleafs; ++iLeaf2 ) + { + if ( iLeaf2 == iLeaf ) + continue; + + if ( !(dleafs[iLeaf2].flags & ( LEAF_FLAGS_SKY | LEAF_FLAGS_SKY2D ) ) ) + continue; + + // Can this leaf see into the leaf with the sky in it? + if ( !PVSCheck( pvs, dleafs[iLeaf2].cluster ) ) + continue; + + if ( dleafs[iLeaf2].flags & LEAF_FLAGS_SKY2D ) + { + pLeaf2DBits[ nByte ] |= nBit; + } + if ( dleafs[iLeaf2].flags & LEAF_FLAGS_SKY ) + { + pLeafBits[ nByte ] |= nBit; + + // As soon as we know this leaf needs to draw the 3d skybox, we're done + break; + } + } + } + + // Must set the bits in a separate pass so as to not flood-fill LEAF_FLAGS_SKY everywhere + // pLeafbits is a bit array of all leaves that need to be marked as seeing sky + for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) + { + // If this leaf has light (3d skybox) in it, then don't bother + if ( dleafs[iLeaf].flags & LEAF_FLAGS_SKY ) + continue; + + // Don't bother with this leaf if it's solid + if ( dleafs[iLeaf].contents & CONTENTS_SOLID ) + continue; + + // Check to see if this is a 2D skybox leaf + if ( pLeaf2DBits[ iLeaf >> 3 ] & (1 << ( iLeaf & 0x7 )) ) + { + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY2D; + } + + // If this is a 3D skybox leaf, then we don't care if it was previously a 2D skybox leaf + if ( pLeafBits[ iLeaf >> 3 ] & (1 << ( iLeaf & 0x7 )) ) + { + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; + dleafs[iLeaf].flags &= ~LEAF_FLAGS_SKY2D; + } + else + { + // if radial vis was used on this leaf some of the portals leading + // to sky may have been culled. Try tracing to find sky. + if ( dleafs[iLeaf].flags & LEAF_FLAGS_RADIAL ) + { + if ( CanLeafTraceToSky(iLeaf) ) + { + // FIXME: Should make a version that checks if we hit 2D skyboxes.. oh well. + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; + } + } + } + } +} + +static char *ValueForKeyWithDefault (entity_t *ent, char *key, char *default_value = NULL) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + return ep->value; + return default_value; +} + +static void ParseLightEnvironment( entity_t* e, directlight_t* dl ) +{ + Vector dest; + GetVectorForKey (e, "origin", dest ); + dl = AllocDLight( dest, false ); + + ParseLightGeneric( e, dl ); + + char *angle_str=ValueForKeyWithDefault( e, "SunSpreadAngle" ); + if (angle_str) + { + g_SunAngularExtent=atof(angle_str); + g_SunAngularExtent=sin((M_PI/180.0)*g_SunAngularExtent); + printf("sun extent from map=%f\n",g_SunAngularExtent); + } + if ( !gSkyLight ) + { + // Sky light. + gSkyLight = dl; + dl->light.type = emit_skylight; + + // Sky ambient light. + gAmbient = AllocDLight( dl->light.origin, false ); + gAmbient->light.type = emit_skyambient; + if( g_bHDR && LightForKey( e, "_ambientHDR", gAmbient->light.intensity ) ) + { + // we have a valid HDR ambient light value + } + else if ( !LightForKey( e, "_ambient", gAmbient->light.intensity ) ) + { + VectorScale( dl->light.intensity, 0.5, gAmbient->light.intensity ); + } + if ( g_bHDR ) + { + VectorScale( gAmbient->light.intensity, + FloatForKeyWithDefault( e, "_AmbientScaleHDR", 1.0 ), + gAmbient->light.intensity ); + } + + BuildVisForLightEnvironment(); + + // Add sky and sky ambient lights to the list. + AddDLightToActiveList( gSkyLight ); + AddDLightToActiveList( gAmbient ); + } +} + +static void ParseLightPoint( entity_t* e, directlight_t* dl ) +{ + Vector dest; + GetVectorForKey (e, "origin", dest ); + dl = AllocDLight( dest, true ); + + ParseLightGeneric( e, dl ); + + dl->light.type = emit_point; + + SetLightFalloffParams(e,dl); +} + +/* + ============= + CreateDirectLights + ============= +*/ +#define DIRECT_SCALE (100.0*100.0) +void CreateDirectLights (void) +{ + unsigned i; + CPatch *p = NULL; + directlight_t *dl = NULL; + entity_t *e = NULL; + char *name; + Vector dest; + + numdlights = 0; + + FreeDLights(); + + // + // surfaces + // + unsigned int uiPatchCount = g_Patches.Count(); + for (i=0; i< uiPatchCount; i++) + { + p = &g_Patches.Element( i ); + + // skip parent patches + if (p->child1 != g_Patches.InvalidIndex() ) + continue; + + if (p->basearea < 1e-6) + continue; + + if( VectorAvg( p->baselight ) >= dlight_threshold ) + { + dl = AllocDLight( p->origin, true ); + + dl->light.type = emit_surface; + VectorCopy (p->normal, dl->light.normal); + Assert( VectorLength( p->normal ) > 1.0e-20 ); + // scale intensity by number of texture instances + VectorScale( p->baselight, lightscale * p->area * p->scale[0] * p->scale[1] / p->basearea, dl->light.intensity ); + + // scale to a range that results in actual light + VectorScale( dl->light.intensity, DIRECT_SCALE, dl->light.intensity ); + } + } + + // + // entities + // + for (i=0 ; i<(unsigned)num_entities ; i++) + { + e = &entities[i]; + name = ValueForKey (e, "classname"); + if (strncmp (name, "light", 5)) + continue; + + // Light_dynamic is actually a real entity; not to be included here... + if (!strcmp (name, "light_dynamic")) + continue; + + if (!strcmp (name, "light_spot")) + { + ParseLightSpot( e, dl ); + } + else if (!strcmp(name, "light_environment")) + { + ParseLightEnvironment( e, dl ); + } + else if (!strcmp(name, "light")) + { + ParseLightPoint( e, dl ); + } + else + { + qprintf( "unsupported light entity: \"%s\"\n", name ); + } + } + + qprintf ("%i direct lights\n", numdlights); + // exit(1); +} + +/* + ============= + ExportDirectLightsToWorldLights + ============= +*/ + +void ExportDirectLightsToWorldLights() +{ + directlight_t *dl; + + // In case the level has already been VRADed. + *pNumworldlights = 0; + + for (dl = activelights; dl != NULL; dl = dl->next ) + { + dworldlight_t *wl = &dworldlights[(*pNumworldlights)++]; + + if (*pNumworldlights > MAX_MAP_WORLDLIGHTS) + { + Error("too many lights %d / %d\n", *pNumworldlights, MAX_MAP_WORLDLIGHTS ); + } + + wl->cluster = dl->light.cluster; + wl->type = dl->light.type; + wl->style = dl->light.style; + VectorCopy( dl->light.origin, wl->origin ); + // FIXME: why does vrad want 0 to 255 and not 0 to 1?? + VectorScale( dl->light.intensity, (1.0 / 255.0), wl->intensity ); + VectorCopy( dl->light.normal, wl->normal ); + wl->stopdot = dl->light.stopdot; + wl->stopdot2 = dl->light.stopdot2; + wl->exponent = dl->light.exponent; + wl->radius = dl->light.radius; + wl->constant_attn = dl->light.constant_attn; + wl->linear_attn = dl->light.linear_attn; + wl->quadratic_attn = dl->light.quadratic_attn; + wl->flags = 0; + } +} + +/* + ============= + GatherSampleLight + ============= +*/ +#define NORMALFORMFACTOR 40.156979 // accumuated dot products for hemisphere + +#define CONSTANT_DOT (.7/2) + +#define NSAMPLES_SUN_AREA_LIGHT 30 // number of samples to take for an + // non-point sun light + +// Helper function - gathers light from sun (emit_skylight) +void GatherSampleSkyLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, int static_prop_index_to_ignore, + float flEpsilon ) +{ + bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; + bool force_fast = ( nLFlags & GATHERLFLAGS_FORCE_FAST ) != 0; + + fltx4 dot; + + if ( bIgnoreNormals ) + dot = ReplicateX4( CONSTANT_DOT ); + else + dot = NegSIMD( pNormals[0] * dl->light.normal ); + + dot = MaxSIMD( dot, Four_Zeros ); + int zeroMask = TestSignSIMD ( CmpEqSIMD( dot, Four_Zeros ) ); + if (zeroMask == 0xF) + return; + + int nsamples = 1; + if ( g_SunAngularExtent > 0.0f ) + { + nsamples = NSAMPLES_SUN_AREA_LIGHT; + if ( do_fast || force_fast ) + nsamples /= 4; + } + + fltx4 totalFractionVisible = Four_Zeros; + fltx4 fractionVisible = Four_Zeros; + + DirectionalSampler_t sampler; + + for ( int d = 0; d < nsamples; d++ ) + { + // determine visibility of skylight + // serach back to see if we can hit a sky brush + Vector delta; + VectorScale( dl->light.normal, -MAX_TRACE_LENGTH, delta ); + if ( d ) + { + // jitter light source location + Vector ofs = sampler.NextValue(); + ofs *= MAX_TRACE_LENGTH * g_SunAngularExtent; + delta += ofs; + } + FourVectors delta4; + delta4.DuplicateVector ( delta ); + delta4 += pos; + + TestLine_DoesHitSky ( pos, delta4, &fractionVisible, true, static_prop_index_to_ignore ); + + totalFractionVisible = AddSIMD ( totalFractionVisible, fractionVisible ); + } + + fltx4 seeAmount = MulSIMD ( totalFractionVisible, ReplicateX4 ( 1.0f / nsamples ) ); + out.m_flDot[0] = MulSIMD ( dot, seeAmount ); + out.m_flFalloff = Four_Ones; + out.m_flSunAmount = MulSIMD ( seeAmount, ReplicateX4( 10000.0f ) ); + for ( int i = 1; i < normalCount; i++ ) + { + if ( bIgnoreNormals ) + out.m_flDot[i] = ReplicateX4 ( CONSTANT_DOT ); + else + { + out.m_flDot[i] = NegSIMD( pNormals[i] * dl->light.normal ); + out.m_flDot[i] = MulSIMD( out.m_flDot[i], seeAmount ); + } + } +} + +// Helper function - gathers light from ambient sky light +void GatherSampleAmbientSkySSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, int static_prop_index_to_ignore, + float flEpsilon ) +{ + + bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; + bool force_fast = ( nLFlags & GATHERLFLAGS_FORCE_FAST ) != 0; + + fltx4 sumdot = Four_Zeros; + fltx4 ambient_intensity[NUM_BUMP_VECTS+1]; + fltx4 possibleHitCount[NUM_BUMP_VECTS+1]; + fltx4 dots[NUM_BUMP_VECTS+1]; + + for ( int i = 0; i < normalCount; i++ ) + { + ambient_intensity[i] = Four_Zeros; + possibleHitCount[i] = Four_Zeros; + } + + DirectionalSampler_t sampler; + int nsky_samples = NUMVERTEXNORMALS; + if (do_fast || force_fast ) + nsky_samples /= 4; + else + nsky_samples *= g_flSkySampleScale; + + for (int j = 0; j < nsky_samples; j++) + { + FourVectors anorm; + anorm.DuplicateVector( sampler.NextValue() ); + + if ( bIgnoreNormals ) + dots[0] = ReplicateX4( CONSTANT_DOT ); + else + dots[0] = NegSIMD( pNormals[0] * anorm ); + + fltx4 validity = CmpGtSIMD( dots[0], ReplicateX4( EQUAL_EPSILON ) ); + + // No possibility of anybody getting lit + if ( !TestSignSIMD( validity ) ) + continue; + + dots[0] = AndSIMD( validity, dots[0] ); + sumdot = AddSIMD( dots[0], sumdot ); + possibleHitCount[0] = AddSIMD( AndSIMD( validity, Four_Ones ), possibleHitCount[0] ); + + for ( int i = 1; i < normalCount; i++ ) + { + if ( bIgnoreNormals ) + dots[i] = ReplicateX4( CONSTANT_DOT ); + else + dots[i] = NegSIMD( pNormals[i] * anorm ); + fltx4 validity2 = CmpGtSIMD( dots[i], ReplicateX4 ( EQUAL_EPSILON ) ); + dots[i] = AndSIMD( validity2, dots[i] ); + possibleHitCount[i] = AddSIMD( AndSIMD( AndSIMD( validity, validity2 ), Four_Ones ), possibleHitCount[i] ); + } + + // search back to see if we can hit a sky brush + FourVectors delta = anorm; + delta *= -MAX_TRACE_LENGTH; + delta += pos; + FourVectors surfacePos = pos; + FourVectors offset = anorm; + offset *= -flEpsilon; + surfacePos -= offset; + + fltx4 fractionVisible = Four_Ones; + TestLine_DoesHitSky( surfacePos, delta, &fractionVisible, true, static_prop_index_to_ignore ); + for ( int i = 0; i < normalCount; i++ ) + { + fltx4 addedAmount = MulSIMD( fractionVisible, dots[i] ); + ambient_intensity[i] = AddSIMD( ambient_intensity[i], addedAmount ); + } + + } + + out.m_flFalloff = Four_Ones; + for ( int i = 0; i < normalCount; i++ ) + { + // now scale out the missing parts of the hemisphere of this bump basis vector + fltx4 factor = ReciprocalSIMD( possibleHitCount[0] ); + factor = MulSIMD( factor, possibleHitCount[i] ); + out.m_flDot[i] = MulSIMD( factor, sumdot ); + out.m_flDot[i] = ReciprocalSIMD( out.m_flDot[i] ); + out.m_flDot[i] = MulSIMD( ambient_intensity[i], out.m_flDot[i] ); + } + +} + +// Helper function - gathers light from area lights, spot lights, and point lights +void GatherSampleStandardLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, int static_prop_index_to_ignore, + float flEpsilon ) +{ + bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; + + FourVectors src; + src.DuplicateVector( vec3_origin ); + + if (dl->facenum == -1) + { + src.DuplicateVector( dl->light.origin ); + } + + // Find light vector + FourVectors delta; + delta = src; + delta -= pos; + fltx4 dist2 = delta.length2(); + fltx4 rpcDist = ReciprocalSqrtSIMD( dist2 ); + delta *= rpcDist; + fltx4 dist = SqrtEstSIMD( dist2 );//delta.VectorNormalize(); + + // Compute dot + fltx4 dot = ReplicateX4( (float) CONSTANT_DOT ); + if ( !bIgnoreNormals ) + dot = delta * pNormals[0]; + dot = MaxSIMD( Four_Zeros, dot ); + + // Affix dot to zero if past fade distz + bool bHasHardFalloff = ( dl->m_flEndFadeDistance > dl->m_flStartFadeDistance ); + if ( bHasHardFalloff ) + { + fltx4 notPastFadeDist = CmpLeSIMD ( dist, ReplicateX4 ( dl->m_flEndFadeDistance ) ); + dot = AndSIMD( dot, notPastFadeDist ); // dot = 0 if past fade distance + if ( !TestSignSIMD ( notPastFadeDist ) ) + return; + } + + dist = MaxSIMD( dist, Four_Ones ); + fltx4 falloffEvalDist = MinSIMD( dist, ReplicateX4( dl->m_flCapDist ) ); + + fltx4 constant, linear, quadratic; + fltx4 dot2, inCone, inFringe, mult; + FourVectors offset; + + switch (dl->light.type) + { + case emit_point: + constant = ReplicateX4( dl->light.constant_attn ); + linear = ReplicateX4( dl->light.linear_attn ); + quadratic = ReplicateX4( dl->light.quadratic_attn ); + + out.m_flFalloff = MulSIMD( falloffEvalDist, falloffEvalDist ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, quadratic ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, MulSIMD( linear, falloffEvalDist ) ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, constant ); + out.m_flFalloff = ReciprocalSIMD( out.m_flFalloff ); + break; + + case emit_surface: + dot2 = delta * dl->light.normal; + dot2 = NegSIMD( dot2 ); + + // Light behind surface yields zero dot + dot2 = MaxSIMD( Four_Zeros, dot2 ); + if ( TestSignSIMD( CmpEqSIMD( Four_Zeros, dot ) ) == 0xF ) + return; + + out.m_flFalloff = ReciprocalSIMD ( dist2 ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, dot2 ); + + // move the endpoint away from the surface by epsilon to prevent hitting the surface with the trace + offset.DuplicateVector ( dl->light.normal ); + offset *= DIST_EPSILON; + src += offset; + break; + + case emit_spotlight: + dot2 = delta * dl->light.normal; + dot2 = NegSIMD( dot2 ); + + // Affix dot2 to zero if outside light cone + inCone = CmpGtSIMD( dot2, ReplicateX4( dl->light.stopdot2 ) ); + if ( !TestSignSIMD ( inCone ) ) + return; + dot = AndSIMD( inCone, dot ); + + constant = ReplicateX4( dl->light.constant_attn ); + linear = ReplicateX4( dl->light.linear_attn ); + quadratic = ReplicateX4( dl->light.quadratic_attn ); + + out.m_flFalloff = MulSIMD( falloffEvalDist, falloffEvalDist ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, quadratic ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, MulSIMD( linear, falloffEvalDist ) ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, constant ); + out.m_flFalloff = ReciprocalSIMD( out.m_flFalloff ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, dot2 ); + + // outside the inner cone + inFringe = CmpLeSIMD( dot2, ReplicateX4( dl->light.stopdot ) ); + mult = ReplicateX4( dl->light.stopdot - dl->light.stopdot2 ); + mult = ReciprocalSIMD( mult ); + mult = MulSIMD( mult, SubSIMD( dot2, ReplicateX4( dl->light.stopdot2 ) ) ); + mult = MinSIMD( mult, Four_Ones ); + mult = MaxSIMD( mult, Four_Zeros ); + + // pow is fixed point, so this isn't the most accurate, but it doesn't need to be + if ( (dl->light.exponent != 0.0f ) && ( dl->light.exponent != 1.0f ) ) + mult = PowSIMD( mult, dl->light.exponent ); + + // if not in between inner and outer cones, mult by 1 + mult = AndSIMD( inFringe, mult ); + mult = AddSIMD( mult, AndNotSIMD( inFringe, Four_Ones ) ); + out.m_flFalloff = MulSIMD( mult, out.m_flFalloff ); + break; + + } + + // we may be in the fade region - modulate lighting by the fade curve + //float t = ( dist - dl->m_flStartFadeDistance ) / + // ( dl->m_flEndFadeDistance - dl->m_flStartFadeDistance ); + if ( bHasHardFalloff ) + { + fltx4 t = ReplicateX4( dl->m_flEndFadeDistance - dl->m_flStartFadeDistance ); + t = ReciprocalSIMD( t ); + t = MulSIMD( t, SubSIMD( dist, ReplicateX4( dl->m_flStartFadeDistance ) ) ); + + // clamp t to [0...1] + t = MinSIMD( t, Four_Ones ); + t = MaxSIMD( t, Four_Zeros ); + t = SubSIMD( Four_Ones, t ); + + // Using QuinticInterpolatingPolynomial, SSE-ified + // t * t * t *( t * ( t* 6.0 - 15.0 ) + 10.0 ) + mult = SubSIMD( MulSIMD( ReplicateX4( 6.0f ), t ), ReplicateX4( 15.0f ) ); + mult = AddSIMD( MulSIMD( mult, t ), ReplicateX4( 10.0f ) ); + mult = MulSIMD( MulSIMD( t, t), mult ); + mult = MulSIMD( t, mult ); + out.m_flFalloff = MulSIMD( mult, out.m_flFalloff ); + } + + // Raytrace for visibility function + fltx4 fractionVisible = Four_Ones; + TestLine( pos, src, &fractionVisible, static_prop_index_to_ignore); + dot = MulSIMD( fractionVisible, dot ); + out.m_flDot[0] = dot; + + for ( int i = 1; i < normalCount; i++ ) + { + if ( bIgnoreNormals ) + out.m_flDot[i] = ReplicateX4( (float) CONSTANT_DOT ); + else + { + out.m_flDot[i] = pNormals[i] * delta; + out.m_flDot[i] = MaxSIMD( Four_Zeros, out.m_flDot[i] ); + } + } +} + +// returns dot product with normal and delta +// dl - light +// pos - position of sample +// normal - surface normal of sample +// out.m_flDot[] - returned dot products with light vector and each normal +// out.m_flFalloff - amount of light falloff +void GatherSampleLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, + int static_prop_index_to_ignore, + float flEpsilon ) +{ + for ( int b = 0; b < normalCount; b++ ) + out.m_flDot[b] = Four_Zeros; + out.m_flFalloff = Four_Zeros; + out.m_flSunAmount = Four_Zeros; + Assert( normalCount <= (NUM_BUMP_VECTS+1) ); + + // skylights work fundamentally differently than normal lights + switch( dl->light.type ) + { + case emit_skylight: + GatherSampleSkyLightSSE( out, dl, facenum, pos, pNormals, normalCount, + iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); + break; + case emit_skyambient: + GatherSampleAmbientSkySSE( out, dl, facenum, pos, pNormals, normalCount, + iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); + break; + case emit_point: + case emit_surface: + case emit_spotlight: + GatherSampleStandardLightSSE( out, dl, facenum, pos, pNormals, normalCount, + iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); + break; + default: + Error ("Bad dl->light.type"); + return; + } + + // NOTE: Notice here that if the light is on the back side of the face + // (tested by checking the dot product of the face normal and the light position) + // we don't want it to contribute to *any* of the bumped lightmaps. It glows + // in disturbing ways if we don't do this. + out.m_flDot[0] = MaxSIMD ( out.m_flDot[0], Four_Zeros ); + fltx4 notZero = CmpGtSIMD( out.m_flDot[0], Four_Zeros ); + for ( int n = 1; n < normalCount; n++ ) + { + out.m_flDot[n] = MaxSIMD( out.m_flDot[n], Four_Zeros ); + out.m_flDot[n] = AndSIMD( out.m_flDot[n], notZero ); + } + +} + +/* + ============= + AddSampleToPatch + + Take the sample's collected light and + add it back into the apropriate patch + for the radiosity pass. + ============= +*/ +void AddSampleToPatch (sample_t *s, LightingValue_t& light, int facenum) +{ + CPatch *patch; + Vector mins, maxs; + int i; + + if (numbounce == 0) + return; + if( VectorAvg( light.m_vecLighting ) < 1) + return; + + // + // fixed the sample position and normal -- need to find the equiv pos, etc to set up + // patches + // + if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) + return; + + float radius = sqrt( s->area ) / 2.0; + + CPatch *pNextPatch = NULL; + for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + if (patch->sky) + continue; + + // skip patches with children + if ( patch->child1 != g_Patches.InvalidIndex() ) + continue; + + // see if the point is in this patch (roughly) + WindingBounds (patch->winding, mins, maxs); + + for (i=0 ; i<3 ; i++) + { + if (mins[i] > s->pos[i] + radius) + goto nextpatch; + if (maxs[i] < s->pos[i] - radius) + goto nextpatch; + } + + // add the sample to the patch + patch->samplearea += s->area; + VectorMA( patch->samplelight, s->area, light.m_vecLighting, patch->samplelight ); + + nextpatch:; + } + // don't worry if some samples don't find a patch +} + + +void GetPhongNormal( int facenum, Vector const& spot, Vector& phongnormal ) +{ + int j; + dface_t *f = &g_pFaces[facenum]; +// dplane_t *p = &dplanes[f->planenum]; + Vector facenormal, vspot; + + VectorCopy( dplanes[f->planenum].normal, facenormal ); + VectorCopy( facenormal, phongnormal ); + + if ( smoothing_threshold != 1 ) + { + faceneighbor_t *fn = &faceneighbor[facenum]; + + // Calculate modified point normal for surface + // Use the edge normals iff they are defined. Bend the surface towards the edge normal(s) + // Crude first attempt: find nearest edge normal and do a simple interpolation with facenormal. + // Second attempt: find edge points+center that bound the point and do a three-point triangulation(baricentric) + // Better third attempt: generate the point normals for all vertices and do baricentric triangulation. + + for (j=0 ; jnumedges ; j++) + { + Vector v1, v2; + //int e = dsurfedges[f->firstedge + j]; + //int e1 = dsurfedges[f->firstedge + ((j+f->numedges-1)%f->numedges)]; + //int e2 = dsurfedges[f->firstedge + ((j+1)%f->numedges)]; + + //edgeshare_t *es = &edgeshare[abs(e)]; + //edgeshare_t *es1 = &edgeshare[abs(e1)]; + //edgeshare_t *es2 = &edgeshare[abs(e2)]; + // dface_t *f2; + float a1, a2, aa, bb, ab; + int vert1, vert2; + + Vector& n1 = fn->normal[j]; + Vector& n2 = fn->normal[(j+1)%f->numedges]; + + /* + if (VectorCompare( n1, fn->facenormal ) + && VectorCompare( n2, fn->facenormal) ) + continue; + */ + + vert1 = EdgeVertex( f, j ); + vert2 = EdgeVertex( f, j+1 ); + + Vector& p1 = dvertexes[vert1].point; + Vector& p2 = dvertexes[vert2].point; + + // Build vectors from the middle of the face to the edge vertexes and the sample pos. + VectorSubtract( p1, face_centroids[facenum], v1 ); + VectorSubtract( p2, face_centroids[facenum], v2 ); + VectorSubtract( spot, face_centroids[facenum], vspot ); + aa = DotProduct( v1, v1 ); + bb = DotProduct( v2, v2 ); + ab = DotProduct( v1, v2 ); + a1 = (bb * DotProduct( v1, vspot ) - ab * DotProduct( vspot, v2 )) / (aa * bb - ab * ab); + a2 = (DotProduct( vspot, v2 ) - a1 * ab) / bb; + + // Test center to sample vector for inclusion between center to vertex vectors (Use dot product of vectors) + if ( a1 >= 0.0 && a2 >= 0.0) + { + // calculate distance from edge to pos + Vector temp; + float scale; + + // Interpolate between the center and edge normals based on sample position + scale = 1.0 - a1 - a2; + VectorScale( fn->facenormal, scale, phongnormal ); + VectorScale( n1, a1, temp ); + VectorAdd( phongnormal, temp, phongnormal ); + VectorScale( n2, a2, temp ); + VectorAdd( phongnormal, temp, phongnormal ); + Assert( VectorLength( phongnormal ) > 1.0e-20 ); + VectorNormalize( phongnormal ); + + /* + if (a1 > 1 || a2 > 1 || a1 + a2 > 1) + { + Msg("\n%.2f %.2f\n", a1, a2 ); + Msg("%.2f %.2f %.2f\n", v1[0], v1[1], v1[2] ); + Msg("%.2f %.2f %.2f\n", v2[0], v2[1], v2[2] ); + Msg("%.2f %.2f %.2f\n", vspot[0], vspot[1], vspot[2] ); + exit(1); + + a1 = 0; + } + */ + /* + phongnormal[0] = (((j + 1) & 4) != 0) * 255; + phongnormal[1] = (((j + 1) & 2) != 0) * 255; + phongnormal[2] = (((j + 1) & 1) != 0) * 255; + */ + return; + } + } + } +} + +void GetPhongNormal( int facenum, FourVectors const& spot, FourVectors& phongnormal ) +{ + int j; + dface_t *f = &g_pFaces[facenum]; + // dplane_t *p = &dplanes[f->planenum]; + Vector facenormal; + FourVectors vspot; + + VectorCopy( dplanes[f->planenum].normal, facenormal ); + phongnormal.DuplicateVector( facenormal ); + + FourVectors faceCentroid; + faceCentroid.DuplicateVector( face_centroids[facenum] ); + + if ( smoothing_threshold != 1 ) + { + faceneighbor_t *fn = &faceneighbor[facenum]; + + // Calculate modified point normal for surface + // Use the edge normals iff they are defined. Bend the surface towards the edge normal(s) + // Crude first attempt: find nearest edge normal and do a simple interpolation with facenormal. + // Second attempt: find edge points+center that bound the point and do a three-point triangulation(baricentric) + // Better third attempt: generate the point normals for all vertices and do baricentric triangulation. + + for ( j = 0; j < f->numedges; ++j ) + { + Vector v1, v2; + fltx4 a1, a2; + float aa, bb, ab; + int vert1, vert2; + + Vector& n1 = fn->normal[j]; + Vector& n2 = fn->normal[(j+1)%f->numedges]; + + vert1 = EdgeVertex( f, j ); + vert2 = EdgeVertex( f, j+1 ); + + Vector& p1 = dvertexes[vert1].point; + Vector& p2 = dvertexes[vert2].point; + + // Build vectors from the middle of the face to the edge vertexes and the sample pos. + VectorSubtract( p1, face_centroids[facenum], v1 ); + VectorSubtract( p2, face_centroids[facenum], v2 ); + //VectorSubtract( spot, face_centroids[facenum], vspot ); + vspot = spot; + vspot -= faceCentroid; + aa = DotProduct( v1, v1 ); + bb = DotProduct( v2, v2 ); + ab = DotProduct( v1, v2 ); + //a1 = (bb * DotProduct( v1, vspot ) - ab * DotProduct( vspot, v2 )) / (aa * bb - ab * ab); + a1 = ReciprocalSIMD( ReplicateX4( aa * bb - ab * ab ) ); + a1 = MulSIMD( a1, SubSIMD( MulSIMD( ReplicateX4( bb ), vspot * v1 ), MulSIMD( ReplicateX4( ab ), vspot * v2 ) ) ); + //a2 = (DotProduct( vspot, v2 ) - a1 * ab) / bb; + a2 = ReciprocalSIMD( ReplicateX4( bb ) ); + a2 = MulSIMD( a2, SubSIMD( vspot * v2, MulSIMD( a1, ReplicateX4( ab ) ) ) ); + + fltx4 resultMask = AndSIMD( CmpGeSIMD( a1, Four_Zeros ), CmpGeSIMD( a2, Four_Zeros ) ); + + if ( !TestSignSIMD( resultMask ) ) + continue; + + // Store the old phong normal to avoid overwriting already computed phong normals + FourVectors oldPhongNormal = phongnormal; + + // calculate distance from edge to pos + FourVectors temp; + fltx4 scale; + + // Interpolate between the center and edge normals based on sample position + scale = SubSIMD( SubSIMD( Four_Ones, a1 ), a2 ); + phongnormal.DuplicateVector( fn->facenormal ); + phongnormal *= scale; + temp.DuplicateVector( n1 ); + temp *= a1; + phongnormal += temp; + temp.DuplicateVector( n2 ); + temp *= a2; + phongnormal += temp; + + // restore the old phong normals + phongnormal.x = AddSIMD( AndSIMD( resultMask, phongnormal.x ), AndNotSIMD( resultMask, oldPhongNormal.x ) ); + phongnormal.y = AddSIMD( AndSIMD( resultMask, phongnormal.y ), AndNotSIMD( resultMask, oldPhongNormal.y ) ); + phongnormal.z = AddSIMD( AndSIMD( resultMask, phongnormal.z ), AndNotSIMD( resultMask, oldPhongNormal.z ) ); + } + + phongnormal.VectorNormalize(); + } +} + + + +int GetVisCache( int lastoffset, int cluster, byte *pvs ) +{ + // get the PVS for the pos to limit the number of checks + if ( !visdatasize ) + { + memset (pvs, 255, (dvis->numclusters+7)/8 ); + lastoffset = -1; + } + else + { + if (cluster < 0) + { + // Error, point embedded in wall + // sampled[0][1] = 255; + memset (pvs, 255, (dvis->numclusters+7)/8 ); + lastoffset = -1; + } + else + { + int thisoffset = dvis->bitofs[ cluster ][DVIS_PVS]; + if ( thisoffset != lastoffset ) + { + if ( thisoffset == -1 ) + { + Error ("visofs == -1"); + } + + DecompressVis (&dvisdata[thisoffset], pvs); + } + lastoffset = thisoffset; + } + } + return lastoffset; +} + + +void BuildPatchLights( int facenum ); + +void DumpSamples( int ndxFace, facelight_t *pFaceLight ) +{ + ThreadLock(); + + dface_t *pFace = &g_pFaces[ndxFace]; + if( pFace ) + { + bool bBumpped = ( ( texinfo[pFace->texinfo].flags & SURF_BUMPLIGHT ) != 0 ); + + for( int iStyle = 0; iStyle < 4; ++iStyle ) + { + if( pFace->styles[iStyle] != 255 ) + { + for ( int iBump = 0; iBump < 4; ++iBump ) + { + if ( iBump == 0 || ( iBump > 0 && bBumpped ) ) + { + for( int iSample = 0; iSample < pFaceLight->numsamples; ++iSample ) + { + sample_t *pSample = &pFaceLight->sample[iSample]; + WriteWinding( pFileSamples[iStyle][iBump], pSample->w, pFaceLight->light[iStyle][iBump][iSample].m_vecLighting ); + if( bDumpNormals ) + { + WriteNormal( pFileSamples[iStyle][iBump], pSample->pos, pSample->normal, 15.0f, pSample->normal * 255.0f ); + } + } + } + } + } + } + } + + ThreadUnlock(); +} + + +//----------------------------------------------------------------------------- +// Allocates light sample data +//----------------------------------------------------------------------------- +static inline void AllocateLightstyleSamples( facelight_t* fl, int styleIndex, int numnormals ) +{ + for (int n = 0; n < numnormals; ++n) + { + fl->light[styleIndex][n] = ( LightingValue_t* )calloc( fl->numsamples, sizeof(LightingValue_t ) ); + } +} + + +//----------------------------------------------------------------------------- +// Used to find an existing lightstyle on a face +//----------------------------------------------------------------------------- +static inline int FindLightstyle( dface_t* f, int lightstyle ) +{ + for (int k = 0; k < MAXLIGHTMAPS; k++) + { + if (f->styles[k] == lightstyle) + return k; + } + + return -1; +} + +static int FindOrAllocateLightstyleSamples( dface_t* f, facelight_t *fl, int lightstyle, int numnormals ) +{ + // Search the lightstyles associated with the face for a match + int k; + for (k = 0; k < MAXLIGHTMAPS; k++) + { + if (f->styles[k] == lightstyle) + break; + + // Found an empty entry, we can use it for a new lightstyle + if (f->styles[k] == 255) + { + AllocateLightstyleSamples( fl, k, numnormals ); + f->styles[k] = lightstyle; + break; + } + } + + // Check for overflow + if (k >= MAXLIGHTMAPS) + return -1; + + return k; +} + + +//----------------------------------------------------------------------------- +// Compute the illumination point + normal for the sample +//----------------------------------------------------------------------------- +static void ComputeIlluminationPointAndNormalsSSE( lightinfo_t const& l, FourVectors const &pos, FourVectors const &norm, SSE_SampleInfo_t* pInfo, int numSamples ) +{ + + Vector v[4]; + + pInfo->m_Points = pos; + bool computeNormals = ( pInfo->m_NormalCount > 1 && ( pInfo->m_IsDispFace || !l.isflat ) ); + + // FIXME: move sample point off the surface a bit, this is done so that + // light sampling will not be affected by a bug where raycasts will + // intersect with the face being lit. We really should just have that + // logic in GatherSampleLight + FourVectors faceNormal; + faceNormal.DuplicateVector( l.facenormal ); + pInfo->m_Points += faceNormal; + + if ( pInfo->m_IsDispFace ) + { + pInfo->m_PointNormals[0] = norm; + } + else if ( !l.isflat ) + { + // If the face isn't flat, use a phong-based normal instead + FourVectors modelorg; + modelorg.DuplicateVector( l.modelorg ); + FourVectors vecSample = pos; + vecSample -= modelorg; + GetPhongNormal( pInfo->m_FaceNum, vecSample, pInfo->m_PointNormals[0] ); + } + + if ( computeNormals ) + { + Vector bv[4][NUM_BUMP_VECTS]; + for ( int i = 0; i < 4; ++i ) + { + // TODO: using Vec may slow things down a bit + GetBumpNormals( pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[0], + pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[1], + l.facenormal, pInfo->m_PointNormals[0].Vec( i ), bv[i] ); + } + for ( int b = 0; b < NUM_BUMP_VECTS; ++b ) + { + pInfo->m_PointNormals[b+1].LoadAndSwizzle ( bv[0][b], bv[1][b], bv[2][b], bv[3][b] ); + } + } + + // TODO: this may slow things down a bit ( using Vec ) + for ( int i = 0; i < 4; ++i ) + pInfo->m_Clusters[i] = ClusterFromPoint( pos.Vec( i ) ); +} + +//----------------------------------------------------------------------------- +// Iterates over all lights and computes lighting at up to 4 sample points +//----------------------------------------------------------------------------- +static void GatherSampleLightAt4Points( SSE_SampleInfo_t& info, int sampleIdx, int numSamples ) +{ + SSE_sampleLightOutput_t out; + + // Iterate over all direct lights and add them to the particular sample + for (directlight_t *dl = activelights; dl != NULL; dl = dl->next) + { + // is this lights cluster visible? + fltx4 dotMask = Four_Zeros; + bool skipLight = true; + for( int s = 0; s < numSamples; s++ ) + { + if( PVSCheck( dl->pvs, info.m_Clusters[s] ) ) + { + dotMask = SetComponentSIMD( dotMask, s, 1.0f ); + skipLight = false; + } + } + if ( skipLight ) + continue; + + GatherSampleLightSSE( out, dl, info.m_FaceNum, info.m_Points, info.m_PointNormals, info.m_NormalCount, info.m_iThread ); + + // Apply the PVS check filter and compute falloff x dot + fltx4 fxdot[NUM_BUMP_VECTS + 1]; + skipLight = true; + for ( int b = 0; b < info.m_NormalCount; b++ ) + { + fxdot[b] = MulSIMD( out.m_flDot[b], dotMask ); + fxdot[b] = MulSIMD( fxdot[b], out.m_flFalloff ); + if ( !IsAllZeros( fxdot[b] ) ) + { + skipLight = false; + } + } + if ( skipLight ) + continue; + + // Figure out the lightstyle for this particular sample + int lightStyleIndex = FindOrAllocateLightstyleSamples( info.m_pFace, info.m_pFaceLight, + dl->light.style, info.m_NormalCount ); + if (lightStyleIndex < 0) + { + if (info.m_WarnFace != info.m_FaceNum) + { + Warning ("\nWARNING: Too many light styles on a face at (%f, %f, %f)\n", + info.m_Points.x.m128_f32[0], info.m_Points.y.m128_f32[0], info.m_Points.z.m128_f32[0] ); + info.m_WarnFace = info.m_FaceNum; + } + continue; + } + + // pLightmaps is an array of the lightmaps for each normal direction, + // here's where the result of the sample gathering goes + LightingValue_t** pLightmaps = info.m_pFaceLight->light[lightStyleIndex]; + + // Incremental lighting only cares about lightstyle zero + if( g_pIncremental && (dl->light.style == 0) ) + { + for ( int i = 0; i < numSamples; i++ ) + { + g_pIncremental->AddLightToFace( dl->m_IncrementalID, info.m_FaceNum, sampleIdx + i, + info.m_LightmapSize, SubFloat( fxdot[0], i ), info.m_iThread ); + } + } + + for( int n = 0; n < info.m_NormalCount; ++n ) + { + for ( int i = 0; i < numSamples; i++ ) + { + pLightmaps[n][sampleIdx + i].AddLight( SubFloat( fxdot[n], i ), dl->light.intensity, SubFloat( out.m_flSunAmount, i ) ); + } + } + } +} + + + +//----------------------------------------------------------------------------- +// Iterates over all lights and computes lighting at a sample point +//----------------------------------------------------------------------------- +static void ResampleLightAt4Points( SSE_SampleInfo_t& info, int lightStyleIndex, int flags, LightingValue_t pLightmap[4][NUM_BUMP_VECTS+1] ) +{ + SSE_sampleLightOutput_t out; + + // Clear result + for ( int i = 0; i < 4; ++i ) + { + for ( int n = 0; n < info.m_NormalCount; ++n ) + { + pLightmap[i][n].Zero(); + } + } + + // Iterate over all direct lights and add them to the particular sample + for (directlight_t *dl = activelights; dl != NULL; dl = dl->next) + { + if ((flags & AMBIENT_ONLY) && (dl->light.type != emit_skyambient)) + continue; + + if ((flags & NON_AMBIENT_ONLY) && (dl->light.type == emit_skyambient)) + continue; + + // Only add contributions that match the lightstyle + Assert( lightStyleIndex <= MAXLIGHTMAPS ); + Assert( info.m_pFace->styles[lightStyleIndex] != 255 ); + if (dl->light.style != info.m_pFace->styles[lightStyleIndex]) + continue; + + // is this lights cluster visible? + fltx4 dotMask = Four_Zeros; + bool skipLight = true; + for( int s = 0; s < 4; s++ ) + { + if( PVSCheck( dl->pvs, info.m_Clusters[s] ) ) + { + dotMask = SetComponentSIMD( dotMask, s, 1.0f ); + skipLight = false; + } + } + if ( skipLight ) + continue; + + // NOTE: Notice here that if the light is on the back side of the face + // (tested by checking the dot product of the face normal and the light position) + // we don't want it to contribute to *any* of the bumped lightmaps. It glows + // in disturbing ways if we don't do this. + GatherSampleLightSSE( out, dl, info.m_FaceNum, info.m_Points, info.m_PointNormals, info.m_NormalCount, info.m_iThread ); + + // Apply the PVS check filter and compute falloff x dot + fltx4 fxdot[NUM_BUMP_VECTS + 1]; + for ( int b = 0; b < info.m_NormalCount; b++ ) + { + fxdot[b] = MulSIMD( out.m_flFalloff, out.m_flDot[b] ); + fxdot[b] = MulSIMD( fxdot[b], dotMask ); + } + + // Compute the contributions to each of the bumped lightmaps + // The first sample is for non-bumped lighting. + // The other sample are for bumpmapping. + for( int i = 0; i < 4; ++i ) + { + for( int n = 0; n < info.m_NormalCount; ++n ) + { + pLightmap[i][n].AddLight( SubFloat( fxdot[n], i ), dl->light.intensity, SubFloat( out.m_flSunAmount, i ) ); + } + } + } +} + +bool PointsInWinding ( FourVectors const & point, winding_t *w, int &invalidBits ) +{ + FourVectors edge, toPt, cross, testCross, p0, p1; + fltx4 invalidMask; + + // + // get the first normal to test + // + p0.DuplicateVector( w->p[0] ); + p1.DuplicateVector( w->p[1] ); + toPt = point; + toPt -= p0; + edge = p1; + edge -= p0; + testCross = edge ^ toPt; + testCross.VectorNormalizeFast(); + + for( int ndxPt = 1; ndxPt < w->numpoints; ndxPt++ ) + { + p0.DuplicateVector( w->p[ndxPt] ); + p1.DuplicateVector( w->p[(ndxPt+1)%w->numpoints] ); + toPt = point; + toPt -= p0; + edge = p1; + edge -= p0; + cross = edge ^ toPt; + cross.VectorNormalizeFast(); + + fltx4 dot = cross * testCross; + invalidMask = OrSIMD( invalidMask, CmpLtSIMD( dot, Four_Zeros ) ); + + invalidBits = TestSignSIMD ( invalidMask ); + if ( invalidBits == 0xF ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Perform supersampling at a particular point +//----------------------------------------------------------------------------- +static int SupersampleLightAtPoint( lightinfo_t& l, SSE_SampleInfo_t& info, + int sampleIndex, int lightStyleIndex, LightingValue_t *pLight, int flags ) +{ + sample_t& sample = info.m_pFaceLight->sample[sampleIndex]; + + // Get the position of the original sample in lightmapspace + Vector2D temp; + WorldToLuxelSpace( &l, sample.pos, temp ); + Vector sampleLightOrigin( temp[0], temp[1], 0.0f ); + + // Some parameters related to supersampling + float sampleWidth = ( flags & NON_AMBIENT_ONLY ) ? 4 : 2; + float cscale = 1.0f / sampleWidth; + float csshift = -((sampleWidth - 1) * cscale) / 2.0; + + // Clear out the light values + for (int i = 0; i < info.m_NormalCount; ++i ) + pLight[i].Zero(); + + int subsampleCount = 0; + + FourVectors superSampleNormal; + superSampleNormal.DuplicateVector( sample.normal ); + + FourVectors superSampleLightCoord; + FourVectors superSamplePosition; + + if ( flags & NON_AMBIENT_ONLY ) + { + float aRow[4]; + for ( int coord = 0; coord < 4; ++coord ) + aRow[coord] = csshift + coord * cscale; + fltx4 sseRow = LoadUnalignedSIMD( aRow ); + + for (int s = 0; s < 4; ++s) + { + // make sure the coordinate is inside of the sample's winding and when normalizing + // below use the number of samples used, not just numsamples and some of them + // will be skipped if they are not inside of the winding + superSampleLightCoord.DuplicateVector( sampleLightOrigin ); + superSampleLightCoord.x = AddSIMD( superSampleLightCoord.x, ReplicateX4( aRow[s] ) ); + superSampleLightCoord.y = AddSIMD( superSampleLightCoord.y, sseRow ); + + // Figure out where the supersample exists in the world, and make sure + // it lies within the sample winding + LuxelSpaceToWorld( &l, superSampleLightCoord[0], superSampleLightCoord[1], superSamplePosition ); + + // A winding should exist only if the sample wasn't a uniform luxel, or if g_bDumpPatches is true. + int invalidBits = 0; + if ( sample.w && !PointsInWinding( superSamplePosition, sample.w, invalidBits ) ) + continue; + + // Compute the super-sample illumination point and normal + // We're assuming the flat normal is the same for all supersamples + ComputeIlluminationPointAndNormalsSSE( l, superSamplePosition, superSampleNormal, &info, 4 ); + + // Resample the non-ambient light at this point... + LightingValue_t result[4][NUM_BUMP_VECTS+1]; + ResampleLightAt4Points( info, lightStyleIndex, NON_AMBIENT_ONLY, result ); + + // Got more subsamples + for ( int i = 0; i < 4; i++ ) + { + if ( !( ( invalidBits >> i ) & 0x1 ) ) + { + for ( int n = 0; n < info.m_NormalCount; ++n ) + { + pLight[n].AddLight( result[i][n] ); + } + ++subsampleCount; + } + } + } + } + else + { + FourVectors superSampleOffsets; + superSampleOffsets.LoadAndSwizzle( Vector( csshift, csshift, 0 ), Vector( csshift, csshift + cscale, 0), + Vector( csshift + cscale, csshift, 0 ), Vector( csshift + cscale, csshift + cscale, 0 ) ); + superSampleLightCoord.DuplicateVector( sampleLightOrigin ); + superSampleLightCoord += superSampleOffsets; + + LuxelSpaceToWorld( &l, superSampleLightCoord[0], superSampleLightCoord[1], superSamplePosition ); + + int invalidBits = 0; + if ( sample.w && !PointsInWinding( superSamplePosition, sample.w, invalidBits ) ) + return 0; + + ComputeIlluminationPointAndNormalsSSE( l, superSamplePosition, superSampleNormal, &info, 4 ); + + LightingValue_t result[4][NUM_BUMP_VECTS+1]; + ResampleLightAt4Points( info, lightStyleIndex, AMBIENT_ONLY, result ); + + // Got more subsamples + for ( int i = 0; i < 4; i++ ) + { + if ( !( ( invalidBits >> i ) & 0x1 ) ) + { + for ( int n = 0; n < info.m_NormalCount; ++n ) + { + pLight[n].AddLight( result[i][n] ); + } + ++subsampleCount; + } + } + } + + return subsampleCount; +} + + +//----------------------------------------------------------------------------- +// Compute gradients of a lightmap +//----------------------------------------------------------------------------- +static void ComputeLightmapGradients( SSE_SampleInfo_t& info, bool const* pHasProcessedSample, + float* pIntensity, float* gradient ) +{ + int w = info.m_LightmapWidth; + int h = info.m_LightmapHeight; + facelight_t* fl = info.m_pFaceLight; + + for (int i=0 ; inumsamples ; i++) + { + // Don't supersample the same sample twice + if (pHasProcessedSample[i]) + continue; + + gradient[i] = 0.0f; + sample_t& sample = fl->sample[i]; + + // Choose the maximum gradient of all bumped lightmap intensities + for ( int n = 0; n < info.m_NormalCount; ++n ) + { + int j = n * info.m_LightmapSize + sample.s + sample.t * w; + + if (sample.t > 0) + { + if (sample.s > 0) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-1-w] ) ); + gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-w] ) ); + if (sample.s < w-1) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+1-w] ) ); + } + if (sample.t < h-1) + { + if (sample.s > 0) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-1+w] ) ); + gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+w] ) ); + if (sample.s < w-1) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+1+w] ) ); + } + if (sample.s > 0) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-1] ) ); + if (sample.s < w-1) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+1] ) ); + } + } +} + +//----------------------------------------------------------------------------- +// ComputeLuxelIntensity... +//----------------------------------------------------------------------------- +static inline void ComputeLuxelIntensity( SSE_SampleInfo_t& info, int sampleIdx, + LightingValue_t **ppLightSamples, float* pSampleIntensity ) +{ + // Compute a separate intensity for each + sample_t& sample = info.m_pFaceLight->sample[sampleIdx]; + int destIdx = sample.s + sample.t * info.m_LightmapWidth; + for (int n = 0; n < info.m_NormalCount; ++n) + { + float intensity = ppLightSamples[n][sampleIdx].Intensity(); + + // convert to a linear perception space + pSampleIntensity[n * info.m_LightmapSize + destIdx] = pow( intensity / 256.0, 1.0 / 2.2 ); + } +} + +//----------------------------------------------------------------------------- +// Compute the maximum intensity based on all bumped lighting +//----------------------------------------------------------------------------- +static void ComputeSampleIntensities( SSE_SampleInfo_t& info, LightingValue_t **ppLightSamples, float* pSampleIntensity ) +{ + for (int i=0; inumsamples; i++) + { + ComputeLuxelIntensity( info, i, ppLightSamples, pSampleIntensity ); + } +} + +//----------------------------------------------------------------------------- +// Perform supersampling on a particular lightstyle +//----------------------------------------------------------------------------- +static void BuildSupersampleFaceLights( lightinfo_t& l, SSE_SampleInfo_t& info, int lightstyleIndex ) +{ + LightingValue_t pAmbientLight[NUM_BUMP_VECTS+1]; + LightingValue_t pDirectLight[NUM_BUMP_VECTS+1]; + + // This is used to make sure we don't supersample a light sample more than once + int processedSampleSize = info.m_LightmapSize * sizeof(bool); + bool* pHasProcessedSample = (bool*)stackalloc( processedSampleSize ); + memset( pHasProcessedSample, 0, processedSampleSize ); + + // This is used to compute a simple gradient computation of the light samples + // We're going to store the maximum intensity of all bumped samples at each sample location + float* pGradient = (float*)stackalloc( info.m_pFaceLight->numsamples * sizeof(float) ); + float* pSampleIntensity = (float*)stackalloc( info.m_NormalCount * info.m_LightmapSize * sizeof(float) ); + + // Compute the maximum intensity of all lighting associated with this lightstyle + // for all bumped lighting + LightingValue_t **ppLightSamples = info.m_pFaceLight->light[lightstyleIndex]; + ComputeSampleIntensities( info, ppLightSamples, pSampleIntensity ); + + Vector *pVisualizePass = NULL; + if (debug_extra) + { + int visualizationSize = info.m_pFaceLight->numsamples * sizeof(Vector); + pVisualizePass = (Vector*)stackalloc( visualizationSize ); + memset( pVisualizePass, 0, visualizationSize ); + } + + // What's going on here is that we're looking for large lighting discontinuities + // (large light intensity gradients) as a clue that we should probably be supersampling + // in that area. Because the supersampling operation will cause lighting changes, + // we've found that it's good to re-check the gradients again and see if any other + // areas should be supersampled as a result of the previous pass. Keep going + // until all the gradients are reasonable or until we hit a max number of passes + bool do_anotherpass = true; + int pass = 1; + while (do_anotherpass && pass <= extrapasses) + { + // Look for lighting discontinuities to see what we should be supersampling + ComputeLightmapGradients( info, pHasProcessedSample, pSampleIntensity, pGradient ); + + do_anotherpass = false; + + // Now check all of the samples and supersample those which we have + // marked as having high gradients + for (int i=0 ; inumsamples; ++i) + { + // Don't supersample the same sample twice + if (pHasProcessedSample[i]) + continue; + + // Don't supersample if the lighting is pretty uniform near the sample + if (pGradient[i] < 0.0625) + continue; + + // Joy! We're supersampling now, and we therefore must do another pass + // Also, we need never bother with this sample again + pHasProcessedSample[i] = true; + do_anotherpass = true; + + if (debug_extra) + { + // Mark the little visualization bitmap with a color indicating + // which pass it was updated on. + pVisualizePass[i][0] = (pass & 1) * 255; + pVisualizePass[i][1] = (pass & 2) * 128; + pVisualizePass[i][2] = (pass & 4) * 64; + } + + // Supersample the ambient light for each bump direction vector + int ambientSupersampleCount = SupersampleLightAtPoint( l, info, i, lightstyleIndex, pAmbientLight, AMBIENT_ONLY ); + + // Supersample the non-ambient light for each bump direction vector + int directSupersampleCount = SupersampleLightAtPoint( l, info, i, lightstyleIndex, pDirectLight, NON_AMBIENT_ONLY ); + + // Because of sampling problems, small area triangles may have no samples. + // In this case, just use what we already have + if ( ambientSupersampleCount > 0 && directSupersampleCount > 0 ) + { + // Add the ambient + directional terms together, stick it back into the lightmap + for (int n = 0; n < info.m_NormalCount; ++n) + { + ppLightSamples[n][i].Zero(); + ppLightSamples[n][i].AddWeighted( pDirectLight[n],1.0f / directSupersampleCount ); + ppLightSamples[n][i].AddWeighted( pAmbientLight[n], 1.0f / ambientSupersampleCount ); + } + + // Recompute the luxel intensity based on the supersampling + ComputeLuxelIntensity( info, i, ppLightSamples, pSampleIntensity ); + } + + } + + // We've finished another pass + pass++; + } + + if (debug_extra) + { + // Copy colors representing which supersample pass the sample was messed with + // into the actual lighting values so we can visualize it + for (int i=0 ; inumsamples ; ++i) + { + for (int j = 0; j facenum = facenum; + + pl->face = f; + + // + // rotate plane + // + VectorCopy (dplanes[f->planenum].normal, pl->facenormal); + pl->facedist = dplanes[f->planenum].dist; + + // get the origin offset for rotating bmodels + VectorCopy (face_offset[facenum], pl->modelorg); + + CalcFaceVectors( pl ); + + // figure out if the surface is flat + pl->isflat = true; + if (smoothing_threshold != 1) + { + faceneighbor_t *fn = &faceneighbor[facenum]; + + for (int j=0 ; jnumedges ; j++) + { + float dot = DotProduct( pl->facenormal, fn->normal[j] ); + if (dot < 1.0 - EQUAL_EPSILON) + { + pl->isflat = false; + break; + } + } + } +} + +static void InitSampleInfo( lightinfo_t const& l, int iThread, SSE_SampleInfo_t& info ) +{ + info.m_LightmapWidth = l.face->m_LightmapTextureSizeInLuxels[0]+1; + info.m_LightmapHeight = l.face->m_LightmapTextureSizeInLuxels[1]+1; + info.m_LightmapSize = info.m_LightmapWidth * info.m_LightmapHeight; + + // How many lightmaps are we going to need? + info.m_pTexInfo = &texinfo[l.face->texinfo]; + info.m_NormalCount = (info.m_pTexInfo->flags & SURF_BUMPLIGHT) ? NUM_BUMP_VECTS + 1 : 1; + info.m_FaceNum = l.facenum; + info.m_pFace = l.face; + info.m_pFaceLight = &facelight[info.m_FaceNum]; + info.m_IsDispFace = ValidDispFace( info.m_pFace ); + info.m_iThread = iThread; + info.m_WarnFace = -1; + + info.m_NumSamples = info.m_pFaceLight->numsamples; + info.m_NumSampleGroups = ( info.m_NumSamples & 0x3) ? ( info.m_NumSamples / 4 ) + 1 : ( info.m_NumSamples / 4 ); + + // initialize normals if the surface is flat + if (l.isflat) + { + info.m_PointNormals[0].DuplicateVector( l.facenormal ); + + // use facenormal along with the smooth normal to build the three bump map vectors + if( info.m_NormalCount > 1 ) + { + Vector bumpVects[NUM_BUMP_VECTS]; + GetBumpNormals( info.m_pTexInfo->textureVecsTexelsPerWorldUnits[0], + info.m_pTexInfo->textureVecsTexelsPerWorldUnits[1], l.facenormal, + l.facenormal, bumpVects );//&info.m_PointNormal[1] ); + + for ( int b = 0; b < NUM_BUMP_VECTS; ++b ) + { + info.m_PointNormals[b + 1].DuplicateVector( bumpVects[b] ); + } + } + } +} + +void BuildFacelights (int iThread, int facenum) +{ + int i, j; + + lightinfo_t l; + dface_t *f; + facelight_t *fl; + SSE_SampleInfo_t sampleInfo; + directlight_t *dl; + Vector spot; + Vector v[4], n[4]; + + if( g_bInterrupt ) + return; + + // FIXME: Is there a better way to do this? Like, in RunThreadsOn, for instance? + // Don't pay this cost unless we have to; this is super perf-critical code. + if (g_pIncremental) + { + // Both threads will be accessing this so it needs to be protected or else thread A + // will load it in and thread B will increment it but its increment will be + // overwritten by thread A when thread A writes it back. + ThreadLock(); + ++g_iCurFace; + ThreadUnlock(); + } + + // some surfaces don't need lightmaps + f = &g_pFaces[facenum]; + f->lightofs = -1; + for (j=0 ; jstyles[j] = 255; + + // Trivial-reject the whole face? + if( !( g_FacesVisibleToLights[facenum>>3] & (1 << (facenum & 7)) ) ) + return; + + if ( texinfo[f->texinfo].flags & TEX_SPECIAL) + return; // non-lit texture + + // check for patches for this face. If none it must be degenerate. Ignore. + if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) + return; + + fl = &facelight[facenum]; + + InitLightinfo( &l, facenum ); + CalcPoints( &l, fl, facenum ); + InitSampleInfo( l, iThread, sampleInfo ); + + // Allocate sample positions/normals to SSE + int numGroups = ( fl->numsamples & 0x3) ? ( fl->numsamples / 4 ) + 1 : ( fl->numsamples / 4 ); + + // always allocate style 0 lightmap + f->styles[0] = 0; + AllocateLightstyleSamples( fl, 0, sampleInfo.m_NormalCount ); + + // sample the lights at each sample location + for ( int grp = 0; grp < numGroups; ++grp ) + { + int nSample = 4 * grp; + + sample_t *sample = sampleInfo.m_pFaceLight->sample + nSample; + int numSamples = min ( 4, sampleInfo.m_pFaceLight->numsamples - nSample ); + + FourVectors positions; + FourVectors normals; + + for ( int i = 0; i < 4; i++ ) + { + v[i] = ( i < numSamples ) ? sample[i].pos : sample[numSamples - 1].pos; + n[i] = ( i < numSamples ) ? sample[i].normal : sample[numSamples - 1].normal; + } + positions.LoadAndSwizzle( v[0], v[1], v[2], v[3] ); + normals.LoadAndSwizzle( n[0], n[1], n[2], n[3] ); + + ComputeIlluminationPointAndNormalsSSE( l, positions, normals, &sampleInfo, numSamples ); + + // Fixup sample normals in case of smooth faces + if ( !l.isflat ) + { + for ( int i = 0; i < numSamples; i++ ) + sample[i].normal = sampleInfo.m_PointNormals[0].Vec( i ); + } + + // Iterate over all the lights and add their contribution to this group of spots + GatherSampleLightAt4Points( sampleInfo, nSample, numSamples ); + } + + // Tell the incremental light manager that we're done with this face. + if( g_pIncremental ) + { + for (dl = activelights; dl != NULL; dl = dl->next) + { + // Only deal with lightstyle 0 for incremental lighting + if (dl->light.style == 0) + g_pIncremental->FinishFace( dl->m_IncrementalID, facenum, iThread ); + } + + // Don't have to deal with patch lights (only direct lighting is used) + // or supersampling + return; + } + + // get rid of the -extra functionality on displacement surfaces + if (do_extra && !sampleInfo.m_IsDispFace) + { + // For each lightstyle, perform a supersampling pass + for ( i = 0; i < MAXLIGHTMAPS; ++i ) + { + // Stop when we run out of lightstyles + if (f->styles[i] == 255) + break; + + BuildSupersampleFaceLights( l, sampleInfo, i ); + } + } + + if (!g_bUseMPI) + { + // + // This is done on the master node when MPI is used + // + BuildPatchLights( facenum ); + } + + if( g_bDumpPatches ) + { + DumpSamples( facenum, fl ); + } + else + { + FreeSampleWindings( fl ); + } + +} + +void BuildPatchLights( int facenum ) +{ + int i, k; + + CPatch *patch; + + dface_t *f = &g_pFaces[facenum]; + facelight_t *fl = &facelight[facenum]; + + for( k = 0; k < MAXLIGHTMAPS; k++ ) + { + if (f->styles[k] == 0) + break; + } + + if (k >= MAXLIGHTMAPS) + return; + + for (i = 0; i < fl->numsamples; i++) + { + AddSampleToPatch( &fl->sample[i], fl->light[k][0][i], facenum); + } + + // check for a valid face + if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) + return; + + // push up sampled light to parents (children always exist first in the list) + CPatch *pNextPatch; + for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + // skip patches without parents + if( patch->parent == g_Patches.InvalidIndex() ) +// if (patch->parent == -1) + continue; + + CPatch *parent = &g_Patches.Element( patch->parent ); + + parent->samplearea += patch->samplearea; + VectorAdd( parent->samplelight, patch->samplelight, parent->samplelight ); + } + + // average up the direct light on each patch for radiosity + if (numbounce > 0) + { + for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + if (patch->samplearea) + { + float scale; + Vector v; + scale = 1.0 / patch->samplearea; + + VectorScale( patch->samplelight, scale, v ); + VectorAdd( patch->totallight.light[0], v, patch->totallight.light[0] ); + VectorAdd( patch->directlight, v, patch->directlight ); + } + } + } + + // pull totallight from children (children always exist first in the list) + for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + if ( patch->child1 != g_Patches.InvalidIndex() ) + { + float s1, s2; + CPatch *child1; + CPatch *child2; + + child1 = &g_Patches.Element( patch->child1 ); + child2 = &g_Patches.Element( patch->child2 ); + + s1 = child1->area / (child1->area + child2->area); + s2 = child2->area / (child1->area + child2->area); + + VectorScale( child1->totallight.light[0], s1, patch->totallight.light[0] ); + VectorMA( patch->totallight.light[0], s2, child2->totallight.light[0], patch->totallight.light[0] ); + + VectorCopy( patch->totallight.light[0], patch->directlight ); + } + } + + bool needsBumpmap = false; + if( texinfo[f->texinfo].flags & SURF_BUMPLIGHT ) + { + needsBumpmap = true; + } + + // add an ambient term if desired + if (ambient[0] || ambient[1] || ambient[2]) + { + for( int j=0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + if ( f->styles[j] == 0 ) + { + for (i = 0; i < fl->numsamples; i++) + { + fl->light[j][0][i].m_vecLighting += ambient; + if( needsBumpmap ) + { + fl->light[j][1][i].m_vecLighting += ambient; + fl->light[j][2][i].m_vecLighting += ambient; + fl->light[j][3][i].m_vecLighting += ambient; + } + } + break; + } + } + } + + // light from dlight_threshold and above is sent out, but the + // texture itself should still be full bright + +#if 0 + // if( VectorAvg( g_FacePatches[facenum]->baselight ) >= dlight_threshold) // Now all lighted surfaces glow + { + for( j=0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + if ( f->styles[j] == 0 ) + { + // BUG: shouldn't this be done for all patches on the face? + for (i=0 ; inumsamples ; i++) + { + // garymctchange + VectorAdd( fl->light[j][0][i], g_FacePatches[facenum]->baselight, fl->light[j][0][i] ); + if( needsBumpmap ) + { + for( bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + VectorAdd( fl->light[j][bumpSample][i], g_FacePatches[facenum]->baselight, fl->light[j][bumpSample][i] ); + } + } + } + break; + } + } + } +#endif +} + + +/* + ============= + PrecompLightmapOffsets + ============= +*/ + +void PrecompLightmapOffsets() +{ + int facenum; + dface_t *f; + int lightstyles; + int lightdatasize = 0; + + // NOTE: We store avg face light data in this lump *before* the lightmap data itself + // in *reverse order* of the way the lightstyles appear in the styles array. + for( facenum = 0; facenum < numfaces; facenum++ ) + { + f = &g_pFaces[facenum]; + + if ( texinfo[f->texinfo].flags & TEX_SPECIAL) + continue; // non-lit texture + + if ( dlight_map != 0 ) + f->styles[1] = 0; + + for (lightstyles=0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if ( f->styles[lightstyles] == 255 ) + break; + } + + if ( !lightstyles ) + continue; + + // Reserve room for the avg light color data + lightdatasize += lightstyles * 4; + + f->lightofs = lightdatasize; + + bool needsBumpmap = false; + if( texinfo[f->texinfo].flags & SURF_BUMPLIGHT ) + { + needsBumpmap = true; + } + + int nLuxels = (f->m_LightmapTextureSizeInLuxels[0]+1) * (f->m_LightmapTextureSizeInLuxels[1]+1); + if( needsBumpmap ) + { + lightdatasize += nLuxels * 4 * lightstyles * ( NUM_BUMP_VECTS + 1 ); + } + else + { + lightdatasize += nLuxels * 4 * lightstyles; + } + } + + // The incremental lighting code needs us to preserve the contents of dlightdata + // since it only recomposites lighting for faces that have lights that touch them. + if( g_pIncremental && pdlightdata->Count() ) + return; + + pdlightdata->SetSize( lightdatasize ); +} + +// Clamp the three values for bumped lighting such that we trade off directionality for brightness. +static void ColorClampBumped( Vector& color1, Vector& color2, Vector& color3 ) +{ + Vector maxs; + Vector *colors[3] = { &color1, &color2, &color3 }; + maxs[0] = VectorMaximum( color1 ); + maxs[1] = VectorMaximum( color2 ); + maxs[2] = VectorMaximum( color3 ); + + // HACK! Clean this up, and add some else statements +#define CONDITION(a,b,c) do { if( maxs[a] >= maxs[b] && maxs[b] >= maxs[c] ) { order[0] = a; order[1] = b; order[2] = c; } } while( 0 ) + + int order[3]; + CONDITION(0,1,2); + CONDITION(0,2,1); + CONDITION(1,0,2); + CONDITION(1,2,0); + CONDITION(2,0,1); + CONDITION(2,1,0); + + int i; + for( i = 0; i < 3; i++ ) + { + float max = VectorMaximum( *colors[order[i]] ); + if( max <= 1.0f ) + { + continue; + } + // This channel is too bright. . take half of the amount that we are over and + // add it to the other two channel. + float factorToRedist = ( max - 1.0f ) / max; + Vector colorToRedist = factorToRedist * *colors[order[i]]; + *colors[order[i]] -= colorToRedist; + colorToRedist *= 0.5f; + *colors[order[(i+1)%3]] += colorToRedist; + *colors[order[(i+2)%3]] += colorToRedist; + } + + ColorClamp( color1 ); + ColorClamp( color2 ); + ColorClamp( color3 ); + + if( color1[0] < 0.f ) color1[0] = 0.f; + if( color1[1] < 0.f ) color1[1] = 0.f; + if( color1[2] < 0.f ) color1[2] = 0.f; + if( color2[0] < 0.f ) color2[0] = 0.f; + if( color2[1] < 0.f ) color2[1] = 0.f; + if( color2[2] < 0.f ) color2[2] = 0.f; + if( color3[0] < 0.f ) color3[0] = 0.f; + if( color3[1] < 0.f ) color3[1] = 0.f; + if( color3[2] < 0.f ) color3[2] = 0.f; +} + +static void LinearToBumpedLightmap( + const float *linearColor, + const float *linearBumpColor1, + const float *linearBumpColor2, + const float *linearBumpColor3, + unsigned char *ret, + unsigned char *retBump1, + unsigned char *retBump2, + unsigned char *retBump3 ) +{ + const Vector &linearBump1 = *( ( const Vector * )linearBumpColor1 ); + const Vector &linearBump2 = *( ( const Vector * )linearBumpColor2 ); + const Vector &linearBump3 = *( ( const Vector * )linearBumpColor3 ); + + Vector gammaGoal; + // gammaGoal is premultiplied by 1/overbright, which we want + gammaGoal[0] = LinearToVertexLight( linearColor[0] ); + gammaGoal[1] = LinearToVertexLight( linearColor[1] ); + gammaGoal[2] = LinearToVertexLight( linearColor[2] ); + Vector bumpAverage = linearBump1; + bumpAverage += linearBump2; + bumpAverage += linearBump3; + bumpAverage *= ( 1.0f / 3.0f ); + + Vector correctionScale; + if( *( int * )&bumpAverage[0] != 0 && *( int * )&bumpAverage[1] != 0 && *( int * )&bumpAverage[2] != 0 ) + { + // fast path when we know that we don't have to worry about divide by zero. + VectorDivide( gammaGoal, bumpAverage, correctionScale ); +// correctionScale = gammaGoal / bumpSum; + } + else + { + correctionScale.Init( 0.0f, 0.0f, 0.0f ); + if( bumpAverage[0] != 0.0f ) + { + correctionScale[0] = gammaGoal[0] / bumpAverage[0]; + } + if( bumpAverage[1] != 0.0f ) + { + correctionScale[1] = gammaGoal[1] / bumpAverage[1]; + } + if( bumpAverage[2] != 0.0f ) + { + correctionScale[2] = gammaGoal[2] / bumpAverage[2]; + } + } + Vector correctedBumpColor1; + Vector correctedBumpColor2; + Vector correctedBumpColor3; + VectorMultiply( linearBump1, correctionScale, correctedBumpColor1 ); + VectorMultiply( linearBump2, correctionScale, correctedBumpColor2 ); + VectorMultiply( linearBump3, correctionScale, correctedBumpColor3 ); + + Vector check = ( correctedBumpColor1 + correctedBumpColor2 + correctedBumpColor3 ) / 3.0f; + + ColorClampBumped( correctedBumpColor1, correctedBumpColor2, correctedBumpColor3 ); + + ret[0] = RoundFloatToByte( gammaGoal[0] * 255.0f ); + ret[1] = RoundFloatToByte( gammaGoal[1] * 255.0f ); + ret[2] = RoundFloatToByte( gammaGoal[2] * 255.0f ); + retBump1[0] = RoundFloatToByte( correctedBumpColor1[0] * 255.0f ); + retBump1[1] = RoundFloatToByte( correctedBumpColor1[1] * 255.0f ); + retBump1[2] = RoundFloatToByte( correctedBumpColor1[2] * 255.0f ); + retBump2[0] = RoundFloatToByte( correctedBumpColor2[0] * 255.0f ); + retBump2[1] = RoundFloatToByte( correctedBumpColor2[1] * 255.0f ); + retBump2[2] = RoundFloatToByte( correctedBumpColor2[2] * 255.0f ); + retBump3[0] = RoundFloatToByte( correctedBumpColor3[0] * 255.0f ); + retBump3[1] = RoundFloatToByte( correctedBumpColor3[1] * 255.0f ); + retBump3[2] = RoundFloatToByte( correctedBumpColor3[2] * 255.0f ); +} + +//----------------------------------------------------------------------------- +// Convert a RGBExp32 to a RGBA8888 +// This matches the engine's conversion, so the lighting result is consistent. +//----------------------------------------------------------------------------- +void ConvertRGBExp32ToRGBA8888( const ColorRGBExp32 *pSrc, unsigned char *pDst ) +{ + Vector linearColor; + Vector vertexColor; + + // convert from ColorRGBExp32 to linear space + linearColor[0] = TexLightToLinear( ((ColorRGBExp32 *)pSrc)->r, ((ColorRGBExp32 *)pSrc)->exponent ); + linearColor[1] = TexLightToLinear( ((ColorRGBExp32 *)pSrc)->g, ((ColorRGBExp32 *)pSrc)->exponent ); + linearColor[2] = TexLightToLinear( ((ColorRGBExp32 *)pSrc)->b, ((ColorRGBExp32 *)pSrc)->exponent ); + + // convert from linear space to lightmap space + // cannot use mathlib routine directly because it doesn't match + // the colorspace version found in the engine, which *is* the same sequence here + vertexColor[0] = LinearToVertexLight( linearColor[0] ); + vertexColor[1] = LinearToVertexLight( linearColor[1] ); + vertexColor[2] = LinearToVertexLight( linearColor[2] ); + + // this is really a color normalization with a floor + ColorClamp( vertexColor ); + + // final [0..255] scale + pDst[0] = RoundFloatToByte( vertexColor[0] * 255.0f ); + pDst[1] = RoundFloatToByte( vertexColor[1] * 255.0f ); + pDst[2] = RoundFloatToByte( vertexColor[2] * 255.0f ); + pDst[3] = 255; +} + diff --git a/mp/src/utils/vrad/lightmap.h b/mp/src/utils/vrad/lightmap.h index 0912c43f..a4c698da 100644 --- a/mp/src/utils/vrad/lightmap.h +++ b/mp/src/utils/vrad/lightmap.h @@ -1,141 +1,141 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef LIGHTMAP_H -#define LIGHTMAP_H -#pragma once - -#include "mathlib/bumpvects.h" -#include "bsplib.h" - -typedef struct -{ - dface_t *faces[2]; - Vector interface_normal; - qboolean coplanar; -} edgeshare_t; - -extern edgeshare_t edgeshare[MAX_MAP_EDGES]; - - -//============================================== - -// This is incremented each time BuildFaceLights and FinalLightFace -// are called. It's used for a status bar in WorldCraft. -extern int g_iCurFace; - -extern int vertexref[MAX_MAP_VERTS]; -extern int *vertexface[MAX_MAP_VERTS]; - -struct faceneighbor_t -{ - int numneighbors; // neighboring faces that share vertices - int *neighbor; // neighboring face list (max of 64) - - Vector *normal; // adjusted normal per vertex - Vector facenormal; // face normal - - bool bHasDisp; // is this surface a displacement surface??? -}; - -extern faceneighbor_t faceneighbor[MAX_MAP_FACES]; - -//============================================== - - -struct sample_t -{ - // in local luxel space - winding_t *w; - int s, t; - Vector2D coord; - Vector2D mins; - Vector2D maxs; - // in world units - Vector pos; - Vector normal; - float area; -}; - -struct facelight_t -{ - // irregularly shaped light sample data, clipped by face and luxel grid - int numsamples; - sample_t *sample; - LightingValue_t *light[MAXLIGHTMAPS][NUM_BUMP_VECTS+1]; // result of direct illumination, indexed by sample - - // regularly spaced lightmap grid - int numluxels; - Vector *luxel; // world space position of luxel - Vector *luxelNormals; // world space normal of luxel - float worldAreaPerLuxel; -}; - -extern directlight_t *activelights; -extern directlight_t *freelights; - -extern facelight_t facelight[MAX_MAP_FACES]; -extern int numdlights; - - -//============================================== - -struct lightinfo_t -{ - vec_t facedist; - Vector facenormal; - - Vector facemid; // world coordinates of center - - Vector modelorg; // for origined bmodels - - Vector luxelOrigin; - Vector worldToLuxelSpace[2]; // s = (world - luxelOrigin) . worldToLuxelSpace[0], t = (world - luxelOrigin) . worldToLuxelSpace[1] - Vector luxelToWorldSpace[2]; // world = luxelOrigin + s * luxelToWorldSpace[0] + t * luxelToWorldSpace[1] - - int facenum; - dface_t *face; - - int isflat; - int hasbumpmap; -}; - -struct SSE_SampleInfo_t -{ - int m_FaceNum; - int m_WarnFace; - dface_t *m_pFace; - facelight_t *m_pFaceLight; - int m_LightmapWidth; - int m_LightmapHeight; - int m_LightmapSize; - int m_NormalCount; - int m_iThread; - texinfo_t *m_pTexInfo; - bool m_IsDispFace; - - int m_NumSamples; - int m_NumSampleGroups; - int m_Clusters[4]; - FourVectors m_Points; - FourVectors m_PointNormals[ NUM_BUMP_VECTS + 1 ]; -}; - -extern void InitLightinfo( lightinfo_t *l, int facenum ); - -void FreeDLights(); - -void ExportDirectLightsToWorldLights(); - - -#endif // LIGHTMAP_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef LIGHTMAP_H +#define LIGHTMAP_H +#pragma once + +#include "mathlib/bumpvects.h" +#include "bsplib.h" + +typedef struct +{ + dface_t *faces[2]; + Vector interface_normal; + qboolean coplanar; +} edgeshare_t; + +extern edgeshare_t edgeshare[MAX_MAP_EDGES]; + + +//============================================== + +// This is incremented each time BuildFaceLights and FinalLightFace +// are called. It's used for a status bar in WorldCraft. +extern int g_iCurFace; + +extern int vertexref[MAX_MAP_VERTS]; +extern int *vertexface[MAX_MAP_VERTS]; + +struct faceneighbor_t +{ + int numneighbors; // neighboring faces that share vertices + int *neighbor; // neighboring face list (max of 64) + + Vector *normal; // adjusted normal per vertex + Vector facenormal; // face normal + + bool bHasDisp; // is this surface a displacement surface??? +}; + +extern faceneighbor_t faceneighbor[MAX_MAP_FACES]; + +//============================================== + + +struct sample_t +{ + // in local luxel space + winding_t *w; + int s, t; + Vector2D coord; + Vector2D mins; + Vector2D maxs; + // in world units + Vector pos; + Vector normal; + float area; +}; + +struct facelight_t +{ + // irregularly shaped light sample data, clipped by face and luxel grid + int numsamples; + sample_t *sample; + LightingValue_t *light[MAXLIGHTMAPS][NUM_BUMP_VECTS+1]; // result of direct illumination, indexed by sample + + // regularly spaced lightmap grid + int numluxels; + Vector *luxel; // world space position of luxel + Vector *luxelNormals; // world space normal of luxel + float worldAreaPerLuxel; +}; + +extern directlight_t *activelights; +extern directlight_t *freelights; + +extern facelight_t facelight[MAX_MAP_FACES]; +extern int numdlights; + + +//============================================== + +struct lightinfo_t +{ + vec_t facedist; + Vector facenormal; + + Vector facemid; // world coordinates of center + + Vector modelorg; // for origined bmodels + + Vector luxelOrigin; + Vector worldToLuxelSpace[2]; // s = (world - luxelOrigin) . worldToLuxelSpace[0], t = (world - luxelOrigin) . worldToLuxelSpace[1] + Vector luxelToWorldSpace[2]; // world = luxelOrigin + s * luxelToWorldSpace[0] + t * luxelToWorldSpace[1] + + int facenum; + dface_t *face; + + int isflat; + int hasbumpmap; +}; + +struct SSE_SampleInfo_t +{ + int m_FaceNum; + int m_WarnFace; + dface_t *m_pFace; + facelight_t *m_pFaceLight; + int m_LightmapWidth; + int m_LightmapHeight; + int m_LightmapSize; + int m_NormalCount; + int m_iThread; + texinfo_t *m_pTexInfo; + bool m_IsDispFace; + + int m_NumSamples; + int m_NumSampleGroups; + int m_Clusters[4]; + FourVectors m_Points; + FourVectors m_PointNormals[ NUM_BUMP_VECTS + 1 ]; +}; + +extern void InitLightinfo( lightinfo_t *l, int facenum ); + +void FreeDLights(); + +void ExportDirectLightsToWorldLights(); + + +#endif // LIGHTMAP_H diff --git a/mp/src/utils/vrad/macro_texture.cpp b/mp/src/utils/vrad/macro_texture.cpp index 8511d979..cd0eac12 100644 --- a/mp/src/utils/vrad/macro_texture.cpp +++ b/mp/src/utils/vrad/macro_texture.cpp @@ -1,166 +1,166 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#include "tier1/strtools.h" -#include "macro_texture.h" -#include "bsplib.h" -#include "cmdlib.h" -#include "vtf/vtf.h" -#include "tier1/utldict.h" -#include "tier1/utlbuffer.h" -#include "bitmap/imageformat.h" - - -class CMacroTextureData -{ -public: - int m_Width, m_Height; - CUtlMemory m_ImageData; -}; - - -CMacroTextureData *g_pGlobalMacroTextureData = NULL; - -// Which macro texture each map face uses. -static CUtlDict g_MacroTextureLookup; // Stores a list of unique macro textures. -static CUtlVector g_FaceMacroTextures; // Which macro texture each face wants to use. -static Vector g_MacroWorldMins, g_MacroWorldMaxs; - - -CMacroTextureData* FindMacroTexture( const char *pFilename ) -{ - int index = g_MacroTextureLookup.Find( pFilename ); - if ( g_MacroTextureLookup.IsValidIndex( index ) ) - return g_MacroTextureLookup[index]; - else - return NULL; -} - - -CMacroTextureData* LoadMacroTextureFile( const char *pFilename ) -{ - FileHandle_t hFile = g_pFileSystem->Open( pFilename, "rb" ); - if ( hFile == FILESYSTEM_INVALID_HANDLE ) - return NULL; - - // Read the file in. - CUtlVector tempData; - tempData.SetSize( g_pFileSystem->Size( hFile ) ); - g_pFileSystem->Read( tempData.Base(), tempData.Count(), hFile ); - g_pFileSystem->Close( hFile ); - - - // Now feed the data into a CUtlBuffer (great...) - CUtlBuffer buf; - buf.Put( tempData.Base(), tempData.Count() ); - - - // Now make a texture out of it. - IVTFTexture *pTex = CreateVTFTexture(); - if ( !pTex->Unserialize( buf ) ) - Error( "IVTFTexture::Unserialize( %s ) failed.", pFilename ); - - pTex->ConvertImageFormat( IMAGE_FORMAT_RGBA8888, false ); // Get it in a format we like. - - - // Now convert to a CMacroTextureData. - CMacroTextureData *pData = new CMacroTextureData; - pData->m_Width = pTex->Width(); - pData->m_Height = pTex->Height(); - pData->m_ImageData.EnsureCapacity( pData->m_Width * pData->m_Height * 4 ); - memcpy( pData->m_ImageData.Base(), pTex->ImageData(), pData->m_Width * pData->m_Height * 4 ); - - DestroyVTFTexture( pTex ); - - Msg( "-- LoadMacroTextureFile: %s\n", pFilename ); - return pData; -} - - -void InitMacroTexture( const char *pBSPFilename ) -{ - // Get the world bounds (same ones used by minimaps and level designers know how to use). - int i = 0; - for (i; i < num_entities; ++i) - { - char* pEntity = ValueForKey(&entities[i], "classname"); - if( !strcmp(pEntity, "worldspawn") ) - { - GetVectorForKey( &entities[i], "world_mins", g_MacroWorldMins ); - GetVectorForKey( &entities[i], "world_maxs", g_MacroWorldMaxs ); - break; - } - } - - if ( i == num_entities ) - { - Warning( "MaskOnMacroTexture: can't find worldspawn" ); - return; - } - - - // Load the macro texture that is mapped onto everything. - char mapName[512], vtfFilename[512]; - Q_FileBase( pBSPFilename, mapName, sizeof( mapName ) ); - Q_snprintf( vtfFilename, sizeof( vtfFilename ), "materials/macro/%s/base.vtf", mapName ); - g_pGlobalMacroTextureData = LoadMacroTextureFile( vtfFilename ); - - - // Now load the macro texture for each face. - g_FaceMacroTextures.SetSize( numfaces ); - for ( int iFace=0; iFace < numfaces; iFace++ ) - { - g_FaceMacroTextures[iFace] = NULL; - - if ( iFace < g_FaceMacroTextureInfos.Count() ) - { - unsigned short stringID = g_FaceMacroTextureInfos[iFace].m_MacroTextureNameID; - if ( stringID != 0xFFFF ) - { - const char *pMacroTextureName = &g_TexDataStringData[ g_TexDataStringTable[stringID] ]; - Q_snprintf( vtfFilename, sizeof( vtfFilename ), "%smaterials/%s.vtf", gamedir, pMacroTextureName ); - - g_FaceMacroTextures[iFace] = FindMacroTexture( vtfFilename ); - if ( !g_FaceMacroTextures[iFace] ) - { - g_FaceMacroTextures[iFace] = LoadMacroTextureFile( vtfFilename ); - if ( g_FaceMacroTextures[iFace] ) - { - g_MacroTextureLookup.Insert( vtfFilename, g_FaceMacroTextures[iFace] ); - } - } - } - } - } -} - - -inline Vector SampleMacroTexture( const CMacroTextureData *t, const Vector &vWorldPos ) -{ - int ix = (int)RemapVal( vWorldPos.x, g_MacroWorldMins.x, g_MacroWorldMaxs.x, 0, t->m_Width-0.00001 ); - int iy = (int)RemapVal( vWorldPos.y, g_MacroWorldMins.y, g_MacroWorldMaxs.y, 0, t->m_Height-0.00001 ); - ix = clamp( ix, 0, t->m_Width-1 ); - iy = t->m_Height - 1 - clamp( iy, 0, t->m_Height-1 ); - - const unsigned char *pInputColor = &t->m_ImageData[(iy*t->m_Width + ix) * 4]; - return Vector( pInputColor[0] / 255.0, pInputColor[1] / 255.0, pInputColor[2] / 255.0 ); -} - - -void ApplyMacroTextures( int iFace, const Vector &vWorldPos, Vector &outLuxel ) -{ - // Add the global macro texture. - Vector vGlobal; - if ( g_pGlobalMacroTextureData ) - outLuxel *= SampleMacroTexture( g_pGlobalMacroTextureData, vWorldPos ); - - // Now add the per-material macro texture. - if ( g_FaceMacroTextures[iFace] ) - outLuxel *= SampleMacroTexture( g_FaceMacroTextures[iFace], vWorldPos ); -} - - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "tier1/strtools.h" +#include "macro_texture.h" +#include "bsplib.h" +#include "cmdlib.h" +#include "vtf/vtf.h" +#include "tier1/utldict.h" +#include "tier1/utlbuffer.h" +#include "bitmap/imageformat.h" + + +class CMacroTextureData +{ +public: + int m_Width, m_Height; + CUtlMemory m_ImageData; +}; + + +CMacroTextureData *g_pGlobalMacroTextureData = NULL; + +// Which macro texture each map face uses. +static CUtlDict g_MacroTextureLookup; // Stores a list of unique macro textures. +static CUtlVector g_FaceMacroTextures; // Which macro texture each face wants to use. +static Vector g_MacroWorldMins, g_MacroWorldMaxs; + + +CMacroTextureData* FindMacroTexture( const char *pFilename ) +{ + int index = g_MacroTextureLookup.Find( pFilename ); + if ( g_MacroTextureLookup.IsValidIndex( index ) ) + return g_MacroTextureLookup[index]; + else + return NULL; +} + + +CMacroTextureData* LoadMacroTextureFile( const char *pFilename ) +{ + FileHandle_t hFile = g_pFileSystem->Open( pFilename, "rb" ); + if ( hFile == FILESYSTEM_INVALID_HANDLE ) + return NULL; + + // Read the file in. + CUtlVector tempData; + tempData.SetSize( g_pFileSystem->Size( hFile ) ); + g_pFileSystem->Read( tempData.Base(), tempData.Count(), hFile ); + g_pFileSystem->Close( hFile ); + + + // Now feed the data into a CUtlBuffer (great...) + CUtlBuffer buf; + buf.Put( tempData.Base(), tempData.Count() ); + + + // Now make a texture out of it. + IVTFTexture *pTex = CreateVTFTexture(); + if ( !pTex->Unserialize( buf ) ) + Error( "IVTFTexture::Unserialize( %s ) failed.", pFilename ); + + pTex->ConvertImageFormat( IMAGE_FORMAT_RGBA8888, false ); // Get it in a format we like. + + + // Now convert to a CMacroTextureData. + CMacroTextureData *pData = new CMacroTextureData; + pData->m_Width = pTex->Width(); + pData->m_Height = pTex->Height(); + pData->m_ImageData.EnsureCapacity( pData->m_Width * pData->m_Height * 4 ); + memcpy( pData->m_ImageData.Base(), pTex->ImageData(), pData->m_Width * pData->m_Height * 4 ); + + DestroyVTFTexture( pTex ); + + Msg( "-- LoadMacroTextureFile: %s\n", pFilename ); + return pData; +} + + +void InitMacroTexture( const char *pBSPFilename ) +{ + // Get the world bounds (same ones used by minimaps and level designers know how to use). + int i = 0; + for (i; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if( !strcmp(pEntity, "worldspawn") ) + { + GetVectorForKey( &entities[i], "world_mins", g_MacroWorldMins ); + GetVectorForKey( &entities[i], "world_maxs", g_MacroWorldMaxs ); + break; + } + } + + if ( i == num_entities ) + { + Warning( "MaskOnMacroTexture: can't find worldspawn" ); + return; + } + + + // Load the macro texture that is mapped onto everything. + char mapName[512], vtfFilename[512]; + Q_FileBase( pBSPFilename, mapName, sizeof( mapName ) ); + Q_snprintf( vtfFilename, sizeof( vtfFilename ), "materials/macro/%s/base.vtf", mapName ); + g_pGlobalMacroTextureData = LoadMacroTextureFile( vtfFilename ); + + + // Now load the macro texture for each face. + g_FaceMacroTextures.SetSize( numfaces ); + for ( int iFace=0; iFace < numfaces; iFace++ ) + { + g_FaceMacroTextures[iFace] = NULL; + + if ( iFace < g_FaceMacroTextureInfos.Count() ) + { + unsigned short stringID = g_FaceMacroTextureInfos[iFace].m_MacroTextureNameID; + if ( stringID != 0xFFFF ) + { + const char *pMacroTextureName = &g_TexDataStringData[ g_TexDataStringTable[stringID] ]; + Q_snprintf( vtfFilename, sizeof( vtfFilename ), "%smaterials/%s.vtf", gamedir, pMacroTextureName ); + + g_FaceMacroTextures[iFace] = FindMacroTexture( vtfFilename ); + if ( !g_FaceMacroTextures[iFace] ) + { + g_FaceMacroTextures[iFace] = LoadMacroTextureFile( vtfFilename ); + if ( g_FaceMacroTextures[iFace] ) + { + g_MacroTextureLookup.Insert( vtfFilename, g_FaceMacroTextures[iFace] ); + } + } + } + } + } +} + + +inline Vector SampleMacroTexture( const CMacroTextureData *t, const Vector &vWorldPos ) +{ + int ix = (int)RemapVal( vWorldPos.x, g_MacroWorldMins.x, g_MacroWorldMaxs.x, 0, t->m_Width-0.00001 ); + int iy = (int)RemapVal( vWorldPos.y, g_MacroWorldMins.y, g_MacroWorldMaxs.y, 0, t->m_Height-0.00001 ); + ix = clamp( ix, 0, t->m_Width-1 ); + iy = t->m_Height - 1 - clamp( iy, 0, t->m_Height-1 ); + + const unsigned char *pInputColor = &t->m_ImageData[(iy*t->m_Width + ix) * 4]; + return Vector( pInputColor[0] / 255.0, pInputColor[1] / 255.0, pInputColor[2] / 255.0 ); +} + + +void ApplyMacroTextures( int iFace, const Vector &vWorldPos, Vector &outLuxel ) +{ + // Add the global macro texture. + Vector vGlobal; + if ( g_pGlobalMacroTextureData ) + outLuxel *= SampleMacroTexture( g_pGlobalMacroTextureData, vWorldPos ); + + // Now add the per-material macro texture. + if ( g_FaceMacroTextures[iFace] ) + outLuxel *= SampleMacroTexture( g_FaceMacroTextures[iFace], vWorldPos ); +} + + + diff --git a/mp/src/utils/vrad/macro_texture.h b/mp/src/utils/vrad/macro_texture.h index 249c2474..bfae258a 100644 --- a/mp/src/utils/vrad/macro_texture.h +++ b/mp/src/utils/vrad/macro_texture.h @@ -1,24 +1,24 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef MACRO_TEXTURE_H -#define MACRO_TEXTURE_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "mathlib/vector.h" - - -// The macro texture looks for a TGA file with the same name as the BSP file and in -// the same directory. If it finds one, it maps this texture onto the world dimensions -// (in the worldspawn entity) and masks all lightmaps with it. -void InitMacroTexture( const char *pBSPFilename ); -void ApplyMacroTextures( int iFace, const Vector &vWorldPos, Vector &outLuxel ); - - -#endif // MACRO_TEXTURE_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MACRO_TEXTURE_H +#define MACRO_TEXTURE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "mathlib/vector.h" + + +// The macro texture looks for a TGA file with the same name as the BSP file and in +// the same directory. If it finds one, it maps this texture onto the world dimensions +// (in the worldspawn entity) and masks all lightmaps with it. +void InitMacroTexture( const char *pBSPFilename ); +void ApplyMacroTextures( int iFace, const Vector &vWorldPos, Vector &outLuxel ); + + +#endif // MACRO_TEXTURE_H diff --git a/mp/src/utils/vrad/mpivrad.cpp b/mp/src/utils/vrad/mpivrad.cpp index d54dfaeb..5b7bfc03 100644 --- a/mp/src/utils/vrad/mpivrad.cpp +++ b/mp/src/utils/vrad/mpivrad.cpp @@ -1,496 +1,496 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// -// mpivrad.cpp -// - -#include -#include -#include "vrad.h" -#include "physdll.h" -#include "lightmap.h" -#include "tier1/strtools.h" -#include "radial.h" -#include "utlbuffer.h" -#include "pacifier.h" -#include "messbuf.h" -#include "bsplib.h" -#include "consolewnd.h" -#include "vismat.h" -#include "vmpi_filesystem.h" -#include "vmpi_dispatch.h" -#include "utllinkedlist.h" -#include "vmpi.h" -#include "mpi_stats.h" -#include "vmpi_distribute_work.h" -#include "vmpi_tools_shared.h" - - - - -CUtlVector g_LightResultsFilename; - - -extern int total_transfer; -extern int max_transfer; - -extern void BuildVisLeafs(int); -extern void BuildPatchLights( int facenum ); - - -// Handle VRAD packets. -bool VRAD_DispatchFn( MessageBuffer *pBuf, int iSource, int iPacketID ) -{ - switch( pBuf->data[1] ) - { - case VMPI_SUBPACKETID_PLIGHTDATA_RESULTS: - { - const char *pFilename = &pBuf->data[2]; - g_LightResultsFilename.CopyArray( pFilename, strlen( pFilename ) + 1 ); - return true; - } - - default: - return false; - } -} -CDispatchReg g_VRADDispatchReg( VMPI_VRAD_PACKET_ID, VRAD_DispatchFn ); // register to handle the messages we want -CDispatchReg g_DistributeWorkReg( VMPI_DISTRIBUTEWORK_PACKETID, DistributeWorkDispatch ); - - - -void VRAD_SetupMPI( int &argc, char **&argv ) -{ - CmdLib_AtCleanup( VMPI_Stats_Term ); - - // - // Preliminary check -mpi flag - // - if ( !VMPI_FindArg( argc, argv, "-mpi", "" ) && !VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Worker ), "" ) ) - return; - - // Force local mode? - VMPIRunMode mode; - if ( VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Local ), "" ) ) - mode = VMPI_RUN_LOCAL; - else - mode = VMPI_RUN_NETWORKED; - - VMPI_Stats_InstallSpewHook(); - - // - // Extract mpi specific arguments - // - Msg( "Initializing VMPI...\n" ); - if ( !VMPI_Init( - argc, - argv, - "dependency_info_vrad.txt", - HandleMPIDisconnect, - mode - ) ) - { - Error( "MPI_Init failed." ); - } - - StatsDB_InitStatsDatabase( argc, argv, "dbinfo_vrad.txt" ); -} - - -//----------------------------------------- -// -// Run BuildFaceLights across all available processing nodes -// and collect the results. -// - -CCycleCount g_CPUTime; - - -template void WriteValues( MessageBuffer *pmb, T const *pSrc, int nNumValues) -{ - pmb->write(pSrc, sizeof( pSrc[0]) * nNumValues ); -} - -template int ReadValues( MessageBuffer *pmb, T *pDest, int nNumValues) -{ - return pmb->read( pDest, sizeof( pDest[0]) * nNumValues ); -} - - -//-------------------------------------------------- -// Serialize face data -void SerializeFace( MessageBuffer * pmb, int facenum ) -{ - int i, n; - - dface_t * f = &g_pFaces[facenum]; - facelight_t * fl = &facelight[facenum]; - - pmb->write(f, sizeof(dface_t)); - pmb->write(fl, sizeof(facelight_t)); - - WriteValues( pmb, fl->sample, fl->numsamples); - - // - // Write the light information - // - for (i=0; ilight[i][n]) - { - WriteValues( pmb, fl->light[i][n], fl->numsamples); - } - } - } - - if (fl->luxel) - WriteValues( pmb, fl->luxel, fl->numluxels); - - if (fl->luxelNormals) - WriteValues( pmb, fl->luxelNormals, fl->numluxels); -} - -//-------------------------------------------------- -// UnSerialize face data -// -void UnSerializeFace( MessageBuffer * pmb, int facenum, int iSource ) -{ - int i, n; - - dface_t * f = &g_pFaces[facenum]; - facelight_t * fl = &facelight[facenum]; - - if (pmb->read(f, sizeof(dface_t)) < 0) - Error("UnSerializeFace - invalid dface_t from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); - - if (pmb->read(fl, sizeof(facelight_t)) < 0) - Error("UnSerializeFace - invalid facelight_t from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); - - fl->sample = (sample_t *) calloc(fl->numsamples, sizeof(sample_t)); - if (pmb->read(fl->sample, sizeof(sample_t) * fl->numsamples) < 0) - Error("UnSerializeFace - invalid sample_t from %s (mb len: %d, offset: %d, fl->numsamples: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset(), fl->numsamples ); - - // - // Read the light information - // - for (i=0; ilight[i][n]) - { - fl->light[i][n] = (LightingValue_t *) calloc( fl->numsamples, sizeof(LightingValue_t ) ); - if ( ReadValues( pmb, fl->light[i][n], fl->numsamples) < 0) - Error("UnSerializeFace - invalid fl->light from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); - } - } - } - - if (fl->luxel) { - fl->luxel = (Vector *) calloc(fl->numluxels, sizeof(Vector)); - if (ReadValues( pmb, fl->luxel, fl->numluxels) < 0) - Error("UnSerializeFace - invalid fl->luxel from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); - } - - if (fl->luxelNormals) { - fl->luxelNormals = (Vector *) calloc(fl->numluxels, sizeof( Vector )); - if ( ReadValues( pmb, fl->luxelNormals, fl->numluxels) < 0) - Error("UnSerializeFace - invalid fl->luxelNormals from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); - } - -} - - -void MPI_ReceiveFaceResults( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ) -{ - UnSerializeFace( pBuf, iWorkUnit, iWorker ); -} - - -void MPI_ProcessFaces( int iThread, uint64 iWorkUnit, MessageBuffer *pBuf ) -{ - // Do BuildFacelights on the face. - CTimeAdder adder( &g_CPUTime ); - - BuildFacelights( iThread, iWorkUnit ); - - // Send the results. - if ( pBuf ) - { - SerializeFace( pBuf, iWorkUnit ); - } -} - - -void RunMPIBuildFacelights() -{ - g_CPUTime.Init(); - - Msg( "%-20s ", "BuildFaceLights:" ); - if ( g_bMPIMaster ) - { - StartPacifier(""); - } - - VMPI_SetCurrentStage( "RunMPIBuildFaceLights" ); - double elapsed = DistributeWork( - numfaces, - VMPI_DISTRIBUTEWORK_PACKETID, - MPI_ProcessFaces, - MPI_ReceiveFaceResults ); - - if ( g_bMPIMaster ) - { - EndPacifier(false); - Msg( " (%d)\n", (int)elapsed ); - } - - if ( g_bMPIMaster ) - { - // - // BuildPatchLights is normally called from BuildFacelights(), - // but in MPI mode we have the master do the calculation - // We might be able to speed this up by doing while the master - // is idling in the above loop. Wouldn't want to slow down the - // handing out of work - maybe another thread? - // - for ( int i=0; i < numfaces; ++i ) - { - BuildPatchLights(i); - } - } - else - { - if ( g_iVMPIVerboseLevel >= 1 ) - Msg( "\n\n%.1f%% CPU utilization during BuildFaceLights\n\n", ( g_CPUTime.GetSeconds() * 100 / elapsed ) ); - } -} - - -//----------------------------------------- -// -// Run BuildVisLeafs across all available processing nodes -// and collect the results. -// - -// This function is called when the master receives results back from a worker. -void MPI_ReceiveVisLeafsResults( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ) -{ - int patchesInCluster = 0; - - pBuf->read(&patchesInCluster, sizeof(patchesInCluster)); - - for ( int k=0; k < patchesInCluster; ++k ) - { - int patchnum = 0; - pBuf->read(&patchnum, sizeof(patchnum)); - - CPatch * patch = &g_Patches[patchnum]; - int numtransfers; - pBuf->read( &numtransfers, sizeof(numtransfers) ); - patch->numtransfers = numtransfers; - if (numtransfers) - { - patch->transfers = new transfer_t[numtransfers]; - pBuf->read(patch->transfers, numtransfers * sizeof(transfer_t)); - } - - total_transfer += numtransfers; - if (max_transfer < numtransfers) - max_transfer = numtransfers; - } -} - - -// Temporary variables used during callbacks. If we're going to be threadsafe, these -// should go in a structure and get passed around. -class CVMPIVisLeafsData -{ -public: - MessageBuffer *m_pVisLeafsMB; - int m_nPatchesInCluster; - transfer_t *m_pBuildVisLeafsTransfers; -}; - -CVMPIVisLeafsData g_VMPIVisLeafsData[MAX_TOOL_THREADS+1]; - - - -// This is called by BuildVisLeafs_Cluster every time it finishes a patch. -// The results are appended to g_VisLeafsMB and sent back to the master when all clusters are done. -void MPI_AddPatchData( int iThread, int patchnum, CPatch *patch ) -{ - CVMPIVisLeafsData *pData = &g_VMPIVisLeafsData[iThread]; - if ( pData->m_pVisLeafsMB ) - { - // Add in results for this patch - ++pData->m_nPatchesInCluster; - pData->m_pVisLeafsMB->write(&patchnum, sizeof(patchnum)); - pData->m_pVisLeafsMB->write(&patch->numtransfers, sizeof(patch->numtransfers)); - pData->m_pVisLeafsMB->write( patch->transfers, patch->numtransfers * sizeof(transfer_t) ); - } -} - - -// This handles a work unit sent by the master. Each work unit here is a -// list of clusters. -void MPI_ProcessVisLeafs( int iThread, uint64 iWorkUnit, MessageBuffer *pBuf ) -{ - CTimeAdder adder( &g_CPUTime ); - - CVMPIVisLeafsData *pData = &g_VMPIVisLeafsData[iThread]; - int iCluster = iWorkUnit; - - // Start this cluster. - pData->m_nPatchesInCluster = 0; - pData->m_pVisLeafsMB = pBuf; - - // Write a temp value in there. We overwrite it later. - int iSavePos = 0; - if ( pBuf ) - { - iSavePos = pBuf->getLen(); - pBuf->write( &pData->m_nPatchesInCluster, sizeof(pData->m_nPatchesInCluster) ); - } - - // Collect the results in MPI_AddPatchData. - BuildVisLeafs_Cluster( iThread, pData->m_pBuildVisLeafsTransfers, iCluster, MPI_AddPatchData ); - - // Now send the results back.. - if ( pBuf ) - { - pBuf->update( iSavePos, &pData->m_nPatchesInCluster, sizeof(pData->m_nPatchesInCluster) ); - pData->m_pVisLeafsMB = NULL; - } -} - - -void RunMPIBuildVisLeafs() -{ - g_CPUTime.Init(); - - Msg( "%-20s ", "BuildVisLeafs :" ); - if ( g_bMPIMaster ) - { - StartPacifier(""); - } - - memset( g_VMPIVisLeafsData, 0, sizeof( g_VMPIVisLeafsData ) ); - if ( !g_bMPIMaster || VMPI_GetActiveWorkUnitDistributor() == k_eWorkUnitDistributor_SDK ) - { - // Allocate space for the transfers for each thread. - for ( int i=0; i < numthreads; i++ ) - { - g_VMPIVisLeafsData[i].m_pBuildVisLeafsTransfers = BuildVisLeafs_Start(); - } - } - - // - // Slaves ask for work via GetMPIBuildVisLeafWork() - // Results are returned in BuildVisRow() - // - VMPI_SetCurrentStage( "RunMPIBuildVisLeafs" ); - - double elapsed = DistributeWork( - dvis->numclusters, - VMPI_DISTRIBUTEWORK_PACKETID, - MPI_ProcessVisLeafs, - MPI_ReceiveVisLeafsResults ); - - // Free the transfers from each thread. - for ( int i=0; i < numthreads; i++ ) - { - if ( g_VMPIVisLeafsData[i].m_pBuildVisLeafsTransfers ) - BuildVisLeafs_End( g_VMPIVisLeafsData[i].m_pBuildVisLeafsTransfers ); - } - - if ( g_bMPIMaster ) - { - EndPacifier(false); - Msg( " (%d)\n", (int)elapsed ); - } - else - { - if ( g_iVMPIVerboseLevel >= 1 ) - Msg( "%.1f%% CPU utilization during PortalFlow\n", (g_CPUTime.GetSeconds() * 100.0f / elapsed) / numthreads ); - } -} - -void VMPI_DistributeLightData() -{ - if ( !g_bUseMPI ) - return; - - if ( g_bMPIMaster ) - { - const char *pVirtualFilename = "--plightdata--"; - - CUtlBuffer lightFaceData; - - // write out the light data - lightFaceData.EnsureCapacity( pdlightdata->Count() + (numfaces * (MAXLIGHTMAPS+sizeof(int))) ); - Q_memcpy( lightFaceData.PeekPut(), pdlightdata->Base(), pdlightdata->Count() ); - lightFaceData.SeekPut( CUtlBuffer::SEEK_HEAD, pdlightdata->Count() ); - - // write out the relevant face info into the stream - for ( int i = 0; i < numfaces; i++ ) - { - for ( int j = 0; j < MAXLIGHTMAPS; j++ ) - { - lightFaceData.PutChar(g_pFaces[i].styles[j]); - } - lightFaceData.PutInt(g_pFaces[i].lightofs); - } - VMPI_FileSystem_CreateVirtualFile( pVirtualFilename, lightFaceData.Base(), lightFaceData.TellMaxPut() ); - - char cPacketID[2] = { VMPI_VRAD_PACKET_ID, VMPI_SUBPACKETID_PLIGHTDATA_RESULTS }; - VMPI_Send2Chunks( cPacketID, sizeof( cPacketID ), pVirtualFilename, strlen( pVirtualFilename ) + 1, VMPI_PERSISTENT ); - } - else - { - VMPI_SetCurrentStage( "VMPI_DistributeLightData" ); - - // Wait until we've received the filename from the master. - while ( g_LightResultsFilename.Count() == 0 ) - { - VMPI_DispatchNextMessage(); - } - - // Open - FileHandle_t fp = g_pFileSystem->Open( g_LightResultsFilename.Base(), "rb", VMPI_VIRTUAL_FILES_PATH_ID ); - if ( !fp ) - Error( "Can't open '%s' to read lighting info.", g_LightResultsFilename.Base() ); - - int size = g_pFileSystem->Size( fp ); - int faceSize = (numfaces*(MAXLIGHTMAPS+sizeof(int))); - - if ( size > faceSize ) - { - int lightSize = size - faceSize; - CUtlBuffer faceData; - pdlightdata->EnsureCount( lightSize ); - faceData.EnsureCapacity( faceSize ); - - g_pFileSystem->Read( pdlightdata->Base(), lightSize, fp ); - g_pFileSystem->Read( faceData.Base(), faceSize, fp ); - g_pFileSystem->Close( fp ); - - faceData.SeekPut( CUtlBuffer::SEEK_HEAD, faceSize ); - - // write out the face data - for ( int i = 0; i < numfaces; i++ ) - { - for ( int j = 0; j < MAXLIGHTMAPS; j++ ) - { - g_pFaces[i].styles[j] = faceData.GetChar(); - } - g_pFaces[i].lightofs = faceData.GetInt(); - } - } - } -} - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// +// mpivrad.cpp +// + +#include +#include +#include "vrad.h" +#include "physdll.h" +#include "lightmap.h" +#include "tier1/strtools.h" +#include "radial.h" +#include "utlbuffer.h" +#include "pacifier.h" +#include "messbuf.h" +#include "bsplib.h" +#include "consolewnd.h" +#include "vismat.h" +#include "vmpi_filesystem.h" +#include "vmpi_dispatch.h" +#include "utllinkedlist.h" +#include "vmpi.h" +#include "mpi_stats.h" +#include "vmpi_distribute_work.h" +#include "vmpi_tools_shared.h" + + + + +CUtlVector g_LightResultsFilename; + + +extern int total_transfer; +extern int max_transfer; + +extern void BuildVisLeafs(int); +extern void BuildPatchLights( int facenum ); + + +// Handle VRAD packets. +bool VRAD_DispatchFn( MessageBuffer *pBuf, int iSource, int iPacketID ) +{ + switch( pBuf->data[1] ) + { + case VMPI_SUBPACKETID_PLIGHTDATA_RESULTS: + { + const char *pFilename = &pBuf->data[2]; + g_LightResultsFilename.CopyArray( pFilename, strlen( pFilename ) + 1 ); + return true; + } + + default: + return false; + } +} +CDispatchReg g_VRADDispatchReg( VMPI_VRAD_PACKET_ID, VRAD_DispatchFn ); // register to handle the messages we want +CDispatchReg g_DistributeWorkReg( VMPI_DISTRIBUTEWORK_PACKETID, DistributeWorkDispatch ); + + + +void VRAD_SetupMPI( int &argc, char **&argv ) +{ + CmdLib_AtCleanup( VMPI_Stats_Term ); + + // + // Preliminary check -mpi flag + // + if ( !VMPI_FindArg( argc, argv, "-mpi", "" ) && !VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Worker ), "" ) ) + return; + + // Force local mode? + VMPIRunMode mode; + if ( VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Local ), "" ) ) + mode = VMPI_RUN_LOCAL; + else + mode = VMPI_RUN_NETWORKED; + + VMPI_Stats_InstallSpewHook(); + + // + // Extract mpi specific arguments + // + Msg( "Initializing VMPI...\n" ); + if ( !VMPI_Init( + argc, + argv, + "dependency_info_vrad.txt", + HandleMPIDisconnect, + mode + ) ) + { + Error( "MPI_Init failed." ); + } + + StatsDB_InitStatsDatabase( argc, argv, "dbinfo_vrad.txt" ); +} + + +//----------------------------------------- +// +// Run BuildFaceLights across all available processing nodes +// and collect the results. +// + +CCycleCount g_CPUTime; + + +template void WriteValues( MessageBuffer *pmb, T const *pSrc, int nNumValues) +{ + pmb->write(pSrc, sizeof( pSrc[0]) * nNumValues ); +} + +template int ReadValues( MessageBuffer *pmb, T *pDest, int nNumValues) +{ + return pmb->read( pDest, sizeof( pDest[0]) * nNumValues ); +} + + +//-------------------------------------------------- +// Serialize face data +void SerializeFace( MessageBuffer * pmb, int facenum ) +{ + int i, n; + + dface_t * f = &g_pFaces[facenum]; + facelight_t * fl = &facelight[facenum]; + + pmb->write(f, sizeof(dface_t)); + pmb->write(fl, sizeof(facelight_t)); + + WriteValues( pmb, fl->sample, fl->numsamples); + + // + // Write the light information + // + for (i=0; ilight[i][n]) + { + WriteValues( pmb, fl->light[i][n], fl->numsamples); + } + } + } + + if (fl->luxel) + WriteValues( pmb, fl->luxel, fl->numluxels); + + if (fl->luxelNormals) + WriteValues( pmb, fl->luxelNormals, fl->numluxels); +} + +//-------------------------------------------------- +// UnSerialize face data +// +void UnSerializeFace( MessageBuffer * pmb, int facenum, int iSource ) +{ + int i, n; + + dface_t * f = &g_pFaces[facenum]; + facelight_t * fl = &facelight[facenum]; + + if (pmb->read(f, sizeof(dface_t)) < 0) + Error("UnSerializeFace - invalid dface_t from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); + + if (pmb->read(fl, sizeof(facelight_t)) < 0) + Error("UnSerializeFace - invalid facelight_t from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); + + fl->sample = (sample_t *) calloc(fl->numsamples, sizeof(sample_t)); + if (pmb->read(fl->sample, sizeof(sample_t) * fl->numsamples) < 0) + Error("UnSerializeFace - invalid sample_t from %s (mb len: %d, offset: %d, fl->numsamples: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset(), fl->numsamples ); + + // + // Read the light information + // + for (i=0; ilight[i][n]) + { + fl->light[i][n] = (LightingValue_t *) calloc( fl->numsamples, sizeof(LightingValue_t ) ); + if ( ReadValues( pmb, fl->light[i][n], fl->numsamples) < 0) + Error("UnSerializeFace - invalid fl->light from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); + } + } + } + + if (fl->luxel) { + fl->luxel = (Vector *) calloc(fl->numluxels, sizeof(Vector)); + if (ReadValues( pmb, fl->luxel, fl->numluxels) < 0) + Error("UnSerializeFace - invalid fl->luxel from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); + } + + if (fl->luxelNormals) { + fl->luxelNormals = (Vector *) calloc(fl->numluxels, sizeof( Vector )); + if ( ReadValues( pmb, fl->luxelNormals, fl->numluxels) < 0) + Error("UnSerializeFace - invalid fl->luxelNormals from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); + } + +} + + +void MPI_ReceiveFaceResults( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ) +{ + UnSerializeFace( pBuf, iWorkUnit, iWorker ); +} + + +void MPI_ProcessFaces( int iThread, uint64 iWorkUnit, MessageBuffer *pBuf ) +{ + // Do BuildFacelights on the face. + CTimeAdder adder( &g_CPUTime ); + + BuildFacelights( iThread, iWorkUnit ); + + // Send the results. + if ( pBuf ) + { + SerializeFace( pBuf, iWorkUnit ); + } +} + + +void RunMPIBuildFacelights() +{ + g_CPUTime.Init(); + + Msg( "%-20s ", "BuildFaceLights:" ); + if ( g_bMPIMaster ) + { + StartPacifier(""); + } + + VMPI_SetCurrentStage( "RunMPIBuildFaceLights" ); + double elapsed = DistributeWork( + numfaces, + VMPI_DISTRIBUTEWORK_PACKETID, + MPI_ProcessFaces, + MPI_ReceiveFaceResults ); + + if ( g_bMPIMaster ) + { + EndPacifier(false); + Msg( " (%d)\n", (int)elapsed ); + } + + if ( g_bMPIMaster ) + { + // + // BuildPatchLights is normally called from BuildFacelights(), + // but in MPI mode we have the master do the calculation + // We might be able to speed this up by doing while the master + // is idling in the above loop. Wouldn't want to slow down the + // handing out of work - maybe another thread? + // + for ( int i=0; i < numfaces; ++i ) + { + BuildPatchLights(i); + } + } + else + { + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "\n\n%.1f%% CPU utilization during BuildFaceLights\n\n", ( g_CPUTime.GetSeconds() * 100 / elapsed ) ); + } +} + + +//----------------------------------------- +// +// Run BuildVisLeafs across all available processing nodes +// and collect the results. +// + +// This function is called when the master receives results back from a worker. +void MPI_ReceiveVisLeafsResults( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ) +{ + int patchesInCluster = 0; + + pBuf->read(&patchesInCluster, sizeof(patchesInCluster)); + + for ( int k=0; k < patchesInCluster; ++k ) + { + int patchnum = 0; + pBuf->read(&patchnum, sizeof(patchnum)); + + CPatch * patch = &g_Patches[patchnum]; + int numtransfers; + pBuf->read( &numtransfers, sizeof(numtransfers) ); + patch->numtransfers = numtransfers; + if (numtransfers) + { + patch->transfers = new transfer_t[numtransfers]; + pBuf->read(patch->transfers, numtransfers * sizeof(transfer_t)); + } + + total_transfer += numtransfers; + if (max_transfer < numtransfers) + max_transfer = numtransfers; + } +} + + +// Temporary variables used during callbacks. If we're going to be threadsafe, these +// should go in a structure and get passed around. +class CVMPIVisLeafsData +{ +public: + MessageBuffer *m_pVisLeafsMB; + int m_nPatchesInCluster; + transfer_t *m_pBuildVisLeafsTransfers; +}; + +CVMPIVisLeafsData g_VMPIVisLeafsData[MAX_TOOL_THREADS+1]; + + + +// This is called by BuildVisLeafs_Cluster every time it finishes a patch. +// The results are appended to g_VisLeafsMB and sent back to the master when all clusters are done. +void MPI_AddPatchData( int iThread, int patchnum, CPatch *patch ) +{ + CVMPIVisLeafsData *pData = &g_VMPIVisLeafsData[iThread]; + if ( pData->m_pVisLeafsMB ) + { + // Add in results for this patch + ++pData->m_nPatchesInCluster; + pData->m_pVisLeafsMB->write(&patchnum, sizeof(patchnum)); + pData->m_pVisLeafsMB->write(&patch->numtransfers, sizeof(patch->numtransfers)); + pData->m_pVisLeafsMB->write( patch->transfers, patch->numtransfers * sizeof(transfer_t) ); + } +} + + +// This handles a work unit sent by the master. Each work unit here is a +// list of clusters. +void MPI_ProcessVisLeafs( int iThread, uint64 iWorkUnit, MessageBuffer *pBuf ) +{ + CTimeAdder adder( &g_CPUTime ); + + CVMPIVisLeafsData *pData = &g_VMPIVisLeafsData[iThread]; + int iCluster = iWorkUnit; + + // Start this cluster. + pData->m_nPatchesInCluster = 0; + pData->m_pVisLeafsMB = pBuf; + + // Write a temp value in there. We overwrite it later. + int iSavePos = 0; + if ( pBuf ) + { + iSavePos = pBuf->getLen(); + pBuf->write( &pData->m_nPatchesInCluster, sizeof(pData->m_nPatchesInCluster) ); + } + + // Collect the results in MPI_AddPatchData. + BuildVisLeafs_Cluster( iThread, pData->m_pBuildVisLeafsTransfers, iCluster, MPI_AddPatchData ); + + // Now send the results back.. + if ( pBuf ) + { + pBuf->update( iSavePos, &pData->m_nPatchesInCluster, sizeof(pData->m_nPatchesInCluster) ); + pData->m_pVisLeafsMB = NULL; + } +} + + +void RunMPIBuildVisLeafs() +{ + g_CPUTime.Init(); + + Msg( "%-20s ", "BuildVisLeafs :" ); + if ( g_bMPIMaster ) + { + StartPacifier(""); + } + + memset( g_VMPIVisLeafsData, 0, sizeof( g_VMPIVisLeafsData ) ); + if ( !g_bMPIMaster || VMPI_GetActiveWorkUnitDistributor() == k_eWorkUnitDistributor_SDK ) + { + // Allocate space for the transfers for each thread. + for ( int i=0; i < numthreads; i++ ) + { + g_VMPIVisLeafsData[i].m_pBuildVisLeafsTransfers = BuildVisLeafs_Start(); + } + } + + // + // Slaves ask for work via GetMPIBuildVisLeafWork() + // Results are returned in BuildVisRow() + // + VMPI_SetCurrentStage( "RunMPIBuildVisLeafs" ); + + double elapsed = DistributeWork( + dvis->numclusters, + VMPI_DISTRIBUTEWORK_PACKETID, + MPI_ProcessVisLeafs, + MPI_ReceiveVisLeafsResults ); + + // Free the transfers from each thread. + for ( int i=0; i < numthreads; i++ ) + { + if ( g_VMPIVisLeafsData[i].m_pBuildVisLeafsTransfers ) + BuildVisLeafs_End( g_VMPIVisLeafsData[i].m_pBuildVisLeafsTransfers ); + } + + if ( g_bMPIMaster ) + { + EndPacifier(false); + Msg( " (%d)\n", (int)elapsed ); + } + else + { + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "%.1f%% CPU utilization during PortalFlow\n", (g_CPUTime.GetSeconds() * 100.0f / elapsed) / numthreads ); + } +} + +void VMPI_DistributeLightData() +{ + if ( !g_bUseMPI ) + return; + + if ( g_bMPIMaster ) + { + const char *pVirtualFilename = "--plightdata--"; + + CUtlBuffer lightFaceData; + + // write out the light data + lightFaceData.EnsureCapacity( pdlightdata->Count() + (numfaces * (MAXLIGHTMAPS+sizeof(int))) ); + Q_memcpy( lightFaceData.PeekPut(), pdlightdata->Base(), pdlightdata->Count() ); + lightFaceData.SeekPut( CUtlBuffer::SEEK_HEAD, pdlightdata->Count() ); + + // write out the relevant face info into the stream + for ( int i = 0; i < numfaces; i++ ) + { + for ( int j = 0; j < MAXLIGHTMAPS; j++ ) + { + lightFaceData.PutChar(g_pFaces[i].styles[j]); + } + lightFaceData.PutInt(g_pFaces[i].lightofs); + } + VMPI_FileSystem_CreateVirtualFile( pVirtualFilename, lightFaceData.Base(), lightFaceData.TellMaxPut() ); + + char cPacketID[2] = { VMPI_VRAD_PACKET_ID, VMPI_SUBPACKETID_PLIGHTDATA_RESULTS }; + VMPI_Send2Chunks( cPacketID, sizeof( cPacketID ), pVirtualFilename, strlen( pVirtualFilename ) + 1, VMPI_PERSISTENT ); + } + else + { + VMPI_SetCurrentStage( "VMPI_DistributeLightData" ); + + // Wait until we've received the filename from the master. + while ( g_LightResultsFilename.Count() == 0 ) + { + VMPI_DispatchNextMessage(); + } + + // Open + FileHandle_t fp = g_pFileSystem->Open( g_LightResultsFilename.Base(), "rb", VMPI_VIRTUAL_FILES_PATH_ID ); + if ( !fp ) + Error( "Can't open '%s' to read lighting info.", g_LightResultsFilename.Base() ); + + int size = g_pFileSystem->Size( fp ); + int faceSize = (numfaces*(MAXLIGHTMAPS+sizeof(int))); + + if ( size > faceSize ) + { + int lightSize = size - faceSize; + CUtlBuffer faceData; + pdlightdata->EnsureCount( lightSize ); + faceData.EnsureCapacity( faceSize ); + + g_pFileSystem->Read( pdlightdata->Base(), lightSize, fp ); + g_pFileSystem->Read( faceData.Base(), faceSize, fp ); + g_pFileSystem->Close( fp ); + + faceData.SeekPut( CUtlBuffer::SEEK_HEAD, faceSize ); + + // write out the face data + for ( int i = 0; i < numfaces; i++ ) + { + for ( int j = 0; j < MAXLIGHTMAPS; j++ ) + { + g_pFaces[i].styles[j] = faceData.GetChar(); + } + g_pFaces[i].lightofs = faceData.GetInt(); + } + } + } +} + + diff --git a/mp/src/utils/vrad/mpivrad.h b/mp/src/utils/vrad/mpivrad.h index 990b3df8..01c841b3 100644 --- a/mp/src/utils/vrad/mpivrad.h +++ b/mp/src/utils/vrad/mpivrad.h @@ -1,36 +1,36 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef MPIVRAD_H -#define MPIVRAD_H -#ifdef _WIN32 -#pragma once -#endif - - -#define VMPI_VRAD_PACKET_ID 1 - // Sub packet IDs. - #define VMPI_SUBPACKETID_VIS_LEAFS 0 - #define VMPI_SUBPACKETID_BUILDFACELIGHTS 1 - #define VMPI_SUBPACKETID_PLIGHTDATA_RESULTS 2 - -// DistributeWork owns this packet ID. -#define VMPI_DISTRIBUTEWORK_PACKETID 2 - - -// Called first thing in the exe. -void VRAD_SetupMPI( int &argc, char **&argv ); - -void RunMPIBuildFacelights(void); -void RunMPIBuildVisLeafs(void); -void VMPI_DistributeLightData(); - -// This handles disconnections. They're usually not fatal for the master. -void HandleMPIDisconnect( int procID ); - - -#endif // MPIVRAD_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MPIVRAD_H +#define MPIVRAD_H +#ifdef _WIN32 +#pragma once +#endif + + +#define VMPI_VRAD_PACKET_ID 1 + // Sub packet IDs. + #define VMPI_SUBPACKETID_VIS_LEAFS 0 + #define VMPI_SUBPACKETID_BUILDFACELIGHTS 1 + #define VMPI_SUBPACKETID_PLIGHTDATA_RESULTS 2 + +// DistributeWork owns this packet ID. +#define VMPI_DISTRIBUTEWORK_PACKETID 2 + + +// Called first thing in the exe. +void VRAD_SetupMPI( int &argc, char **&argv ); + +void RunMPIBuildFacelights(void); +void RunMPIBuildVisLeafs(void); +void VMPI_DistributeLightData(); + +// This handles disconnections. They're usually not fatal for the master. +void HandleMPIDisconnect( int procID ); + + +#endif // MPIVRAD_H diff --git a/mp/src/utils/vrad/origface.cpp b/mp/src/utils/vrad/origface.cpp index 43a19577..fbbe0e87 100644 --- a/mp/src/utils/vrad/origface.cpp +++ b/mp/src/utils/vrad/origface.cpp @@ -1,51 +1,51 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -#include "vrad.h" - -bool bOrigFacesTouched[MAX_MAP_FACES]; - - -//----------------------------------------------------------------------------- -// Pupose: clear (reset) the bOrigFacesTouched list -- parellels the original -// face list allowing an original face to only be processed once in -// pairing edges! -//----------------------------------------------------------------------------- -void ResetOrigFacesTouched( void ) -{ - for( int i = 0; i < MAX_MAP_FACES; i++ ) - { - bOrigFacesTouched[i] = false; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: mark an original faces as touched (dirty) -// Input: index - index of the original face touched -//----------------------------------------------------------------------------- -void SetOrigFaceTouched( int index ) -{ - bOrigFacesTouched[index] = true; -} - - -//----------------------------------------------------------------------------- -// Purpose: return whether or not an original face has been touched -// Input: index - index of the original face touched -// Output: true/false -//----------------------------------------------------------------------------- -bool IsOrigFaceTouched( int index ) -{ - return bOrigFacesTouched[index]; -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#include "vrad.h" + +bool bOrigFacesTouched[MAX_MAP_FACES]; + + +//----------------------------------------------------------------------------- +// Pupose: clear (reset) the bOrigFacesTouched list -- parellels the original +// face list allowing an original face to only be processed once in +// pairing edges! +//----------------------------------------------------------------------------- +void ResetOrigFacesTouched( void ) +{ + for( int i = 0; i < MAX_MAP_FACES; i++ ) + { + bOrigFacesTouched[i] = false; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: mark an original faces as touched (dirty) +// Input: index - index of the original face touched +//----------------------------------------------------------------------------- +void SetOrigFaceTouched( int index ) +{ + bOrigFacesTouched[index] = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: return whether or not an original face has been touched +// Input: index - index of the original face touched +// Output: true/false +//----------------------------------------------------------------------------- +bool IsOrigFaceTouched( int index ) +{ + return bOrigFacesTouched[index]; +} diff --git a/mp/src/utils/vrad/radial.cpp b/mp/src/utils/vrad/radial.cpp index 46e3b925..5dc99b50 100644 --- a/mp/src/utils/vrad/radial.cpp +++ b/mp/src/utils/vrad/radial.cpp @@ -1,882 +1,882 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "vrad.h" -#include "lightmap.h" -#include "radial.h" -#include "mathlib/bumpvects.h" -#include "utlrbtree.h" -#include "mathlib/VMatrix.h" -#include "macro_texture.h" - - -void WorldToLuxelSpace( lightinfo_t const *l, Vector const &world, Vector2D &coord ) -{ - Vector pos; - - VectorSubtract( world, l->luxelOrigin, pos ); - coord[0] = DotProduct( pos, l->worldToLuxelSpace[0] ) - l->face->m_LightmapTextureMinsInLuxels[0]; - coord[1] = DotProduct( pos, l->worldToLuxelSpace[1] ) - l->face->m_LightmapTextureMinsInLuxels[1]; -} - -void LuxelSpaceToWorld( lightinfo_t const *l, float s, float t, Vector &world ) -{ - Vector pos; - - s += l->face->m_LightmapTextureMinsInLuxels[0]; - t += l->face->m_LightmapTextureMinsInLuxels[1]; - VectorMA( l->luxelOrigin, s, l->luxelToWorldSpace[0], pos ); - VectorMA( pos, t, l->luxelToWorldSpace[1], world ); -} - -void WorldToLuxelSpace( lightinfo_t const *l, FourVectors const &world, FourVectors &coord ) -{ - FourVectors luxelOrigin; - luxelOrigin.DuplicateVector ( l->luxelOrigin ); - - FourVectors pos = world; - pos -= luxelOrigin; - - coord.x = pos * l->worldToLuxelSpace[0]; - coord.x = SubSIMD ( coord.x, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[0] ) ); - coord.y = pos * l->worldToLuxelSpace[1]; - coord.y = SubSIMD ( coord.y, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[1] ) ); - coord.z = Four_Zeros; -} - -void LuxelSpaceToWorld( lightinfo_t const *l, fltx4 s, fltx4 t, FourVectors &world ) -{ - world.DuplicateVector ( l->luxelOrigin ); - FourVectors st; - - s = AddSIMD ( s, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[0] ) ); - st.DuplicateVector ( l->luxelToWorldSpace[0] ); - st *= s; - world += st; - - t = AddSIMD ( t, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[1] ) ); - st.DuplicateVector ( l->luxelToWorldSpace[1] ); - st *= t; - world += st; -} - - - -void AddDirectToRadial( radial_t *rad, - Vector const &pnt, - Vector2D const &coordmins, Vector2D const &coordmaxs, - LightingValue_t const light[NUM_BUMP_VECTS+1], - bool hasBumpmap, bool neighborHasBumpmap ) -{ - int s_min, s_max, t_min, t_max; - Vector2D coord; - int s, t; - float ds, dt; - float r; - float area; - int bumpSample; - - // convert world pos into local lightmap texture coord - WorldToLuxelSpace( &rad->l, pnt, coord ); - - s_min = ( int )( coordmins[0] ); - t_min = ( int )( coordmins[1] ); - s_max = ( int )( coordmaxs[0] + 0.9999f ) + 1; // ???? - t_max = ( int )( coordmaxs[1] + 0.9999f ) + 1; - - s_min = max( s_min, 0 ); - t_min = max( t_min, 0 ); - s_max = min( s_max, rad->w ); - t_max = min( t_max, rad->h ); - - for( s = s_min; s < s_max; s++ ) - { - for( t = t_min; t < t_max; t++ ) - { - float s0 = max( coordmins[0] - s, -1.0 ); - float t0 = max( coordmins[1] - t, -1.0 ); - float s1 = min( coordmaxs[0] - s, 1.0 ); - float t1 = min( coordmaxs[1] - t, 1.0 ); - - area = (s1 - s0) * (t1 - t0); - - if (area > EQUAL_EPSILON) - { - ds = fabs( coord[0] - s ); - dt = fabs( coord[1] - t ); - - r = max( ds, dt ); - - if (r < 0.1) - { - r = area / 0.1; - } - else - { - r = area / r; - } - - int i = s+t*rad->w; - - if( hasBumpmap ) - { - if( neighborHasBumpmap ) - { - for( bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) - { - rad->light[bumpSample][i].AddWeighted( light[bumpSample], r ); - } - } - else - { - rad->light[0][i].AddWeighted(light[0],r ); - for( bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) - { - rad->light[bumpSample][i].AddWeighted( light[0], r * OO_SQRT_3 ); - } - } - } - else - { - rad->light[0][i].AddWeighted( light[0], r ); - } - - rad->weight[i] += r; - } - } - } -} - - - -void AddBouncedToRadial( radial_t *rad, - Vector const &pnt, - Vector2D const &coordmins, Vector2D const &coordmaxs, - Vector const light[NUM_BUMP_VECTS+1], - bool hasBumpmap, bool neighborHasBumpmap ) -{ - int s_min, s_max, t_min, t_max; - Vector2D coord; - int s, t; - float ds, dt; - float r; - int bumpSample; - - // convert world pos into local lightmap texture coord - WorldToLuxelSpace( &rad->l, pnt, coord ); - - float dists, distt; - - dists = (coordmaxs[0] - coordmins[0]); - distt = (coordmaxs[1] - coordmins[1]); - - // patches less than a luxel in size could be mistakeningly filtered, so clamp. - dists = max( 1.0, dists ); - distt = max( 1.0, distt ); - - // find possible domain of patch influence - s_min = ( int )( coord[0] - dists * RADIALDIST ); - t_min = ( int )( coord[1] - distt * RADIALDIST ); - s_max = ( int )( coord[0] + dists * RADIALDIST + 1.0f ); - t_max = ( int )( coord[1] + distt * RADIALDIST + 1.0f ); - - // clamp to valid luxel - s_min = max( s_min, 0 ); - t_min = max( t_min, 0 ); - s_max = min( s_max, rad->w ); - t_max = min( t_max, rad->h ); - - for( s = s_min; s < s_max; s++ ) - { - for( t = t_min; t < t_max; t++ ) - { - // patch influence is based on patch size - ds = ( coord[0] - s ) / dists; - dt = ( coord[1] - t ) / distt; - - r = RADIALDIST2 - (ds * ds + dt * dt); - - int i = s+t*rad->w; - - if (r > 0) - { - if( hasBumpmap ) - { - if( neighborHasBumpmap ) - { - for( bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) - { - rad->light[bumpSample][i].AddWeighted( light[bumpSample], r ); - } - } - else - { - rad->light[0][i].AddWeighted( light[0], r ); - for( bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) - { - rad->light[bumpSample][i].AddWeighted( light[0], r * OO_SQRT_3 ); - } - } - } - else - { - rad->light[0][i].AddWeighted( light[0], r ); - } - - rad->weight[i] += r; - } - } - } -} - -void PatchLightmapCoordRange( radial_t *rad, int ndxPatch, Vector2D &mins, Vector2D &maxs ) -{ - winding_t *w; - int i; - Vector2D coord; - - mins.Init( 1E30, 1E30 ); - maxs.Init( -1E30, -1E30 ); - - CPatch *patch = &g_Patches.Element( ndxPatch ); - w = patch->winding; - - for (i = 0; i < w->numpoints; i++) - { - WorldToLuxelSpace( &rad->l, w->p[i], coord ); - mins[0] = min( mins[0], coord[0] ); - maxs[0] = max( maxs[0], coord[0] ); - mins[1] = min( mins[1], coord[1] ); - maxs[1] = max( maxs[1], coord[1] ); - } -} - -radial_t *AllocateRadial( int facenum ) -{ - radial_t *rad; - - rad = ( radial_t* )calloc( 1, sizeof( *rad ) ); - - rad->facenum = facenum; - InitLightinfo( &rad->l, facenum ); - - rad->w = rad->l.face->m_LightmapTextureSizeInLuxels[0]+1; - rad->h = rad->l.face->m_LightmapTextureSizeInLuxels[1]+1; - - return rad; -} - -void FreeRadial( radial_t *rad ) -{ - if (rad) - free( rad ); -} - - -radial_t *BuildPatchRadial( int facenum ) -{ - int j; - radial_t *rad; - CPatch *patch; - faceneighbor_t *fn; - Vector2D mins, maxs; - bool needsBumpmap, neighborNeedsBumpmap; - - needsBumpmap = texinfo[g_pFaces[facenum].texinfo].flags & SURF_BUMPLIGHT ? true : false; - - rad = AllocateRadial( facenum ); - - fn = &faceneighbor[ rad->facenum ]; - - CPatch *pNextPatch; - - if( g_FacePatches.Element( rad->facenum ) != g_FacePatches.InvalidIndex() ) - { - for( patch = &g_Patches.Element( g_FacePatches.Element( rad->facenum ) ); patch; patch = pNextPatch ) - { - // next patch - pNextPatch = NULL; - if( patch->ndxNext != g_Patches.InvalidIndex() ) - { - pNextPatch = &g_Patches.Element( patch->ndxNext ); - } - - // skip patches with children - if (patch->child1 != g_Patches.InvalidIndex() ) - continue; - - // get the range of patch lightmap texture coords - int ndxPatch = patch - g_Patches.Base(); - PatchLightmapCoordRange( rad, ndxPatch, mins, maxs ); - - if (patch->numtransfers == 0) - { - // Error, using patch that was never evaluated or has no samples - // patch->totallight[1] = 255; - } - - // - // displacement surface patch origin position and normal vectors have been changed to - // represent the displacement surface position and normal -- for radial "blending" - // we need to get the base surface patch origin! - // - if( ValidDispFace( &g_pFaces[facenum] ) ) - { - Vector patchOrigin; - WindingCenter (patch->winding, patchOrigin ); - AddBouncedToRadial( rad, patchOrigin, mins, maxs, patch->totallight.light, - needsBumpmap, needsBumpmap ); - } - else - { - AddBouncedToRadial( rad, patch->origin, mins, maxs, patch->totallight.light, - needsBumpmap, needsBumpmap ); - } - } - } - - for (j=0 ; jnumneighbors; j++) - { - if( g_FacePatches.Element( fn->neighbor[j] ) != g_FacePatches.InvalidIndex() ) - { - for( patch = &g_Patches.Element( g_FacePatches.Element( fn->neighbor[j] ) ); patch; patch = pNextPatch ) - { - // next patch - pNextPatch = NULL; - if( patch->ndxNext != g_Patches.InvalidIndex() ) - { - pNextPatch = &g_Patches.Element( patch->ndxNext ); - } - - // skip patches with children - if (patch->child1 != g_Patches.InvalidIndex() ) - continue; - - // get the range of patch lightmap texture coords - int ndxPatch = patch - g_Patches.Base(); - PatchLightmapCoordRange( rad, ndxPatch, mins, maxs ); - - neighborNeedsBumpmap = texinfo[g_pFaces[facenum].texinfo].flags & SURF_BUMPLIGHT ? true : false; - - // - // displacement surface patch origin position and normal vectors have been changed to - // represent the displacement surface position and normal -- for radial "blending" - // we need to get the base surface patch origin! - // - if( ValidDispFace( &g_pFaces[fn->neighbor[j]] ) ) - { - Vector patchOrigin; - WindingCenter (patch->winding, patchOrigin ); - AddBouncedToRadial( rad, patchOrigin, mins, maxs, patch->totallight.light, - needsBumpmap, needsBumpmap ); - } - else - { - AddBouncedToRadial( rad, patch->origin, mins, maxs, patch->totallight.light, - needsBumpmap, needsBumpmap ); - } - } - } - } - - return rad; -} - - -radial_t *BuildLuxelRadial( int facenum, int style ) -{ - LightingValue_t light[NUM_BUMP_VECTS + 1]; - - facelight_t *fl = &facelight[facenum]; - faceneighbor_t *fn = &faceneighbor[facenum]; - - radial_t *rad = AllocateRadial( facenum ); - - bool needsBumpmap = texinfo[g_pFaces[facenum].texinfo].flags & SURF_BUMPLIGHT ? true : false; - - for (int k=0 ; knumsamples ; k++) - { - if( needsBumpmap ) - { - for( int bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) - { - light[bumpSample] = fl->light[style][bumpSample][k]; - } - } - else - { - light[0] = fl->light[style][0][k]; - } - - AddDirectToRadial( rad, fl->sample[k].pos, fl->sample[k].mins, fl->sample[k].maxs, light, needsBumpmap, needsBumpmap ); - } - - for (int j = 0; j < fn->numneighbors; j++) - { - fl = &facelight[fn->neighbor[j]]; - - bool neighborHasBumpmap = false; - - if( texinfo[g_pFaces[fn->neighbor[j]].texinfo].flags & SURF_BUMPLIGHT ) - { - neighborHasBumpmap = true; - } - - int nstyle = 0; - - // look for style that matches - if (g_pFaces[fn->neighbor[j]].styles[nstyle] != g_pFaces[facenum].styles[style]) - { - for (nstyle = 1; nstyle < MAXLIGHTMAPS; nstyle++ ) - if ( g_pFaces[fn->neighbor[j]].styles[nstyle] == g_pFaces[facenum].styles[style] ) - break; - - // if not found, skip this neighbor - if (nstyle >= MAXLIGHTMAPS) - continue; - } - - lightinfo_t l; - - InitLightinfo( &l, fn->neighbor[j] ); - - for (int k=0 ; knumsamples ; k++) - { - if( neighborHasBumpmap ) - { - for( int bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) - { - light[bumpSample] = fl->light[nstyle][bumpSample][k]; - } - } - else - { - light[0]=fl->light[nstyle][0][k]; - } - - Vector tmp; - Vector2D mins, maxs; - - LuxelSpaceToWorld( &l, fl->sample[k].mins[0], fl->sample[k].mins[1], tmp ); - WorldToLuxelSpace( &rad->l, tmp, mins ); - LuxelSpaceToWorld( &l, fl->sample[k].maxs[0], fl->sample[k].maxs[1], tmp ); - WorldToLuxelSpace( &rad->l, tmp, maxs ); - - AddDirectToRadial( rad, fl->sample[k].pos, mins, maxs, light, - needsBumpmap, neighborHasBumpmap ); - } - } - - return rad; -} - - -//----------------------------------------------------------------------------- -// Purpose: returns the closest light value for a given point on the surface -// this is normally a 1:1 mapping -//----------------------------------------------------------------------------- -bool SampleRadial( radial_t *rad, Vector& pnt, LightingValue_t light[NUM_BUMP_VECTS + 1], int bumpSampleCount ) -{ - int bumpSample; - Vector2D coord; - - WorldToLuxelSpace( &rad->l, pnt, coord ); - int u = ( int )( coord[0] + 0.5f ); - int v = ( int )( coord[1] + 0.5f ); - int i = u + v * rad->w; - - if (u < 0 || u > rad->w || v < 0 || v > rad->h) - { - static bool warning = false; - if ( !warning ) - { - // punting over to KenB - // 2d coord indexes off of lightmap, generation of pnt seems suspect - Warning( "SampleRadial: Punting, Waiting for fix\n" ); - warning = true; - } - for( bumpSample = 0; bumpSample < bumpSampleCount; bumpSample++ ) - { - light[bumpSample].m_vecLighting.Init( 2550, 0, 0 ); - } - return false; - } - - bool baseSampleOk = true; - for( bumpSample = 0; bumpSample < bumpSampleCount; bumpSample++ ) - { - light[bumpSample].Zero(); - - if (rad->weight[i] > WEIGHT_EPS) - { - light[bumpSample]= rad->light[bumpSample][i]; - light[bumpSample].Scale( 1.0 / rad->weight[i] ); - } - else - { - if ( bRed2Black ) - { - // Error, luxel has no samples - light[bumpSample].m_vecLighting.Init( 0, 0, 0 ); - } - else - { - // Error, luxel has no samples - // Yes, it actually should be 2550 - light[bumpSample].m_vecLighting.Init( 2550, 0, 0 ); - } - - if (bumpSample == 0) - baseSampleOk = false; - } - } - - return baseSampleOk; -} - -bool FloatLess( float const& src1, float const& src2 ) -{ - return src1 < src2; -} - - -//----------------------------------------------------------------------------- -// Debugging! -//----------------------------------------------------------------------------- -void GetRandomColor( unsigned char *color ) -{ - static bool firstTime = true; - - if( firstTime ) - { - firstTime = false; - srand( 0 ); - } - - color[0] = ( unsigned char )( rand() * ( 255.0f / VALVE_RAND_MAX ) ); - color[1] = ( unsigned char )( rand() * ( 255.0f / VALVE_RAND_MAX ) ); - color[2] = ( unsigned char )( rand() * ( 255.0f / VALVE_RAND_MAX ) ); -} - - -#if 0 -// debugging! -- not accurate! -void DumpLuxels( facelight_t *pFaceLight, Vector *luxelColors, int ndxFace ) -{ - static FileHandle_t pFpLuxels = NULL; - - ThreadLock(); - - if( !pFpLuxels ) - { - pFpLuxels = g_pFileSystem->Open( "luxels.txt", "w" ); - } - - dface_t *pFace = &g_pFaces[ndxFace]; - bool bDisp = ( pFace->dispinfo != -1 ); - - for( int ndx = 0; ndx < pFaceLight->numluxels; ndx++ ) - { - WriteWinding( pFpLuxels, pFaceLight->sample[ndx].w, luxelColors[ndx] ); - if( bDumpNormals && bDisp ) - { - WriteNormal( pFpLuxels, pFaceLight->luxel[ndx], pFaceLight->luxelNormals[ndx], 15.0f, Vector( 255, 255, 0 ) ); - } - } - - ThreadUnlock(); -} -#endif - - -static FileHandle_t pFileLuxels[4] = { NULL, NULL, NULL, NULL }; - -void DumpDispLuxels( int iFace, Vector &color, int iLuxel, int nBump ) -{ - // Lock the thread and dump the luxel data. - ThreadLock(); - - // Get the face and facelight data. - facelight_t *pFaceLight = &facelight[iFace]; - - // Open the luxel files. - char szFileName[512]; - for ( int iBump = 0; iBump < ( NUM_BUMP_VECTS+1 ); ++iBump ) - { - if ( pFileLuxels[iBump] == NULL ) - { - sprintf( szFileName, "luxels_bump%d.txt", iBump ); - pFileLuxels[iBump] = g_pFileSystem->Open( szFileName, "w" ); - } - } - - WriteWinding( pFileLuxels[nBump], pFaceLight->sample[iLuxel].w, color ); - - ThreadUnlock(); -} - -void CloseDispLuxels() -{ - for ( int iBump = 0; iBump < ( NUM_BUMP_VECTS+1 ); ++iBump ) - { - if ( pFileLuxels[iBump] ) - { - g_pFileSystem->Close( pFileLuxels[iBump] ); - } - } -} - -/* -============= -FinalLightFace - -Add the indirect lighting on top of the direct -lighting and save into final map format -============= -*/ -void FinalLightFace( int iThread, int facenum ) -{ - dface_t *f; - int i, j, k; - facelight_t *fl; - float minlight; - int lightstyles; - LightingValue_t lb[NUM_BUMP_VECTS + 1], v[NUM_BUMP_VECTS + 1]; - unsigned char *pdata[NUM_BUMP_VECTS + 1]; - int bumpSample; - radial_t *rad = NULL; - radial_t *prad = NULL; - - f = &g_pFaces[facenum]; - - // test for non-lit texture - if ( texinfo[f->texinfo].flags & TEX_SPECIAL) - return; - - fl = &facelight[facenum]; - - - for (lightstyles=0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) - { - if ( f->styles[lightstyles] == 255 ) - break; - } - if ( !lightstyles ) - return; - - - // - // sample the triangulation - // - minlight = FloatForKey (face_entity[facenum], "_minlight") * 128; - - bool needsBumpmap = ( texinfo[f->texinfo].flags & SURF_BUMPLIGHT ) ? true : false; - int bumpSampleCount = needsBumpmap ? NUM_BUMP_VECTS + 1 : 1; - - bool bDisp = ( f->dispinfo != -1 ); - -//#define RANDOM_COLOR - -#ifdef RANDOM_COLOR - unsigned char randomColor[3]; - GetRandomColor( randomColor ); -#endif - - - // NOTE: I'm using these RB trees to sort all the illumination values - // to compute median colors. Turns out that this is a somewhat better - // method that using the average; usually if there are surfaces - // with a large light intensity variation, the extremely bright regions - // have a very small area and tend to influence the average too much. - CUtlRBTree< float, int > m_Red( 0, 256, FloatLess ); - CUtlRBTree< float, int > m_Green( 0, 256, FloatLess ); - CUtlRBTree< float, int > m_Blue( 0, 256, FloatLess ); - - for (k=0 ; k < lightstyles; k++ ) - { - m_Red.RemoveAll(); - m_Green.RemoveAll(); - m_Blue.RemoveAll(); - - if (!do_fast) - { - if( !bDisp ) - { - rad = BuildLuxelRadial( facenum, k ); - } - else - { - rad = StaticDispMgr()->BuildLuxelRadial( facenum, k, needsBumpmap ); - } - } - - if (numbounce > 0 && k == 0) - { - // currently only radiosity light non-displacement surfaces! - if( !bDisp ) - { - prad = BuildPatchRadial( facenum ); - } - else - { - prad = StaticDispMgr()->BuildPatchRadial( facenum, needsBumpmap ); - } - } - - // pack the nonbump texture and the three bump texture for the given - // lightstyle right next to each other. - // NOTE: Even though it's building positions for all bump-mapped data, - // it isn't going to use those positions (see loop over bumpSample below) - // The file offset is correctly computed to only store space for 1 set - // of light data if we don't have bumped lighting. - for( bumpSample = 0; bumpSample < bumpSampleCount; ++bumpSample ) - { - pdata[bumpSample] = &(*pdlightdata)[f->lightofs + (k * bumpSampleCount + bumpSample) * fl->numluxels*4]; - } - - // Compute the average luxel color, but not for the bump samples - Vector avg( 0.0f, 0.0f, 0.0f ); - int avgCount = 0; - - for (j=0 ; jnumluxels; j++) - { - // garymct - direct lighting - bool baseSampleOk = true; - - if (!do_fast) - { - if( !bDisp ) - { - baseSampleOk = SampleRadial( rad, fl->luxel[j], lb, bumpSampleCount ); - } - else - { - baseSampleOk = StaticDispMgr()->SampleRadial( facenum, rad, fl->luxel[j], j, lb, bumpSampleCount, false ); - } - } - else - { - for ( int iBump = 0 ; iBump < bumpSampleCount; iBump++ ) - { - lb[iBump] = fl->light[0][iBump][j]; - } - } - - if (prad) - { - // garymct - bounced light - // v is indirect light that is received on the luxel. - if( !bDisp ) - { - SampleRadial( prad, fl->luxel[j], v, bumpSampleCount ); - } - else - { - StaticDispMgr()->SampleRadial( facenum, prad, fl->luxel[j], j, v, bumpSampleCount, true ); - } - - for( bumpSample = 0; bumpSample < bumpSampleCount; ++bumpSample ) - { - lb[bumpSample].AddLight( v[bumpSample] ); - } - } - - if ( bDisp && g_bDumpPatches ) - { - for( bumpSample = 0; bumpSample < bumpSampleCount; ++bumpSample ) - { - DumpDispLuxels( facenum, lb[bumpSample].m_vecLighting, j, bumpSample ); - } - } - - if (fl->numsamples == 0) - { - for( i = 0; i < bumpSampleCount; i++ ) - { - lb[i].Init( 255, 0, 0 ); - } - baseSampleOk = false; - } - - int bumpSample; - for( bumpSample = 0; bumpSample < bumpSampleCount; bumpSample++ ) - { - // clip from the bottom first - // garymct: minlight is a per entity minimum light value? - for( i=0; i<3; i++ ) - { - lb[bumpSample].m_vecLighting[i] = max( lb[bumpSample].m_vecLighting[i], minlight ); - } - - // Do the average light computation, I'm assuming (perhaps incorrectly?) - // that all luxels in a particular lightmap have the same area here. - // Also, don't bother doing averages for the bump samples. Doing it here - // because of the minlight clamp above + the random color testy thingy. - // Also have to do it before Vec3toColorRGBExp32 because it - // destructively modifies lb[bumpSample] (Feh!) - if ((bumpSample == 0) && baseSampleOk) - { - ++avgCount; - - ApplyMacroTextures( facenum, fl->luxel[j], lb[0].m_vecLighting ); - - // For median computation - m_Red.Insert( lb[bumpSample].m_vecLighting[0] ); - m_Green.Insert( lb[bumpSample].m_vecLighting[1] ); - m_Blue.Insert( lb[bumpSample].m_vecLighting[2] ); - } - -#ifdef RANDOM_COLOR - pdata[bumpSample][0] = randomColor[0] / ( bumpSample + 1 ); - pdata[bumpSample][1] = randomColor[1] / ( bumpSample + 1 ); - pdata[bumpSample][2] = randomColor[2] / ( bumpSample + 1 ); - pdata[bumpSample][3] = 0; -#else - // convert to a 4 byte r,g,b,signed exponent format - VectorToColorRGBExp32( Vector( lb[bumpSample].m_vecLighting.x, lb[bumpSample].m_vecLighting.y, - lb[bumpSample].m_vecLighting.z ), *( ColorRGBExp32 *)pdata[bumpSample] ); -#endif - - pdata[bumpSample] += 4; - } - } - FreeRadial( rad ); - if (prad) - { - FreeRadial( prad ); - prad = NULL; - } - - // Compute the median color for this lightstyle - // Remember, the data goes *before* the specified light_ofs, in *reverse order* - ColorRGBExp32 *pAvgColor = dface_AvgLightColor( f, k ); - if (avgCount == 0) - { - Vector median( 0, 0, 0 ); - VectorToColorRGBExp32( median, *pAvgColor ); - } - else - { - unsigned int r, g, b; - r = m_Red.FirstInorder(); - g = m_Green.FirstInorder(); - b = m_Blue.FirstInorder(); - avgCount >>= 1; - while (avgCount > 0) - { - r = m_Red.NextInorder(r); - g = m_Green.NextInorder(g); - b = m_Blue.NextInorder(b); - --avgCount; - } - - Vector median( m_Red[r], m_Green[g], m_Blue[b] ); - VectorToColorRGBExp32( median, *pAvgColor ); - } - } -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vrad.h" +#include "lightmap.h" +#include "radial.h" +#include "mathlib/bumpvects.h" +#include "utlrbtree.h" +#include "mathlib/VMatrix.h" +#include "macro_texture.h" + + +void WorldToLuxelSpace( lightinfo_t const *l, Vector const &world, Vector2D &coord ) +{ + Vector pos; + + VectorSubtract( world, l->luxelOrigin, pos ); + coord[0] = DotProduct( pos, l->worldToLuxelSpace[0] ) - l->face->m_LightmapTextureMinsInLuxels[0]; + coord[1] = DotProduct( pos, l->worldToLuxelSpace[1] ) - l->face->m_LightmapTextureMinsInLuxels[1]; +} + +void LuxelSpaceToWorld( lightinfo_t const *l, float s, float t, Vector &world ) +{ + Vector pos; + + s += l->face->m_LightmapTextureMinsInLuxels[0]; + t += l->face->m_LightmapTextureMinsInLuxels[1]; + VectorMA( l->luxelOrigin, s, l->luxelToWorldSpace[0], pos ); + VectorMA( pos, t, l->luxelToWorldSpace[1], world ); +} + +void WorldToLuxelSpace( lightinfo_t const *l, FourVectors const &world, FourVectors &coord ) +{ + FourVectors luxelOrigin; + luxelOrigin.DuplicateVector ( l->luxelOrigin ); + + FourVectors pos = world; + pos -= luxelOrigin; + + coord.x = pos * l->worldToLuxelSpace[0]; + coord.x = SubSIMD ( coord.x, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[0] ) ); + coord.y = pos * l->worldToLuxelSpace[1]; + coord.y = SubSIMD ( coord.y, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[1] ) ); + coord.z = Four_Zeros; +} + +void LuxelSpaceToWorld( lightinfo_t const *l, fltx4 s, fltx4 t, FourVectors &world ) +{ + world.DuplicateVector ( l->luxelOrigin ); + FourVectors st; + + s = AddSIMD ( s, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[0] ) ); + st.DuplicateVector ( l->luxelToWorldSpace[0] ); + st *= s; + world += st; + + t = AddSIMD ( t, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[1] ) ); + st.DuplicateVector ( l->luxelToWorldSpace[1] ); + st *= t; + world += st; +} + + + +void AddDirectToRadial( radial_t *rad, + Vector const &pnt, + Vector2D const &coordmins, Vector2D const &coordmaxs, + LightingValue_t const light[NUM_BUMP_VECTS+1], + bool hasBumpmap, bool neighborHasBumpmap ) +{ + int s_min, s_max, t_min, t_max; + Vector2D coord; + int s, t; + float ds, dt; + float r; + float area; + int bumpSample; + + // convert world pos into local lightmap texture coord + WorldToLuxelSpace( &rad->l, pnt, coord ); + + s_min = ( int )( coordmins[0] ); + t_min = ( int )( coordmins[1] ); + s_max = ( int )( coordmaxs[0] + 0.9999f ) + 1; // ???? + t_max = ( int )( coordmaxs[1] + 0.9999f ) + 1; + + s_min = max( s_min, 0 ); + t_min = max( t_min, 0 ); + s_max = min( s_max, rad->w ); + t_max = min( t_max, rad->h ); + + for( s = s_min; s < s_max; s++ ) + { + for( t = t_min; t < t_max; t++ ) + { + float s0 = max( coordmins[0] - s, -1.0 ); + float t0 = max( coordmins[1] - t, -1.0 ); + float s1 = min( coordmaxs[0] - s, 1.0 ); + float t1 = min( coordmaxs[1] - t, 1.0 ); + + area = (s1 - s0) * (t1 - t0); + + if (area > EQUAL_EPSILON) + { + ds = fabs( coord[0] - s ); + dt = fabs( coord[1] - t ); + + r = max( ds, dt ); + + if (r < 0.1) + { + r = area / 0.1; + } + else + { + r = area / r; + } + + int i = s+t*rad->w; + + if( hasBumpmap ) + { + if( neighborHasBumpmap ) + { + for( bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + rad->light[bumpSample][i].AddWeighted( light[bumpSample], r ); + } + } + else + { + rad->light[0][i].AddWeighted(light[0],r ); + for( bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + rad->light[bumpSample][i].AddWeighted( light[0], r * OO_SQRT_3 ); + } + } + } + else + { + rad->light[0][i].AddWeighted( light[0], r ); + } + + rad->weight[i] += r; + } + } + } +} + + + +void AddBouncedToRadial( radial_t *rad, + Vector const &pnt, + Vector2D const &coordmins, Vector2D const &coordmaxs, + Vector const light[NUM_BUMP_VECTS+1], + bool hasBumpmap, bool neighborHasBumpmap ) +{ + int s_min, s_max, t_min, t_max; + Vector2D coord; + int s, t; + float ds, dt; + float r; + int bumpSample; + + // convert world pos into local lightmap texture coord + WorldToLuxelSpace( &rad->l, pnt, coord ); + + float dists, distt; + + dists = (coordmaxs[0] - coordmins[0]); + distt = (coordmaxs[1] - coordmins[1]); + + // patches less than a luxel in size could be mistakeningly filtered, so clamp. + dists = max( 1.0, dists ); + distt = max( 1.0, distt ); + + // find possible domain of patch influence + s_min = ( int )( coord[0] - dists * RADIALDIST ); + t_min = ( int )( coord[1] - distt * RADIALDIST ); + s_max = ( int )( coord[0] + dists * RADIALDIST + 1.0f ); + t_max = ( int )( coord[1] + distt * RADIALDIST + 1.0f ); + + // clamp to valid luxel + s_min = max( s_min, 0 ); + t_min = max( t_min, 0 ); + s_max = min( s_max, rad->w ); + t_max = min( t_max, rad->h ); + + for( s = s_min; s < s_max; s++ ) + { + for( t = t_min; t < t_max; t++ ) + { + // patch influence is based on patch size + ds = ( coord[0] - s ) / dists; + dt = ( coord[1] - t ) / distt; + + r = RADIALDIST2 - (ds * ds + dt * dt); + + int i = s+t*rad->w; + + if (r > 0) + { + if( hasBumpmap ) + { + if( neighborHasBumpmap ) + { + for( bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + rad->light[bumpSample][i].AddWeighted( light[bumpSample], r ); + } + } + else + { + rad->light[0][i].AddWeighted( light[0], r ); + for( bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + rad->light[bumpSample][i].AddWeighted( light[0], r * OO_SQRT_3 ); + } + } + } + else + { + rad->light[0][i].AddWeighted( light[0], r ); + } + + rad->weight[i] += r; + } + } + } +} + +void PatchLightmapCoordRange( radial_t *rad, int ndxPatch, Vector2D &mins, Vector2D &maxs ) +{ + winding_t *w; + int i; + Vector2D coord; + + mins.Init( 1E30, 1E30 ); + maxs.Init( -1E30, -1E30 ); + + CPatch *patch = &g_Patches.Element( ndxPatch ); + w = patch->winding; + + for (i = 0; i < w->numpoints; i++) + { + WorldToLuxelSpace( &rad->l, w->p[i], coord ); + mins[0] = min( mins[0], coord[0] ); + maxs[0] = max( maxs[0], coord[0] ); + mins[1] = min( mins[1], coord[1] ); + maxs[1] = max( maxs[1], coord[1] ); + } +} + +radial_t *AllocateRadial( int facenum ) +{ + radial_t *rad; + + rad = ( radial_t* )calloc( 1, sizeof( *rad ) ); + + rad->facenum = facenum; + InitLightinfo( &rad->l, facenum ); + + rad->w = rad->l.face->m_LightmapTextureSizeInLuxels[0]+1; + rad->h = rad->l.face->m_LightmapTextureSizeInLuxels[1]+1; + + return rad; +} + +void FreeRadial( radial_t *rad ) +{ + if (rad) + free( rad ); +} + + +radial_t *BuildPatchRadial( int facenum ) +{ + int j; + radial_t *rad; + CPatch *patch; + faceneighbor_t *fn; + Vector2D mins, maxs; + bool needsBumpmap, neighborNeedsBumpmap; + + needsBumpmap = texinfo[g_pFaces[facenum].texinfo].flags & SURF_BUMPLIGHT ? true : false; + + rad = AllocateRadial( facenum ); + + fn = &faceneighbor[ rad->facenum ]; + + CPatch *pNextPatch; + + if( g_FacePatches.Element( rad->facenum ) != g_FacePatches.InvalidIndex() ) + { + for( patch = &g_Patches.Element( g_FacePatches.Element( rad->facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + // skip patches with children + if (patch->child1 != g_Patches.InvalidIndex() ) + continue; + + // get the range of patch lightmap texture coords + int ndxPatch = patch - g_Patches.Base(); + PatchLightmapCoordRange( rad, ndxPatch, mins, maxs ); + + if (patch->numtransfers == 0) + { + // Error, using patch that was never evaluated or has no samples + // patch->totallight[1] = 255; + } + + // + // displacement surface patch origin position and normal vectors have been changed to + // represent the displacement surface position and normal -- for radial "blending" + // we need to get the base surface patch origin! + // + if( ValidDispFace( &g_pFaces[facenum] ) ) + { + Vector patchOrigin; + WindingCenter (patch->winding, patchOrigin ); + AddBouncedToRadial( rad, patchOrigin, mins, maxs, patch->totallight.light, + needsBumpmap, needsBumpmap ); + } + else + { + AddBouncedToRadial( rad, patch->origin, mins, maxs, patch->totallight.light, + needsBumpmap, needsBumpmap ); + } + } + } + + for (j=0 ; jnumneighbors; j++) + { + if( g_FacePatches.Element( fn->neighbor[j] ) != g_FacePatches.InvalidIndex() ) + { + for( patch = &g_Patches.Element( g_FacePatches.Element( fn->neighbor[j] ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + // skip patches with children + if (patch->child1 != g_Patches.InvalidIndex() ) + continue; + + // get the range of patch lightmap texture coords + int ndxPatch = patch - g_Patches.Base(); + PatchLightmapCoordRange( rad, ndxPatch, mins, maxs ); + + neighborNeedsBumpmap = texinfo[g_pFaces[facenum].texinfo].flags & SURF_BUMPLIGHT ? true : false; + + // + // displacement surface patch origin position and normal vectors have been changed to + // represent the displacement surface position and normal -- for radial "blending" + // we need to get the base surface patch origin! + // + if( ValidDispFace( &g_pFaces[fn->neighbor[j]] ) ) + { + Vector patchOrigin; + WindingCenter (patch->winding, patchOrigin ); + AddBouncedToRadial( rad, patchOrigin, mins, maxs, patch->totallight.light, + needsBumpmap, needsBumpmap ); + } + else + { + AddBouncedToRadial( rad, patch->origin, mins, maxs, patch->totallight.light, + needsBumpmap, needsBumpmap ); + } + } + } + } + + return rad; +} + + +radial_t *BuildLuxelRadial( int facenum, int style ) +{ + LightingValue_t light[NUM_BUMP_VECTS + 1]; + + facelight_t *fl = &facelight[facenum]; + faceneighbor_t *fn = &faceneighbor[facenum]; + + radial_t *rad = AllocateRadial( facenum ); + + bool needsBumpmap = texinfo[g_pFaces[facenum].texinfo].flags & SURF_BUMPLIGHT ? true : false; + + for (int k=0 ; knumsamples ; k++) + { + if( needsBumpmap ) + { + for( int bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + light[bumpSample] = fl->light[style][bumpSample][k]; + } + } + else + { + light[0] = fl->light[style][0][k]; + } + + AddDirectToRadial( rad, fl->sample[k].pos, fl->sample[k].mins, fl->sample[k].maxs, light, needsBumpmap, needsBumpmap ); + } + + for (int j = 0; j < fn->numneighbors; j++) + { + fl = &facelight[fn->neighbor[j]]; + + bool neighborHasBumpmap = false; + + if( texinfo[g_pFaces[fn->neighbor[j]].texinfo].flags & SURF_BUMPLIGHT ) + { + neighborHasBumpmap = true; + } + + int nstyle = 0; + + // look for style that matches + if (g_pFaces[fn->neighbor[j]].styles[nstyle] != g_pFaces[facenum].styles[style]) + { + for (nstyle = 1; nstyle < MAXLIGHTMAPS; nstyle++ ) + if ( g_pFaces[fn->neighbor[j]].styles[nstyle] == g_pFaces[facenum].styles[style] ) + break; + + // if not found, skip this neighbor + if (nstyle >= MAXLIGHTMAPS) + continue; + } + + lightinfo_t l; + + InitLightinfo( &l, fn->neighbor[j] ); + + for (int k=0 ; knumsamples ; k++) + { + if( neighborHasBumpmap ) + { + for( int bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + light[bumpSample] = fl->light[nstyle][bumpSample][k]; + } + } + else + { + light[0]=fl->light[nstyle][0][k]; + } + + Vector tmp; + Vector2D mins, maxs; + + LuxelSpaceToWorld( &l, fl->sample[k].mins[0], fl->sample[k].mins[1], tmp ); + WorldToLuxelSpace( &rad->l, tmp, mins ); + LuxelSpaceToWorld( &l, fl->sample[k].maxs[0], fl->sample[k].maxs[1], tmp ); + WorldToLuxelSpace( &rad->l, tmp, maxs ); + + AddDirectToRadial( rad, fl->sample[k].pos, mins, maxs, light, + needsBumpmap, neighborHasBumpmap ); + } + } + + return rad; +} + + +//----------------------------------------------------------------------------- +// Purpose: returns the closest light value for a given point on the surface +// this is normally a 1:1 mapping +//----------------------------------------------------------------------------- +bool SampleRadial( radial_t *rad, Vector& pnt, LightingValue_t light[NUM_BUMP_VECTS + 1], int bumpSampleCount ) +{ + int bumpSample; + Vector2D coord; + + WorldToLuxelSpace( &rad->l, pnt, coord ); + int u = ( int )( coord[0] + 0.5f ); + int v = ( int )( coord[1] + 0.5f ); + int i = u + v * rad->w; + + if (u < 0 || u > rad->w || v < 0 || v > rad->h) + { + static bool warning = false; + if ( !warning ) + { + // punting over to KenB + // 2d coord indexes off of lightmap, generation of pnt seems suspect + Warning( "SampleRadial: Punting, Waiting for fix\n" ); + warning = true; + } + for( bumpSample = 0; bumpSample < bumpSampleCount; bumpSample++ ) + { + light[bumpSample].m_vecLighting.Init( 2550, 0, 0 ); + } + return false; + } + + bool baseSampleOk = true; + for( bumpSample = 0; bumpSample < bumpSampleCount; bumpSample++ ) + { + light[bumpSample].Zero(); + + if (rad->weight[i] > WEIGHT_EPS) + { + light[bumpSample]= rad->light[bumpSample][i]; + light[bumpSample].Scale( 1.0 / rad->weight[i] ); + } + else + { + if ( bRed2Black ) + { + // Error, luxel has no samples + light[bumpSample].m_vecLighting.Init( 0, 0, 0 ); + } + else + { + // Error, luxel has no samples + // Yes, it actually should be 2550 + light[bumpSample].m_vecLighting.Init( 2550, 0, 0 ); + } + + if (bumpSample == 0) + baseSampleOk = false; + } + } + + return baseSampleOk; +} + +bool FloatLess( float const& src1, float const& src2 ) +{ + return src1 < src2; +} + + +//----------------------------------------------------------------------------- +// Debugging! +//----------------------------------------------------------------------------- +void GetRandomColor( unsigned char *color ) +{ + static bool firstTime = true; + + if( firstTime ) + { + firstTime = false; + srand( 0 ); + } + + color[0] = ( unsigned char )( rand() * ( 255.0f / VALVE_RAND_MAX ) ); + color[1] = ( unsigned char )( rand() * ( 255.0f / VALVE_RAND_MAX ) ); + color[2] = ( unsigned char )( rand() * ( 255.0f / VALVE_RAND_MAX ) ); +} + + +#if 0 +// debugging! -- not accurate! +void DumpLuxels( facelight_t *pFaceLight, Vector *luxelColors, int ndxFace ) +{ + static FileHandle_t pFpLuxels = NULL; + + ThreadLock(); + + if( !pFpLuxels ) + { + pFpLuxels = g_pFileSystem->Open( "luxels.txt", "w" ); + } + + dface_t *pFace = &g_pFaces[ndxFace]; + bool bDisp = ( pFace->dispinfo != -1 ); + + for( int ndx = 0; ndx < pFaceLight->numluxels; ndx++ ) + { + WriteWinding( pFpLuxels, pFaceLight->sample[ndx].w, luxelColors[ndx] ); + if( bDumpNormals && bDisp ) + { + WriteNormal( pFpLuxels, pFaceLight->luxel[ndx], pFaceLight->luxelNormals[ndx], 15.0f, Vector( 255, 255, 0 ) ); + } + } + + ThreadUnlock(); +} +#endif + + +static FileHandle_t pFileLuxels[4] = { NULL, NULL, NULL, NULL }; + +void DumpDispLuxels( int iFace, Vector &color, int iLuxel, int nBump ) +{ + // Lock the thread and dump the luxel data. + ThreadLock(); + + // Get the face and facelight data. + facelight_t *pFaceLight = &facelight[iFace]; + + // Open the luxel files. + char szFileName[512]; + for ( int iBump = 0; iBump < ( NUM_BUMP_VECTS+1 ); ++iBump ) + { + if ( pFileLuxels[iBump] == NULL ) + { + sprintf( szFileName, "luxels_bump%d.txt", iBump ); + pFileLuxels[iBump] = g_pFileSystem->Open( szFileName, "w" ); + } + } + + WriteWinding( pFileLuxels[nBump], pFaceLight->sample[iLuxel].w, color ); + + ThreadUnlock(); +} + +void CloseDispLuxels() +{ + for ( int iBump = 0; iBump < ( NUM_BUMP_VECTS+1 ); ++iBump ) + { + if ( pFileLuxels[iBump] ) + { + g_pFileSystem->Close( pFileLuxels[iBump] ); + } + } +} + +/* +============= +FinalLightFace + +Add the indirect lighting on top of the direct +lighting and save into final map format +============= +*/ +void FinalLightFace( int iThread, int facenum ) +{ + dface_t *f; + int i, j, k; + facelight_t *fl; + float minlight; + int lightstyles; + LightingValue_t lb[NUM_BUMP_VECTS + 1], v[NUM_BUMP_VECTS + 1]; + unsigned char *pdata[NUM_BUMP_VECTS + 1]; + int bumpSample; + radial_t *rad = NULL; + radial_t *prad = NULL; + + f = &g_pFaces[facenum]; + + // test for non-lit texture + if ( texinfo[f->texinfo].flags & TEX_SPECIAL) + return; + + fl = &facelight[facenum]; + + + for (lightstyles=0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if ( f->styles[lightstyles] == 255 ) + break; + } + if ( !lightstyles ) + return; + + + // + // sample the triangulation + // + minlight = FloatForKey (face_entity[facenum], "_minlight") * 128; + + bool needsBumpmap = ( texinfo[f->texinfo].flags & SURF_BUMPLIGHT ) ? true : false; + int bumpSampleCount = needsBumpmap ? NUM_BUMP_VECTS + 1 : 1; + + bool bDisp = ( f->dispinfo != -1 ); + +//#define RANDOM_COLOR + +#ifdef RANDOM_COLOR + unsigned char randomColor[3]; + GetRandomColor( randomColor ); +#endif + + + // NOTE: I'm using these RB trees to sort all the illumination values + // to compute median colors. Turns out that this is a somewhat better + // method that using the average; usually if there are surfaces + // with a large light intensity variation, the extremely bright regions + // have a very small area and tend to influence the average too much. + CUtlRBTree< float, int > m_Red( 0, 256, FloatLess ); + CUtlRBTree< float, int > m_Green( 0, 256, FloatLess ); + CUtlRBTree< float, int > m_Blue( 0, 256, FloatLess ); + + for (k=0 ; k < lightstyles; k++ ) + { + m_Red.RemoveAll(); + m_Green.RemoveAll(); + m_Blue.RemoveAll(); + + if (!do_fast) + { + if( !bDisp ) + { + rad = BuildLuxelRadial( facenum, k ); + } + else + { + rad = StaticDispMgr()->BuildLuxelRadial( facenum, k, needsBumpmap ); + } + } + + if (numbounce > 0 && k == 0) + { + // currently only radiosity light non-displacement surfaces! + if( !bDisp ) + { + prad = BuildPatchRadial( facenum ); + } + else + { + prad = StaticDispMgr()->BuildPatchRadial( facenum, needsBumpmap ); + } + } + + // pack the nonbump texture and the three bump texture for the given + // lightstyle right next to each other. + // NOTE: Even though it's building positions for all bump-mapped data, + // it isn't going to use those positions (see loop over bumpSample below) + // The file offset is correctly computed to only store space for 1 set + // of light data if we don't have bumped lighting. + for( bumpSample = 0; bumpSample < bumpSampleCount; ++bumpSample ) + { + pdata[bumpSample] = &(*pdlightdata)[f->lightofs + (k * bumpSampleCount + bumpSample) * fl->numluxels*4]; + } + + // Compute the average luxel color, but not for the bump samples + Vector avg( 0.0f, 0.0f, 0.0f ); + int avgCount = 0; + + for (j=0 ; jnumluxels; j++) + { + // garymct - direct lighting + bool baseSampleOk = true; + + if (!do_fast) + { + if( !bDisp ) + { + baseSampleOk = SampleRadial( rad, fl->luxel[j], lb, bumpSampleCount ); + } + else + { + baseSampleOk = StaticDispMgr()->SampleRadial( facenum, rad, fl->luxel[j], j, lb, bumpSampleCount, false ); + } + } + else + { + for ( int iBump = 0 ; iBump < bumpSampleCount; iBump++ ) + { + lb[iBump] = fl->light[0][iBump][j]; + } + } + + if (prad) + { + // garymct - bounced light + // v is indirect light that is received on the luxel. + if( !bDisp ) + { + SampleRadial( prad, fl->luxel[j], v, bumpSampleCount ); + } + else + { + StaticDispMgr()->SampleRadial( facenum, prad, fl->luxel[j], j, v, bumpSampleCount, true ); + } + + for( bumpSample = 0; bumpSample < bumpSampleCount; ++bumpSample ) + { + lb[bumpSample].AddLight( v[bumpSample] ); + } + } + + if ( bDisp && g_bDumpPatches ) + { + for( bumpSample = 0; bumpSample < bumpSampleCount; ++bumpSample ) + { + DumpDispLuxels( facenum, lb[bumpSample].m_vecLighting, j, bumpSample ); + } + } + + if (fl->numsamples == 0) + { + for( i = 0; i < bumpSampleCount; i++ ) + { + lb[i].Init( 255, 0, 0 ); + } + baseSampleOk = false; + } + + int bumpSample; + for( bumpSample = 0; bumpSample < bumpSampleCount; bumpSample++ ) + { + // clip from the bottom first + // garymct: minlight is a per entity minimum light value? + for( i=0; i<3; i++ ) + { + lb[bumpSample].m_vecLighting[i] = max( lb[bumpSample].m_vecLighting[i], minlight ); + } + + // Do the average light computation, I'm assuming (perhaps incorrectly?) + // that all luxels in a particular lightmap have the same area here. + // Also, don't bother doing averages for the bump samples. Doing it here + // because of the minlight clamp above + the random color testy thingy. + // Also have to do it before Vec3toColorRGBExp32 because it + // destructively modifies lb[bumpSample] (Feh!) + if ((bumpSample == 0) && baseSampleOk) + { + ++avgCount; + + ApplyMacroTextures( facenum, fl->luxel[j], lb[0].m_vecLighting ); + + // For median computation + m_Red.Insert( lb[bumpSample].m_vecLighting[0] ); + m_Green.Insert( lb[bumpSample].m_vecLighting[1] ); + m_Blue.Insert( lb[bumpSample].m_vecLighting[2] ); + } + +#ifdef RANDOM_COLOR + pdata[bumpSample][0] = randomColor[0] / ( bumpSample + 1 ); + pdata[bumpSample][1] = randomColor[1] / ( bumpSample + 1 ); + pdata[bumpSample][2] = randomColor[2] / ( bumpSample + 1 ); + pdata[bumpSample][3] = 0; +#else + // convert to a 4 byte r,g,b,signed exponent format + VectorToColorRGBExp32( Vector( lb[bumpSample].m_vecLighting.x, lb[bumpSample].m_vecLighting.y, + lb[bumpSample].m_vecLighting.z ), *( ColorRGBExp32 *)pdata[bumpSample] ); +#endif + + pdata[bumpSample] += 4; + } + } + FreeRadial( rad ); + if (prad) + { + FreeRadial( prad ); + prad = NULL; + } + + // Compute the median color for this lightstyle + // Remember, the data goes *before* the specified light_ofs, in *reverse order* + ColorRGBExp32 *pAvgColor = dface_AvgLightColor( f, k ); + if (avgCount == 0) + { + Vector median( 0, 0, 0 ); + VectorToColorRGBExp32( median, *pAvgColor ); + } + else + { + unsigned int r, g, b; + r = m_Red.FirstInorder(); + g = m_Green.FirstInorder(); + b = m_Blue.FirstInorder(); + avgCount >>= 1; + while (avgCount > 0) + { + r = m_Red.NextInorder(r); + g = m_Green.NextInorder(g); + b = m_Blue.NextInorder(b); + --avgCount; + } + + Vector median( m_Red[r], m_Green[g], m_Blue[b] ); + VectorToColorRGBExp32( median, *pAvgColor ); + } + } +} diff --git a/mp/src/utils/vrad/radial.h b/mp/src/utils/vrad/radial.h index 862167cb..ebf82890 100644 --- a/mp/src/utils/vrad/radial.h +++ b/mp/src/utils/vrad/radial.h @@ -1,76 +1,76 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef RADIAL_H -#define RADIAL_H -#pragma once - -#include "mathlib/bumpvects.h" -#include "mathlib/ssemath.h" -#include "lightmap.h" - -#define RADIALDIST2 2 // (1.25*1.25+1.25*1.25) -#define RADIALDIST 1.42 // 1.77 // sqrt( RADIALDIST2 ) - -#define WEIGHT_EPS 0.00001f - -//----------------------------------------------------------------------------- -// The radial_t data structure is used to accumulate irregularly spaced and irregularly -// shaped direct and indirect lighting samples into a uniformly spaced and shaped luxel grid. -// -// The name "radial" is more historical than discriptive; it stems from the filtering method, -// one of several methods initially tried. Since all the other methods have since been deleted, -// it would probably be more accurate to rename it something like "LuxelAccumulationBucket" or -// something similar, but since "radial" is fairly meaningless it's not like it's actually confusing -// the issue. -//----------------------------------------------------------------------------- -typedef struct radial_s -{ - int facenum; - lightinfo_t l; - int w, h; - float weight[SINGLEMAP]; - LightingValue_t light[NUM_BUMP_VECTS + 1][SINGLEMAP]; -} radial_t; - - -void WorldToLuxelSpace( lightinfo_t const *l, Vector const &world, Vector2D &coord ); -void LuxelSpaceToWorld( lightinfo_t const *l, float s, float t, Vector &world ); - -void WorldToLuxelSpace( lightinfo_t const *l, FourVectors const &world, FourVectors &coord ); -void LuxelSpaceToWorld( lightinfo_t const *l, fltx4 s, fltx4 t, FourVectors &world ); - -void AddDirectToRadial( radial_t *rad, - Vector const &pnt, - Vector2D const &coordmins, Vector2D const &coordmaxs, - Vector const light[NUM_BUMP_VECTS+1], - bool hasBumpmap, bool neighborHasBumpmap ); - -void AddBounceToRadial( radial_t *rad, - Vector const &pnt, - Vector2D const &coordmins, Vector2D const &coordmaxs, - Vector const light[NUM_BUMP_VECTS+1], - bool hasBumpmap, bool neighborHasBumpmap ); - -bool SampleRadial( radial_t *rad, Vector& pnt, Vector light[NUM_BUMP_VECTS+1], int bumpSampleCount ); - -radial_t *AllocateRadial( int facenum ); -void FreeRadial( radial_t *rad ); - -bool SampleRadial( radial_t *rad, Vector& pnt, Vector light[NUM_BUMP_VECTS + 1], int bumpSampleCount ); -radial_t *BuildPatchRadial( int facenum ); - -// utilities -bool FloatLess( float const& src1, float const& src2 ); - -#endif +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RADIAL_H +#define RADIAL_H +#pragma once + +#include "mathlib/bumpvects.h" +#include "mathlib/ssemath.h" +#include "lightmap.h" + +#define RADIALDIST2 2 // (1.25*1.25+1.25*1.25) +#define RADIALDIST 1.42 // 1.77 // sqrt( RADIALDIST2 ) + +#define WEIGHT_EPS 0.00001f + +//----------------------------------------------------------------------------- +// The radial_t data structure is used to accumulate irregularly spaced and irregularly +// shaped direct and indirect lighting samples into a uniformly spaced and shaped luxel grid. +// +// The name "radial" is more historical than discriptive; it stems from the filtering method, +// one of several methods initially tried. Since all the other methods have since been deleted, +// it would probably be more accurate to rename it something like "LuxelAccumulationBucket" or +// something similar, but since "radial" is fairly meaningless it's not like it's actually confusing +// the issue. +//----------------------------------------------------------------------------- +typedef struct radial_s +{ + int facenum; + lightinfo_t l; + int w, h; + float weight[SINGLEMAP]; + LightingValue_t light[NUM_BUMP_VECTS + 1][SINGLEMAP]; +} radial_t; + + +void WorldToLuxelSpace( lightinfo_t const *l, Vector const &world, Vector2D &coord ); +void LuxelSpaceToWorld( lightinfo_t const *l, float s, float t, Vector &world ); + +void WorldToLuxelSpace( lightinfo_t const *l, FourVectors const &world, FourVectors &coord ); +void LuxelSpaceToWorld( lightinfo_t const *l, fltx4 s, fltx4 t, FourVectors &world ); + +void AddDirectToRadial( radial_t *rad, + Vector const &pnt, + Vector2D const &coordmins, Vector2D const &coordmaxs, + Vector const light[NUM_BUMP_VECTS+1], + bool hasBumpmap, bool neighborHasBumpmap ); + +void AddBounceToRadial( radial_t *rad, + Vector const &pnt, + Vector2D const &coordmins, Vector2D const &coordmaxs, + Vector const light[NUM_BUMP_VECTS+1], + bool hasBumpmap, bool neighborHasBumpmap ); + +bool SampleRadial( radial_t *rad, Vector& pnt, Vector light[NUM_BUMP_VECTS+1], int bumpSampleCount ); + +radial_t *AllocateRadial( int facenum ); +void FreeRadial( radial_t *rad ); + +bool SampleRadial( radial_t *rad, Vector& pnt, Vector light[NUM_BUMP_VECTS + 1], int bumpSampleCount ); +radial_t *BuildPatchRadial( int facenum ); + +// utilities +bool FloatLess( float const& src1, float const& src2 ); + +#endif diff --git a/mp/src/utils/vrad/samplehash.cpp b/mp/src/utils/vrad/samplehash.cpp index fb35d351..09bc94ec 100644 --- a/mp/src/utils/vrad/samplehash.cpp +++ b/mp/src/utils/vrad/samplehash.cpp @@ -1,230 +1,230 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "vrad.h" -#include "lightmap.h" - -#define SAMPLEHASH_NUM_BUCKETS 65536 -#define SAMPLEHASH_GROW_SIZE 0 -#define SAMPLEHASH_INIT_SIZE 0 - -int samplesAdded = 0; -int patchSamplesAdded = 0; -static unsigned short g_PatchIterationKey = 0; - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool SampleData_CompareFunc( SampleData_t const &src1, SampleData_t const &src2 ) -{ - return ( ( src1.x == src2.x ) && - ( src1.y == src2.y ) && - ( src1.z == src2.z ) ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -unsigned int SampleData_KeyFunc( SampleData_t const &src ) -{ - return ( src.x + src.y + src.z ); -} - - -CUtlHash g_SampleHashTable( SAMPLEHASH_NUM_BUCKETS, - SAMPLEHASH_GROW_SIZE, - SAMPLEHASH_INIT_SIZE, - SampleData_CompareFunc, SampleData_KeyFunc ); - - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -UtlHashHandle_t SampleData_Find( sample_t *pSample ) -{ - SampleData_t sampleData; - sampleData.x = ( int )( pSample->pos.x / SAMPLEHASH_VOXEL_SIZE ) * 100; - sampleData.y = ( int )( pSample->pos.y / SAMPLEHASH_VOXEL_SIZE ) * 10; - sampleData.z = ( int )( pSample->pos.z / SAMPLEHASH_VOXEL_SIZE ); - - return g_SampleHashTable.Find( sampleData ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -UtlHashHandle_t SampleData_InsertIntoHashTable( sample_t *pSample, SampleHandle_t sampleHandle ) -{ - SampleData_t sampleData; - sampleData.x = ( int )( pSample->pos.x / SAMPLEHASH_VOXEL_SIZE ) * 100; - sampleData.y = ( int )( pSample->pos.y / SAMPLEHASH_VOXEL_SIZE ) * 10; - sampleData.z = ( int )( pSample->pos.z / SAMPLEHASH_VOXEL_SIZE ); - - UtlHashHandle_t handle = g_SampleHashTable.AllocEntryFromKey( sampleData ); - - SampleData_t *pSampleData = &g_SampleHashTable.Element( handle ); - pSampleData->x = sampleData.x; - pSampleData->y = sampleData.y; - pSampleData->z = sampleData.z; - pSampleData->m_Samples.AddToTail( sampleHandle ); - - samplesAdded++; - - return handle; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -UtlHashHandle_t SampleData_AddSample( sample_t *pSample, SampleHandle_t sampleHandle ) -{ - - // find the key -- if it doesn't exist add new sample data to the - // hash table - UtlHashHandle_t handle = SampleData_Find( pSample ); - if( handle == g_SampleHashTable.InvalidHandle() ) - { - handle = SampleData_InsertIntoHashTable( pSample, sampleHandle ); - } - else - { - SampleData_t *pSampleData = &g_SampleHashTable.Element( handle ); - pSampleData->m_Samples.AddToTail( sampleHandle ); - - samplesAdded++; - } - - return handle; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void SampleData_Log( void ) -{ - if( g_bLogHashData ) - { - g_SampleHashTable.Log( "samplehash.txt" ); - } -} - - -//============================================================================= -//============================================================================= -// -// PatchSample Functions -// -//============================================================================= -//============================================================================= - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool PatchSampleData_CompareFunc( PatchSampleData_t const &src1, PatchSampleData_t const &src2 ) -{ - return ( ( src1.x == src2.x ) && - ( src1.y == src2.y ) && - ( src1.z == src2.z ) ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -unsigned int PatchSampleData_KeyFunc( PatchSampleData_t const &src ) -{ - return ( src.x + src.y + src.z ); -} - - -CUtlHash g_PatchSampleHashTable( SAMPLEHASH_NUM_BUCKETS, - SAMPLEHASH_GROW_SIZE, - SAMPLEHASH_INIT_SIZE, - PatchSampleData_CompareFunc, PatchSampleData_KeyFunc ); - -void GetPatchSampleHashXYZ( const Vector &vOrigin, int &x, int &y, int &z ) -{ - x = ( int )( vOrigin.x / SAMPLEHASH_VOXEL_SIZE ); - y = ( int )( vOrigin.y / SAMPLEHASH_VOXEL_SIZE ); - z = ( int )( vOrigin.z / SAMPLEHASH_VOXEL_SIZE ); -} - - -unsigned short IncrementPatchIterationKey() -{ - if ( g_PatchIterationKey == 0xFFFF ) - { - g_PatchIterationKey = 1; - for ( int i=0; i < g_Patches.Count(); i++ ) - g_Patches[i].m_IterationKey = 0; - } - else - { - g_PatchIterationKey++; - } - return g_PatchIterationKey; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void PatchSampleData_AddSample( CPatch *pPatch, int ndxPatch ) -{ - int patchSampleMins[3], patchSampleMaxs[3]; - -#if defined( SAMPLEHASH_USE_AREA_PATCHES ) - GetPatchSampleHashXYZ( pPatch->mins, patchSampleMins[0], patchSampleMins[1], patchSampleMins[2] ); - GetPatchSampleHashXYZ( pPatch->maxs, patchSampleMaxs[0], patchSampleMaxs[1], patchSampleMaxs[2] ); -#else - // If not using area patches, just use the patch's origin to add it to the voxels. - GetPatchSampleHashXYZ( pPatch->origin, patchSampleMins[0], patchSampleMins[1], patchSampleMins[2] ); - memcpy( patchSampleMaxs, patchSampleMins, sizeof( patchSampleMaxs ) ); -#endif - - // Make sure mins are smaller than maxs so we don't iterate for 4 bil. - Assert( patchSampleMins[0] <= patchSampleMaxs[0] && patchSampleMins[1] <= patchSampleMaxs[1] && patchSampleMins[2] <= patchSampleMaxs[2] ); - patchSampleMins[0] = min( patchSampleMins[0], patchSampleMaxs[0] ); - patchSampleMins[1] = min( patchSampleMins[1], patchSampleMaxs[1] ); - patchSampleMins[2] = min( patchSampleMins[2], patchSampleMaxs[2] ); - - int iterateCoords[3]; - for ( iterateCoords[0]=patchSampleMins[0]; iterateCoords[0] <= patchSampleMaxs[0]; iterateCoords[0]++ ) - { - for ( iterateCoords[1]=patchSampleMins[1]; iterateCoords[1] <= patchSampleMaxs[1]; iterateCoords[1]++ ) - { - for ( iterateCoords[2]=patchSampleMins[2]; iterateCoords[2] <= patchSampleMaxs[2]; iterateCoords[2]++ ) - { - // find the key -- if it doesn't exist add new sample data to the - // hash table - PatchSampleData_t iteratePatch; - iteratePatch.x = iterateCoords[0] * 100; - iteratePatch.y = iterateCoords[1] * 10; - iteratePatch.z = iterateCoords[2]; - - UtlHashHandle_t handle = g_PatchSampleHashTable.Find( iteratePatch ); - if( handle == g_PatchSampleHashTable.InvalidHandle() ) - { - UtlHashHandle_t handle = g_PatchSampleHashTable.AllocEntryFromKey( iteratePatch ); - - PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); - pPatchData->x = iteratePatch.x; - pPatchData->y = iteratePatch.y; - pPatchData->z = iteratePatch.z; - pPatchData->m_ndxPatches.AddToTail( ndxPatch ); - - patchSamplesAdded++; - } - else - { - PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); - pPatchData->m_ndxPatches.AddToTail( ndxPatch ); - - patchSamplesAdded++; - } - } - } - } -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vrad.h" +#include "lightmap.h" + +#define SAMPLEHASH_NUM_BUCKETS 65536 +#define SAMPLEHASH_GROW_SIZE 0 +#define SAMPLEHASH_INIT_SIZE 0 + +int samplesAdded = 0; +int patchSamplesAdded = 0; +static unsigned short g_PatchIterationKey = 0; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool SampleData_CompareFunc( SampleData_t const &src1, SampleData_t const &src2 ) +{ + return ( ( src1.x == src2.x ) && + ( src1.y == src2.y ) && + ( src1.z == src2.z ) ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +unsigned int SampleData_KeyFunc( SampleData_t const &src ) +{ + return ( src.x + src.y + src.z ); +} + + +CUtlHash g_SampleHashTable( SAMPLEHASH_NUM_BUCKETS, + SAMPLEHASH_GROW_SIZE, + SAMPLEHASH_INIT_SIZE, + SampleData_CompareFunc, SampleData_KeyFunc ); + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +UtlHashHandle_t SampleData_Find( sample_t *pSample ) +{ + SampleData_t sampleData; + sampleData.x = ( int )( pSample->pos.x / SAMPLEHASH_VOXEL_SIZE ) * 100; + sampleData.y = ( int )( pSample->pos.y / SAMPLEHASH_VOXEL_SIZE ) * 10; + sampleData.z = ( int )( pSample->pos.z / SAMPLEHASH_VOXEL_SIZE ); + + return g_SampleHashTable.Find( sampleData ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +UtlHashHandle_t SampleData_InsertIntoHashTable( sample_t *pSample, SampleHandle_t sampleHandle ) +{ + SampleData_t sampleData; + sampleData.x = ( int )( pSample->pos.x / SAMPLEHASH_VOXEL_SIZE ) * 100; + sampleData.y = ( int )( pSample->pos.y / SAMPLEHASH_VOXEL_SIZE ) * 10; + sampleData.z = ( int )( pSample->pos.z / SAMPLEHASH_VOXEL_SIZE ); + + UtlHashHandle_t handle = g_SampleHashTable.AllocEntryFromKey( sampleData ); + + SampleData_t *pSampleData = &g_SampleHashTable.Element( handle ); + pSampleData->x = sampleData.x; + pSampleData->y = sampleData.y; + pSampleData->z = sampleData.z; + pSampleData->m_Samples.AddToTail( sampleHandle ); + + samplesAdded++; + + return handle; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +UtlHashHandle_t SampleData_AddSample( sample_t *pSample, SampleHandle_t sampleHandle ) +{ + + // find the key -- if it doesn't exist add new sample data to the + // hash table + UtlHashHandle_t handle = SampleData_Find( pSample ); + if( handle == g_SampleHashTable.InvalidHandle() ) + { + handle = SampleData_InsertIntoHashTable( pSample, sampleHandle ); + } + else + { + SampleData_t *pSampleData = &g_SampleHashTable.Element( handle ); + pSampleData->m_Samples.AddToTail( sampleHandle ); + + samplesAdded++; + } + + return handle; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void SampleData_Log( void ) +{ + if( g_bLogHashData ) + { + g_SampleHashTable.Log( "samplehash.txt" ); + } +} + + +//============================================================================= +//============================================================================= +// +// PatchSample Functions +// +//============================================================================= +//============================================================================= + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool PatchSampleData_CompareFunc( PatchSampleData_t const &src1, PatchSampleData_t const &src2 ) +{ + return ( ( src1.x == src2.x ) && + ( src1.y == src2.y ) && + ( src1.z == src2.z ) ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +unsigned int PatchSampleData_KeyFunc( PatchSampleData_t const &src ) +{ + return ( src.x + src.y + src.z ); +} + + +CUtlHash g_PatchSampleHashTable( SAMPLEHASH_NUM_BUCKETS, + SAMPLEHASH_GROW_SIZE, + SAMPLEHASH_INIT_SIZE, + PatchSampleData_CompareFunc, PatchSampleData_KeyFunc ); + +void GetPatchSampleHashXYZ( const Vector &vOrigin, int &x, int &y, int &z ) +{ + x = ( int )( vOrigin.x / SAMPLEHASH_VOXEL_SIZE ); + y = ( int )( vOrigin.y / SAMPLEHASH_VOXEL_SIZE ); + z = ( int )( vOrigin.z / SAMPLEHASH_VOXEL_SIZE ); +} + + +unsigned short IncrementPatchIterationKey() +{ + if ( g_PatchIterationKey == 0xFFFF ) + { + g_PatchIterationKey = 1; + for ( int i=0; i < g_Patches.Count(); i++ ) + g_Patches[i].m_IterationKey = 0; + } + else + { + g_PatchIterationKey++; + } + return g_PatchIterationKey; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void PatchSampleData_AddSample( CPatch *pPatch, int ndxPatch ) +{ + int patchSampleMins[3], patchSampleMaxs[3]; + +#if defined( SAMPLEHASH_USE_AREA_PATCHES ) + GetPatchSampleHashXYZ( pPatch->mins, patchSampleMins[0], patchSampleMins[1], patchSampleMins[2] ); + GetPatchSampleHashXYZ( pPatch->maxs, patchSampleMaxs[0], patchSampleMaxs[1], patchSampleMaxs[2] ); +#else + // If not using area patches, just use the patch's origin to add it to the voxels. + GetPatchSampleHashXYZ( pPatch->origin, patchSampleMins[0], patchSampleMins[1], patchSampleMins[2] ); + memcpy( patchSampleMaxs, patchSampleMins, sizeof( patchSampleMaxs ) ); +#endif + + // Make sure mins are smaller than maxs so we don't iterate for 4 bil. + Assert( patchSampleMins[0] <= patchSampleMaxs[0] && patchSampleMins[1] <= patchSampleMaxs[1] && patchSampleMins[2] <= patchSampleMaxs[2] ); + patchSampleMins[0] = min( patchSampleMins[0], patchSampleMaxs[0] ); + patchSampleMins[1] = min( patchSampleMins[1], patchSampleMaxs[1] ); + patchSampleMins[2] = min( patchSampleMins[2], patchSampleMaxs[2] ); + + int iterateCoords[3]; + for ( iterateCoords[0]=patchSampleMins[0]; iterateCoords[0] <= patchSampleMaxs[0]; iterateCoords[0]++ ) + { + for ( iterateCoords[1]=patchSampleMins[1]; iterateCoords[1] <= patchSampleMaxs[1]; iterateCoords[1]++ ) + { + for ( iterateCoords[2]=patchSampleMins[2]; iterateCoords[2] <= patchSampleMaxs[2]; iterateCoords[2]++ ) + { + // find the key -- if it doesn't exist add new sample data to the + // hash table + PatchSampleData_t iteratePatch; + iteratePatch.x = iterateCoords[0] * 100; + iteratePatch.y = iterateCoords[1] * 10; + iteratePatch.z = iterateCoords[2]; + + UtlHashHandle_t handle = g_PatchSampleHashTable.Find( iteratePatch ); + if( handle == g_PatchSampleHashTable.InvalidHandle() ) + { + UtlHashHandle_t handle = g_PatchSampleHashTable.AllocEntryFromKey( iteratePatch ); + + PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); + pPatchData->x = iteratePatch.x; + pPatchData->y = iteratePatch.y; + pPatchData->z = iteratePatch.z; + pPatchData->m_ndxPatches.AddToTail( ndxPatch ); + + patchSamplesAdded++; + } + else + { + PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); + pPatchData->m_ndxPatches.AddToTail( ndxPatch ); + + patchSamplesAdded++; + } + } + } + } +} + diff --git a/mp/src/utils/vrad/trace.cpp b/mp/src/utils/vrad/trace.cpp index 8069dbe7..e0926e29 100644 --- a/mp/src/utils/vrad/trace.cpp +++ b/mp/src/utils/vrad/trace.cpp @@ -1,653 +1,653 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//===========================================================================// -// trace.c - -//============================================================================= - -#include "vrad.h" -#include "trace.h" -#include "Cmodel.h" -#include "mathlib/vmatrix.h" - - -//============================================================================= - -class CToolTrace : public CBaseTrace -{ -public: - CToolTrace() {} - - Vector mins; - Vector maxs; - Vector extents; - - texinfo_t *surface; - - qboolean ispoint; - -private: - CToolTrace( const CToolTrace& ); -}; - - -// 1/32 epsilon to keep floating point happy -#define DIST_EPSILON (0.03125) - -// JAYHL2: This used to be -1, but that caused lots of epsilon issues -// around slow sloping planes. Perhaps Quake2 limited maps to a certain -// slope / angle on walkable ground. It has to be a negative number -// so that the tests work out. -#define NEVER_UPDATED -9999 - -//============================================================================= - -bool DM_RayDispIntersectTest( CVRADDispColl *pTree, Vector& rayStart, Vector& rayEnd, CToolTrace *pTrace ); -void DM_ClipBoxToBrush( CToolTrace *trace, const Vector & mins, const Vector & maxs, const Vector& p1, const Vector& p2, dbrush_t *brush ); - -//============================================================================= - -float TraceLeafBrushes( int leafIndex, const Vector &start, const Vector &end, CBaseTrace &traceOut ) -{ - dleaf_t *pLeaf = dleafs + leafIndex; - CToolTrace trace; - memset( &trace, 0, sizeof(trace) ); - trace.ispoint = true; - trace.startsolid = false; - trace.fraction = 1.0; - - for ( int i = 0; i < pLeaf->numleafbrushes; i++ ) - { - int brushnum = dleafbrushes[pLeaf->firstleafbrush+i]; - dbrush_t *b = &dbrushes[brushnum]; - if ( !(b->contents & MASK_OPAQUE)) - continue; - - Vector zeroExtents = vec3_origin; - DM_ClipBoxToBrush( &trace, zeroExtents, zeroExtents, start, end, b); - if ( trace.fraction != 1.0 || trace.startsolid ) - { - if ( trace.startsolid ) - trace.fraction = 0.0f; - traceOut = trace; - return trace.fraction; - } - } - traceOut = trace; - return 1.0f; -} - -DispTested_t s_DispTested[MAX_TOOL_THREADS+1]; - -// this just uses the average coverage for the triangle -class CCoverageCount : public ITransparentTriangleCallback -{ -public: - CCoverageCount() - { - m_coverage = Four_Zeros; - } - - virtual bool VisitTriangle_ShouldContinue( const TriIntersectData_t &triangle, const FourRays &rays, fltx4 *pHitMask, fltx4 *b0, fltx4 *b1, fltx4 *b2, int32 hitID ) - { - float color = g_RtEnv.GetTriangleColor( hitID ).x; - m_coverage = AddSIMD( m_coverage, AndSIMD ( *pHitMask, ReplicateX4 ( color ) ) ); - m_coverage = MinSIMD( m_coverage, Four_Ones ); - - fltx4 onesMask = CmpEqSIMD( m_coverage, Four_Ones ); - - // we should continue if the ones that hit the triangle have onesMask set to zero - // so hitMask & onesMask != hitMask - // so hitMask & onesMask == hitMask means we're done - // so ts(hitMask & onesMask == hitMask) != 0xF says go on - return 0xF != TestSignSIMD ( CmpEqSIMD ( AndSIMD( *pHitMask, onesMask ), *pHitMask ) ); - } - - fltx4 GetCoverage() - { - return m_coverage; - } - - fltx4 GetFractionVisible() - { - return SubSIMD ( Four_Ones, m_coverage ); - } - - fltx4 m_coverage; -}; - -// this will sample the texture to get a coverage at the ray intersection point -class CCoverageCountTexture : public CCoverageCount -{ -public: - virtual bool VisitTriangle_ShouldContinue( const TriIntersectData_t &triangle, const FourRays &rays, fltx4 *pHitMask, fltx4 *b0, fltx4 *b1, fltx4 *b2, int32 hitID ) - { - int sign = TestSignSIMD( *pHitMask ); - float addedCoverage[4]; - for ( int s = 0; s < 4; s++) - { - addedCoverage[s] = 0.0f; - if ( ( sign >> s) & 0x1 ) - { - addedCoverage[s] = ComputeCoverageFromTexture( b0->m128_f32[s], b1->m128_f32[s], b2->m128_f32[s], hitID ); - } - } - m_coverage = AddSIMD( m_coverage, LoadUnalignedSIMD( addedCoverage ) ); - m_coverage = MinSIMD( m_coverage, Four_Ones ); - fltx4 onesMask = CmpEqSIMD( m_coverage, Four_Ones ); - - // we should continue if the ones that hit the triangle have onesMask set to zero - // so hitMask & onesMask != hitMask - // so hitMask & onesMask == hitMask means we're done - // so ts(hitMask & onesMask == hitMask) != 0xF says go on - return 0xF != TestSignSIMD ( CmpEqSIMD ( AndSIMD( *pHitMask, onesMask ), *pHitMask ) ); - } -}; - -void TestLine( const FourVectors& start, const FourVectors& stop, - fltx4 *pFractionVisible, int static_prop_index_to_ignore ) -{ - FourRays myrays; - myrays.origin = start; - myrays.direction = stop; - myrays.direction -= myrays.origin; - fltx4 len = myrays.direction.length(); - myrays.direction *= ReciprocalSIMD( len ); - - RayTracingResult rt_result; - CCoverageCountTexture coverageCallback; - - g_RtEnv.Trace4Rays(myrays, Four_Zeros, len, &rt_result, TRACE_ID_STATICPROP | static_prop_index_to_ignore, g_bTextureShadows ? &coverageCallback : 0 ); - - // Assume we can see the targets unless we get hits - float visibility[4]; - for ( int i = 0; i < 4; i++ ) - { - visibility[i] = 1.0f; - if ( ( rt_result.HitIds[i] != -1 ) && - ( rt_result.HitDistance.m128_f32[i] < len.m128_f32[i] ) ) - { - visibility[i] = 0.0f; - } - } - *pFractionVisible = LoadUnalignedSIMD( visibility ); - if ( g_bTextureShadows ) - *pFractionVisible = MinSIMD( *pFractionVisible, coverageCallback.GetFractionVisible() ); -} - - - -/* -================ -DM_ClipBoxToBrush -================ -*/ -void DM_ClipBoxToBrush( CToolTrace *trace, const Vector& mins, const Vector& maxs, const Vector& p1, const Vector& p2, - dbrush_t *brush) -{ - dplane_t *plane, *clipplane; - float dist; - Vector ofs; - float d1, d2; - float f; - dbrushside_t *side, *leadside; - - if (!brush->numsides) - return; - - float enterfrac = NEVER_UPDATED; - float leavefrac = 1.f; - clipplane = NULL; - - bool getout = false; - bool startout = false; - leadside = NULL; - - // Loop interchanged, so we don't have to check trace->ispoint every side. - if ( !trace->ispoint ) - { - for (int i=0 ; inumsides ; ++i) - { - side = &dbrushsides[brush->firstside+i]; - plane = dplanes + side->planenum; - - // FIXME: special case for axial - - // general box case - // push the plane out apropriately for mins/maxs - - // FIXME: use signbits into 8 way lookup for each mins/maxs - ofs.x = (plane->normal.x < 0) ? maxs.x : mins.x; - ofs.y = (plane->normal.y < 0) ? maxs.y : mins.y; - ofs.z = (plane->normal.z < 0) ? maxs.z : mins.z; -// for (j=0 ; j<3 ; j++) -// { - // Set signmask to either 0 if the sign is negative, or 0xFFFFFFFF is the sign is positive: - //int signmask = (((*(int *)&(plane->normal[j]))&0x80000000) >> 31) - 1; - - //float temp = maxs[j]; - //*(int *)&(ofs[j]) = (~signmask) & (*(int *)&temp); - //float temp1 = mins[j]; - //*(int *)&(ofs[j]) |= (signmask) & (*(int *)&temp1); -// } - dist = DotProduct (ofs, plane->normal); - dist = plane->dist - dist; - - d1 = DotProduct (p1, plane->normal) - dist; - d2 = DotProduct (p2, plane->normal) - dist; - - // if completely in front of face, no intersection - if (d1 > 0 && d2 > 0) - return; - - if (d2 > 0) - getout = true; // endpoint is not in solid - if (d1 > 0) - startout = true; - - if (d1 <= 0 && d2 <= 0) - continue; - - // crosses face - if (d1 > d2) - { // enter - f = (d1-DIST_EPSILON) / (d1-d2); - if (f > enterfrac) - { - enterfrac = f; - clipplane = plane; - leadside = side; - } - } - else - { // leave - f = (d1+DIST_EPSILON) / (d1-d2); - if (f < leavefrac) - leavefrac = f; - } - } - } - else - { - for (int i=0 ; inumsides ; ++i) - { - side = &dbrushsides[brush->firstside+i]; - plane = dplanes + side->planenum; - - // FIXME: special case for axial - - // special point case - // don't ray trace against bevel planes - if( side->bevel == 1 ) - continue; - - dist = plane->dist; - d1 = DotProduct (p1, plane->normal) - dist; - d2 = DotProduct (p2, plane->normal) - dist; - - // if completely in front of face, no intersection - if (d1 > 0 && d2 > 0) - return; - - if (d2 > 0) - getout = true; // endpoint is not in solid - if (d1 > 0) - startout = true; - - if (d1 <= 0 && d2 <= 0) - continue; - - // crosses face - if (d1 > d2) - { // enter - f = (d1-DIST_EPSILON) / (d1-d2); - if (f > enterfrac) - { - enterfrac = f; - clipplane = plane; - leadside = side; - } - } - else - { // leave - f = (d1+DIST_EPSILON) / (d1-d2); - if (f < leavefrac) - leavefrac = f; - } - } - } - - - - if (!startout) - { // original point was inside brush - trace->startsolid = true; - if (!getout) - trace->allsolid = true; - return; - } - if (enterfrac < leavefrac) - { - if (enterfrac > NEVER_UPDATED && enterfrac < trace->fraction) - { - if (enterfrac < 0) - enterfrac = 0; - trace->fraction = enterfrac; - trace->plane.dist = clipplane->dist; - trace->plane.normal = clipplane->normal; - trace->plane.type = clipplane->type; - if (leadside->texinfo!=-1) - trace->surface = &texinfo[leadside->texinfo]; - else - trace->surface = 0; - trace->contents = brush->contents; - } - } -} - -void TestLine_DoesHitSky( FourVectors const& start, FourVectors const& stop, - fltx4 *pFractionVisible, bool canRecurse, int static_prop_to_skip, bool bDoDebug ) -{ - FourRays myrays; - myrays.origin = start; - myrays.direction = stop; - myrays.direction -= myrays.origin; - fltx4 len = myrays.direction.length(); - myrays.direction *= ReciprocalSIMD( len ); - RayTracingResult rt_result; - CCoverageCountTexture coverageCallback; - - g_RtEnv.Trace4Rays(myrays, Four_Zeros, len, &rt_result, TRACE_ID_STATICPROP | static_prop_to_skip, g_bTextureShadows? &coverageCallback : 0); - - if ( bDoDebug ) - { - WriteTrace( "trace.txt", myrays, rt_result ); - } - - float aOcclusion[4]; - for ( int i = 0; i < 4; i++ ) - { - aOcclusion[i] = 0.0f; - if ( ( rt_result.HitIds[i] != -1 ) && - ( rt_result.HitDistance.m128_f32[i] < len.m128_f32[i] ) ) - { - int id = g_RtEnv.OptimizedTriangleList[rt_result.HitIds[i]].m_Data.m_IntersectData.m_nTriangleID; - if ( !( id & TRACE_ID_SKY ) ) - aOcclusion[i] = 1.0f; - } - } - fltx4 occlusion = LoadUnalignedSIMD( aOcclusion ); - if (g_bTextureShadows) - occlusion = MaxSIMD ( occlusion, coverageCallback.GetCoverage() ); - - bool fullyOccluded = ( TestSignSIMD( CmpGeSIMD( occlusion, Four_Ones ) ) == 0xF ); - - // if we hit sky, and we're not in a sky camera's area, try clipping into the 3D sky boxes - if ( (! fullyOccluded) && canRecurse && (! g_bNoSkyRecurse ) ) - { - FourVectors dir = stop; - dir -= start; - dir.VectorNormalize(); - - int leafIndex = -1; - leafIndex = PointLeafnum( start.Vec( 0 ) ); - if ( leafIndex >= 0 ) - { - int area = dleafs[leafIndex].area; - if (area >= 0 && area < numareas) - { - if (area_sky_cameras[area] < 0) - { - int cam; - for (cam = 0; cam < num_sky_cameras; ++cam) - { - FourVectors skystart, skytrans, skystop; - skystart.DuplicateVector( sky_cameras[cam].origin ); - skystop = start; - skystop *= sky_cameras[cam].world_to_sky; - skystart += skystop; - - skystop = dir; - skystop *= MAX_TRACE_LENGTH; - skystop += skystart; - TestLine_DoesHitSky ( skystart, skystop, pFractionVisible, false, static_prop_to_skip, bDoDebug ); - occlusion = AddSIMD ( occlusion, Four_Ones ); - occlusion = SubSIMD ( occlusion, *pFractionVisible ); - } - } - } - } - } - - occlusion = MaxSIMD( occlusion, Four_Zeros ); - occlusion = MinSIMD( occlusion, Four_Ones ); - *pFractionVisible = SubSIMD( Four_Ones, occlusion ); -} - - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int PointLeafnum_r( const Vector &point, int ndxNode ) -{ - // while loop here is to avoid recursion overhead - while( ndxNode >= 0 ) - { - dnode_t *pNode = dnodes + ndxNode; - dplane_t *pPlane = dplanes + pNode->planenum; - - float dist; - if( pPlane->type < 3 ) - { - dist = point[pPlane->type] - pPlane->dist; - } - else - { - dist = DotProduct( pPlane->normal, point ) - pPlane->dist; - } - - if( dist < 0.0f ) - { - ndxNode = pNode->children[1]; - } - else - { - ndxNode = pNode->children[0]; - } - } - - return ( -1 - ndxNode ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -int PointLeafnum( const Vector &point ) -{ - return PointLeafnum_r( point, 0 ); -} - -// this iterates the list of entities looking for _vradshadows 1 -// each brush entity containing this key is added to the raytracing environment -// as a triangle soup model. - -dmodel_t *BrushmodelForEntity( entity_t *pEntity ) -{ - const char *pModelname = ValueForKey( pEntity, "model" ); - if ( Q_strlen(pModelname) > 1 ) - { - int modelIndex = atol( pModelname + 1 ); - if ( modelIndex > 0 && modelIndex < nummodels ) - { - return &dmodels[modelIndex]; - } - } - return NULL; -} - -void AddBrushToRaytraceEnvironment( dbrush_t *pBrush, const VMatrix &xform ) -{ - if ( !( pBrush->contents & MASK_OPAQUE ) ) - return; - - Vector v0, v1, v2; - for (int i = 0; i < pBrush->numsides; i++ ) - { - dbrushside_t *side = &dbrushsides[pBrush->firstside + i]; - dplane_t *plane = &dplanes[side->planenum]; - texinfo_t *tx = &texinfo[side->texinfo]; - winding_t *w = BaseWindingForPlane (plane->normal, plane->dist); - - if ( tx->flags & SURF_SKY || side->dispinfo ) - continue; - - for (int j=0 ; jnumsides && w; j++) - { - if (i == j) - continue; - dbrushside_t *pOtherSide = &dbrushsides[pBrush->firstside + j]; - if (pOtherSide->bevel) - continue; - plane = &dplanes[pOtherSide->planenum^1]; - ChopWindingInPlace (&w, plane->normal, plane->dist, 0); - } - if ( w ) - { - for ( int j = 2; j < w->numpoints; j++ ) - { - v0 = xform.VMul4x3(w->p[0]); - v1 = xform.VMul4x3(w->p[j-1]); - v2 = xform.VMul4x3(w->p[j]); - Vector fullCoverage; - fullCoverage.x = 1.0f; - g_RtEnv.AddTriangle(TRACE_ID_OPAQUE, v0, v1, v2, fullCoverage); - } - FreeWinding( w ); - } - } -} - - -// recurse the bsp and build a list of brushes at the leaves under this node -void GetBrushes_r( int node, CUtlVector &list ) -{ - if ( node < 0 ) - { - int leafIndex = -1 - node; - // Add the solids in the leaf - for ( int i = 0; i < dleafs[leafIndex].numleafbrushes; i++ ) - { - int brushIndex = dleafbrushes[dleafs[leafIndex].firstleafbrush + i]; - if ( list.Find(brushIndex) < 0 ) - { - list.AddToTail( brushIndex ); - } - } - } - else - { - // recurse - dnode_t *pnode = dnodes + node; - - GetBrushes_r( pnode->children[0], list ); - GetBrushes_r( pnode->children[1], list ); - } -} - - -void AddBrushes( dmodel_t *pModel, const VMatrix &xform ) -{ - if ( pModel ) - { - CUtlVector brushList; - GetBrushes_r( pModel->headnode, brushList ); - for ( int i = 0; i < brushList.Count(); i++ ) - { - int ndxBrush = brushList[i]; - AddBrushToRaytraceEnvironment( &dbrushes[ndxBrush], xform ); - } - } -} - - -// Adds the brush entities that cast shadows to the raytrace environment -void ExtractBrushEntityShadowCasters() -{ - for ( int i = 0; i < num_entities; i++ ) - { - if ( IntForKey( &entities[i], "vrad_brush_cast_shadows" ) != 0 ) - { - Vector origin; - QAngle angles; - GetVectorForKey( &entities[i], "origin", origin ); - GetAnglesForKey( &entities[i], "angles", angles ); - VMatrix xform; - xform.SetupMatrixOrgAngles( origin, angles ); - AddBrushes( BrushmodelForEntity( &entities[i] ), xform ); - } - } -} - -void AddBrushesForRayTrace( void ) -{ - if ( !nummodels ) - return; - - VMatrix identity; - identity.Identity(); - - CUtlVector brushList; - GetBrushes_r ( dmodels[0].headnode, brushList ); - - for ( int i = 0; i < brushList.Size(); i++ ) - { - dbrush_t *brush = &dbrushes[brushList[i]]; - AddBrushToRaytraceEnvironment ( brush, identity ); - } - - for ( int i = 0; i < dmodels[0].numfaces; i++ ) - { - int ndxFace = dmodels[0].firstface + i; - dface_t *face = &g_pFaces[ndxFace]; - - texinfo_t *tx = &texinfo[face->texinfo]; - if ( !( tx->flags & SURF_SKY ) ) - continue; - - Vector points[MAX_POINTS_ON_WINDING]; - - for ( int j = 0; j < face->numedges; j++ ) - { - if ( j >= MAX_POINTS_ON_WINDING ) - Error( "***** ERROR! MAX_POINTS_ON_WINDING reached!" ); - - if ( face->firstedge + j >= ARRAYSIZE( dsurfedges ) ) - Error( "***** ERROR! face->firstedge + j >= ARRAYSIZE( dsurfedges )!" ); - - int surfEdge = dsurfedges[face->firstedge + j]; - unsigned short v; - - if (surfEdge < 0) - v = dedges[-surfEdge].v[1]; - else - v = dedges[surfEdge].v[0]; - - if ( v >= ARRAYSIZE( dvertexes ) ) - Error( "***** ERROR! v(%u) >= ARRAYSIZE( dvertexes(%d) )!", ( unsigned int )v, ARRAYSIZE( dvertexes ) ); - - dvertex_t *dv = &dvertexes[v]; - points[j] = dv->point; - } - - for ( int j = 2; j < face->numedges; j++ ) - { - Vector fullCoverage; - fullCoverage.x = 1.0f; - g_RtEnv.AddTriangle ( TRACE_ID_SKY, points[0], points[j - 1], points[j], fullCoverage ); - } - } -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +// trace.c + +//============================================================================= + +#include "vrad.h" +#include "trace.h" +#include "Cmodel.h" +#include "mathlib/vmatrix.h" + + +//============================================================================= + +class CToolTrace : public CBaseTrace +{ +public: + CToolTrace() {} + + Vector mins; + Vector maxs; + Vector extents; + + texinfo_t *surface; + + qboolean ispoint; + +private: + CToolTrace( const CToolTrace& ); +}; + + +// 1/32 epsilon to keep floating point happy +#define DIST_EPSILON (0.03125) + +// JAYHL2: This used to be -1, but that caused lots of epsilon issues +// around slow sloping planes. Perhaps Quake2 limited maps to a certain +// slope / angle on walkable ground. It has to be a negative number +// so that the tests work out. +#define NEVER_UPDATED -9999 + +//============================================================================= + +bool DM_RayDispIntersectTest( CVRADDispColl *pTree, Vector& rayStart, Vector& rayEnd, CToolTrace *pTrace ); +void DM_ClipBoxToBrush( CToolTrace *trace, const Vector & mins, const Vector & maxs, const Vector& p1, const Vector& p2, dbrush_t *brush ); + +//============================================================================= + +float TraceLeafBrushes( int leafIndex, const Vector &start, const Vector &end, CBaseTrace &traceOut ) +{ + dleaf_t *pLeaf = dleafs + leafIndex; + CToolTrace trace; + memset( &trace, 0, sizeof(trace) ); + trace.ispoint = true; + trace.startsolid = false; + trace.fraction = 1.0; + + for ( int i = 0; i < pLeaf->numleafbrushes; i++ ) + { + int brushnum = dleafbrushes[pLeaf->firstleafbrush+i]; + dbrush_t *b = &dbrushes[brushnum]; + if ( !(b->contents & MASK_OPAQUE)) + continue; + + Vector zeroExtents = vec3_origin; + DM_ClipBoxToBrush( &trace, zeroExtents, zeroExtents, start, end, b); + if ( trace.fraction != 1.0 || trace.startsolid ) + { + if ( trace.startsolid ) + trace.fraction = 0.0f; + traceOut = trace; + return trace.fraction; + } + } + traceOut = trace; + return 1.0f; +} + +DispTested_t s_DispTested[MAX_TOOL_THREADS+1]; + +// this just uses the average coverage for the triangle +class CCoverageCount : public ITransparentTriangleCallback +{ +public: + CCoverageCount() + { + m_coverage = Four_Zeros; + } + + virtual bool VisitTriangle_ShouldContinue( const TriIntersectData_t &triangle, const FourRays &rays, fltx4 *pHitMask, fltx4 *b0, fltx4 *b1, fltx4 *b2, int32 hitID ) + { + float color = g_RtEnv.GetTriangleColor( hitID ).x; + m_coverage = AddSIMD( m_coverage, AndSIMD ( *pHitMask, ReplicateX4 ( color ) ) ); + m_coverage = MinSIMD( m_coverage, Four_Ones ); + + fltx4 onesMask = CmpEqSIMD( m_coverage, Four_Ones ); + + // we should continue if the ones that hit the triangle have onesMask set to zero + // so hitMask & onesMask != hitMask + // so hitMask & onesMask == hitMask means we're done + // so ts(hitMask & onesMask == hitMask) != 0xF says go on + return 0xF != TestSignSIMD ( CmpEqSIMD ( AndSIMD( *pHitMask, onesMask ), *pHitMask ) ); + } + + fltx4 GetCoverage() + { + return m_coverage; + } + + fltx4 GetFractionVisible() + { + return SubSIMD ( Four_Ones, m_coverage ); + } + + fltx4 m_coverage; +}; + +// this will sample the texture to get a coverage at the ray intersection point +class CCoverageCountTexture : public CCoverageCount +{ +public: + virtual bool VisitTriangle_ShouldContinue( const TriIntersectData_t &triangle, const FourRays &rays, fltx4 *pHitMask, fltx4 *b0, fltx4 *b1, fltx4 *b2, int32 hitID ) + { + int sign = TestSignSIMD( *pHitMask ); + float addedCoverage[4]; + for ( int s = 0; s < 4; s++) + { + addedCoverage[s] = 0.0f; + if ( ( sign >> s) & 0x1 ) + { + addedCoverage[s] = ComputeCoverageFromTexture( b0->m128_f32[s], b1->m128_f32[s], b2->m128_f32[s], hitID ); + } + } + m_coverage = AddSIMD( m_coverage, LoadUnalignedSIMD( addedCoverage ) ); + m_coverage = MinSIMD( m_coverage, Four_Ones ); + fltx4 onesMask = CmpEqSIMD( m_coverage, Four_Ones ); + + // we should continue if the ones that hit the triangle have onesMask set to zero + // so hitMask & onesMask != hitMask + // so hitMask & onesMask == hitMask means we're done + // so ts(hitMask & onesMask == hitMask) != 0xF says go on + return 0xF != TestSignSIMD ( CmpEqSIMD ( AndSIMD( *pHitMask, onesMask ), *pHitMask ) ); + } +}; + +void TestLine( const FourVectors& start, const FourVectors& stop, + fltx4 *pFractionVisible, int static_prop_index_to_ignore ) +{ + FourRays myrays; + myrays.origin = start; + myrays.direction = stop; + myrays.direction -= myrays.origin; + fltx4 len = myrays.direction.length(); + myrays.direction *= ReciprocalSIMD( len ); + + RayTracingResult rt_result; + CCoverageCountTexture coverageCallback; + + g_RtEnv.Trace4Rays(myrays, Four_Zeros, len, &rt_result, TRACE_ID_STATICPROP | static_prop_index_to_ignore, g_bTextureShadows ? &coverageCallback : 0 ); + + // Assume we can see the targets unless we get hits + float visibility[4]; + for ( int i = 0; i < 4; i++ ) + { + visibility[i] = 1.0f; + if ( ( rt_result.HitIds[i] != -1 ) && + ( rt_result.HitDistance.m128_f32[i] < len.m128_f32[i] ) ) + { + visibility[i] = 0.0f; + } + } + *pFractionVisible = LoadUnalignedSIMD( visibility ); + if ( g_bTextureShadows ) + *pFractionVisible = MinSIMD( *pFractionVisible, coverageCallback.GetFractionVisible() ); +} + + + +/* +================ +DM_ClipBoxToBrush +================ +*/ +void DM_ClipBoxToBrush( CToolTrace *trace, const Vector& mins, const Vector& maxs, const Vector& p1, const Vector& p2, + dbrush_t *brush) +{ + dplane_t *plane, *clipplane; + float dist; + Vector ofs; + float d1, d2; + float f; + dbrushside_t *side, *leadside; + + if (!brush->numsides) + return; + + float enterfrac = NEVER_UPDATED; + float leavefrac = 1.f; + clipplane = NULL; + + bool getout = false; + bool startout = false; + leadside = NULL; + + // Loop interchanged, so we don't have to check trace->ispoint every side. + if ( !trace->ispoint ) + { + for (int i=0 ; inumsides ; ++i) + { + side = &dbrushsides[brush->firstside+i]; + plane = dplanes + side->planenum; + + // FIXME: special case for axial + + // general box case + // push the plane out apropriately for mins/maxs + + // FIXME: use signbits into 8 way lookup for each mins/maxs + ofs.x = (plane->normal.x < 0) ? maxs.x : mins.x; + ofs.y = (plane->normal.y < 0) ? maxs.y : mins.y; + ofs.z = (plane->normal.z < 0) ? maxs.z : mins.z; +// for (j=0 ; j<3 ; j++) +// { + // Set signmask to either 0 if the sign is negative, or 0xFFFFFFFF is the sign is positive: + //int signmask = (((*(int *)&(plane->normal[j]))&0x80000000) >> 31) - 1; + + //float temp = maxs[j]; + //*(int *)&(ofs[j]) = (~signmask) & (*(int *)&temp); + //float temp1 = mins[j]; + //*(int *)&(ofs[j]) |= (signmask) & (*(int *)&temp1); +// } + dist = DotProduct (ofs, plane->normal); + dist = plane->dist - dist; + + d1 = DotProduct (p1, plane->normal) - dist; + d2 = DotProduct (p2, plane->normal) - dist; + + // if completely in front of face, no intersection + if (d1 > 0 && d2 > 0) + return; + + if (d2 > 0) + getout = true; // endpoint is not in solid + if (d1 > 0) + startout = true; + + if (d1 <= 0 && d2 <= 0) + continue; + + // crosses face + if (d1 > d2) + { // enter + f = (d1-DIST_EPSILON) / (d1-d2); + if (f > enterfrac) + { + enterfrac = f; + clipplane = plane; + leadside = side; + } + } + else + { // leave + f = (d1+DIST_EPSILON) / (d1-d2); + if (f < leavefrac) + leavefrac = f; + } + } + } + else + { + for (int i=0 ; inumsides ; ++i) + { + side = &dbrushsides[brush->firstside+i]; + plane = dplanes + side->planenum; + + // FIXME: special case for axial + + // special point case + // don't ray trace against bevel planes + if( side->bevel == 1 ) + continue; + + dist = plane->dist; + d1 = DotProduct (p1, plane->normal) - dist; + d2 = DotProduct (p2, plane->normal) - dist; + + // if completely in front of face, no intersection + if (d1 > 0 && d2 > 0) + return; + + if (d2 > 0) + getout = true; // endpoint is not in solid + if (d1 > 0) + startout = true; + + if (d1 <= 0 && d2 <= 0) + continue; + + // crosses face + if (d1 > d2) + { // enter + f = (d1-DIST_EPSILON) / (d1-d2); + if (f > enterfrac) + { + enterfrac = f; + clipplane = plane; + leadside = side; + } + } + else + { // leave + f = (d1+DIST_EPSILON) / (d1-d2); + if (f < leavefrac) + leavefrac = f; + } + } + } + + + + if (!startout) + { // original point was inside brush + trace->startsolid = true; + if (!getout) + trace->allsolid = true; + return; + } + if (enterfrac < leavefrac) + { + if (enterfrac > NEVER_UPDATED && enterfrac < trace->fraction) + { + if (enterfrac < 0) + enterfrac = 0; + trace->fraction = enterfrac; + trace->plane.dist = clipplane->dist; + trace->plane.normal = clipplane->normal; + trace->plane.type = clipplane->type; + if (leadside->texinfo!=-1) + trace->surface = &texinfo[leadside->texinfo]; + else + trace->surface = 0; + trace->contents = brush->contents; + } + } +} + +void TestLine_DoesHitSky( FourVectors const& start, FourVectors const& stop, + fltx4 *pFractionVisible, bool canRecurse, int static_prop_to_skip, bool bDoDebug ) +{ + FourRays myrays; + myrays.origin = start; + myrays.direction = stop; + myrays.direction -= myrays.origin; + fltx4 len = myrays.direction.length(); + myrays.direction *= ReciprocalSIMD( len ); + RayTracingResult rt_result; + CCoverageCountTexture coverageCallback; + + g_RtEnv.Trace4Rays(myrays, Four_Zeros, len, &rt_result, TRACE_ID_STATICPROP | static_prop_to_skip, g_bTextureShadows? &coverageCallback : 0); + + if ( bDoDebug ) + { + WriteTrace( "trace.txt", myrays, rt_result ); + } + + float aOcclusion[4]; + for ( int i = 0; i < 4; i++ ) + { + aOcclusion[i] = 0.0f; + if ( ( rt_result.HitIds[i] != -1 ) && + ( rt_result.HitDistance.m128_f32[i] < len.m128_f32[i] ) ) + { + int id = g_RtEnv.OptimizedTriangleList[rt_result.HitIds[i]].m_Data.m_IntersectData.m_nTriangleID; + if ( !( id & TRACE_ID_SKY ) ) + aOcclusion[i] = 1.0f; + } + } + fltx4 occlusion = LoadUnalignedSIMD( aOcclusion ); + if (g_bTextureShadows) + occlusion = MaxSIMD ( occlusion, coverageCallback.GetCoverage() ); + + bool fullyOccluded = ( TestSignSIMD( CmpGeSIMD( occlusion, Four_Ones ) ) == 0xF ); + + // if we hit sky, and we're not in a sky camera's area, try clipping into the 3D sky boxes + if ( (! fullyOccluded) && canRecurse && (! g_bNoSkyRecurse ) ) + { + FourVectors dir = stop; + dir -= start; + dir.VectorNormalize(); + + int leafIndex = -1; + leafIndex = PointLeafnum( start.Vec( 0 ) ); + if ( leafIndex >= 0 ) + { + int area = dleafs[leafIndex].area; + if (area >= 0 && area < numareas) + { + if (area_sky_cameras[area] < 0) + { + int cam; + for (cam = 0; cam < num_sky_cameras; ++cam) + { + FourVectors skystart, skytrans, skystop; + skystart.DuplicateVector( sky_cameras[cam].origin ); + skystop = start; + skystop *= sky_cameras[cam].world_to_sky; + skystart += skystop; + + skystop = dir; + skystop *= MAX_TRACE_LENGTH; + skystop += skystart; + TestLine_DoesHitSky ( skystart, skystop, pFractionVisible, false, static_prop_to_skip, bDoDebug ); + occlusion = AddSIMD ( occlusion, Four_Ones ); + occlusion = SubSIMD ( occlusion, *pFractionVisible ); + } + } + } + } + } + + occlusion = MaxSIMD( occlusion, Four_Zeros ); + occlusion = MinSIMD( occlusion, Four_Ones ); + *pFractionVisible = SubSIMD( Four_Ones, occlusion ); +} + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int PointLeafnum_r( const Vector &point, int ndxNode ) +{ + // while loop here is to avoid recursion overhead + while( ndxNode >= 0 ) + { + dnode_t *pNode = dnodes + ndxNode; + dplane_t *pPlane = dplanes + pNode->planenum; + + float dist; + if( pPlane->type < 3 ) + { + dist = point[pPlane->type] - pPlane->dist; + } + else + { + dist = DotProduct( pPlane->normal, point ) - pPlane->dist; + } + + if( dist < 0.0f ) + { + ndxNode = pNode->children[1]; + } + else + { + ndxNode = pNode->children[0]; + } + } + + return ( -1 - ndxNode ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int PointLeafnum( const Vector &point ) +{ + return PointLeafnum_r( point, 0 ); +} + +// this iterates the list of entities looking for _vradshadows 1 +// each brush entity containing this key is added to the raytracing environment +// as a triangle soup model. + +dmodel_t *BrushmodelForEntity( entity_t *pEntity ) +{ + const char *pModelname = ValueForKey( pEntity, "model" ); + if ( Q_strlen(pModelname) > 1 ) + { + int modelIndex = atol( pModelname + 1 ); + if ( modelIndex > 0 && modelIndex < nummodels ) + { + return &dmodels[modelIndex]; + } + } + return NULL; +} + +void AddBrushToRaytraceEnvironment( dbrush_t *pBrush, const VMatrix &xform ) +{ + if ( !( pBrush->contents & MASK_OPAQUE ) ) + return; + + Vector v0, v1, v2; + for (int i = 0; i < pBrush->numsides; i++ ) + { + dbrushside_t *side = &dbrushsides[pBrush->firstside + i]; + dplane_t *plane = &dplanes[side->planenum]; + texinfo_t *tx = &texinfo[side->texinfo]; + winding_t *w = BaseWindingForPlane (plane->normal, plane->dist); + + if ( tx->flags & SURF_SKY || side->dispinfo ) + continue; + + for (int j=0 ; jnumsides && w; j++) + { + if (i == j) + continue; + dbrushside_t *pOtherSide = &dbrushsides[pBrush->firstside + j]; + if (pOtherSide->bevel) + continue; + plane = &dplanes[pOtherSide->planenum^1]; + ChopWindingInPlace (&w, plane->normal, plane->dist, 0); + } + if ( w ) + { + for ( int j = 2; j < w->numpoints; j++ ) + { + v0 = xform.VMul4x3(w->p[0]); + v1 = xform.VMul4x3(w->p[j-1]); + v2 = xform.VMul4x3(w->p[j]); + Vector fullCoverage; + fullCoverage.x = 1.0f; + g_RtEnv.AddTriangle(TRACE_ID_OPAQUE, v0, v1, v2, fullCoverage); + } + FreeWinding( w ); + } + } +} + + +// recurse the bsp and build a list of brushes at the leaves under this node +void GetBrushes_r( int node, CUtlVector &list ) +{ + if ( node < 0 ) + { + int leafIndex = -1 - node; + // Add the solids in the leaf + for ( int i = 0; i < dleafs[leafIndex].numleafbrushes; i++ ) + { + int brushIndex = dleafbrushes[dleafs[leafIndex].firstleafbrush + i]; + if ( list.Find(brushIndex) < 0 ) + { + list.AddToTail( brushIndex ); + } + } + } + else + { + // recurse + dnode_t *pnode = dnodes + node; + + GetBrushes_r( pnode->children[0], list ); + GetBrushes_r( pnode->children[1], list ); + } +} + + +void AddBrushes( dmodel_t *pModel, const VMatrix &xform ) +{ + if ( pModel ) + { + CUtlVector brushList; + GetBrushes_r( pModel->headnode, brushList ); + for ( int i = 0; i < brushList.Count(); i++ ) + { + int ndxBrush = brushList[i]; + AddBrushToRaytraceEnvironment( &dbrushes[ndxBrush], xform ); + } + } +} + + +// Adds the brush entities that cast shadows to the raytrace environment +void ExtractBrushEntityShadowCasters() +{ + for ( int i = 0; i < num_entities; i++ ) + { + if ( IntForKey( &entities[i], "vrad_brush_cast_shadows" ) != 0 ) + { + Vector origin; + QAngle angles; + GetVectorForKey( &entities[i], "origin", origin ); + GetAnglesForKey( &entities[i], "angles", angles ); + VMatrix xform; + xform.SetupMatrixOrgAngles( origin, angles ); + AddBrushes( BrushmodelForEntity( &entities[i] ), xform ); + } + } +} + +void AddBrushesForRayTrace( void ) +{ + if ( !nummodels ) + return; + + VMatrix identity; + identity.Identity(); + + CUtlVector brushList; + GetBrushes_r ( dmodels[0].headnode, brushList ); + + for ( int i = 0; i < brushList.Size(); i++ ) + { + dbrush_t *brush = &dbrushes[brushList[i]]; + AddBrushToRaytraceEnvironment ( brush, identity ); + } + + for ( int i = 0; i < dmodels[0].numfaces; i++ ) + { + int ndxFace = dmodels[0].firstface + i; + dface_t *face = &g_pFaces[ndxFace]; + + texinfo_t *tx = &texinfo[face->texinfo]; + if ( !( tx->flags & SURF_SKY ) ) + continue; + + Vector points[MAX_POINTS_ON_WINDING]; + + for ( int j = 0; j < face->numedges; j++ ) + { + if ( j >= MAX_POINTS_ON_WINDING ) + Error( "***** ERROR! MAX_POINTS_ON_WINDING reached!" ); + + if ( face->firstedge + j >= ARRAYSIZE( dsurfedges ) ) + Error( "***** ERROR! face->firstedge + j >= ARRAYSIZE( dsurfedges )!" ); + + int surfEdge = dsurfedges[face->firstedge + j]; + unsigned short v; + + if (surfEdge < 0) + v = dedges[-surfEdge].v[1]; + else + v = dedges[surfEdge].v[0]; + + if ( v >= ARRAYSIZE( dvertexes ) ) + Error( "***** ERROR! v(%u) >= ARRAYSIZE( dvertexes(%d) )!", ( unsigned int )v, ARRAYSIZE( dvertexes ) ); + + dvertex_t *dv = &dvertexes[v]; + points[j] = dv->point; + } + + for ( int j = 2; j < face->numedges; j++ ) + { + Vector fullCoverage; + fullCoverage.x = 1.0f; + g_RtEnv.AddTriangle ( TRACE_ID_SKY, points[0], points[j - 1], points[j], fullCoverage ); + } + } +} diff --git a/mp/src/utils/vrad/vismat.cpp b/mp/src/utils/vrad/vismat.cpp index 7491f69f..ca9398ec 100644 --- a/mp/src/utils/vrad/vismat.cpp +++ b/mp/src/utils/vrad/vismat.cpp @@ -1,483 +1,483 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include "vrad.h" -#include "vmpi.h" -#ifdef MPI -#include "messbuf.h" -static MessageBuffer mb; -#endif - -#define HALFBIT - -extern char source[MAX_PATH]; -extern char vismatfile[_MAX_PATH]; -extern char incrementfile[_MAX_PATH]; -extern qboolean incremental; - -/* -=================================================================== - -VISIBILITY MATRIX - -Determine which patches can see each other -Use the PVS to accelerate if available -=================================================================== -*/ - -#define TEST_EPSILON 0.1 -#define PLANE_TEST_EPSILON 0.01 // patch must be this much in front of the plane to be considered "in front" -#define PATCH_FACE_OFFSET 0.1 // push patch origins off from the face by this amount to avoid self collisions - -#define STREAM_SIZE 512 - -class CTransferMaker -{ -public: - - CTransferMaker( transfer_t *all_transfers ); - ~CTransferMaker(); - - FORCEINLINE void TestMakeTransfer( Vector start, Vector stop, int ndxShooter, int ndxReciever ) - { - g_RtEnv.AddToRayStream( m_RayStream, start, stop, &m_pResults[m_nTests] ); - m_pShooterPatches[m_nTests] = ndxShooter; - m_pRecieverPatches[m_nTests] = ndxReciever; - ++m_nTests; - } - - void Finish(); - -private: - - int m_nTests; - RayTracingSingleResult *m_pResults; - int *m_pShooterPatches; - int *m_pRecieverPatches; - RayStream m_RayStream; - transfer_t *m_AllTransfers; -}; - -CTransferMaker::CTransferMaker( transfer_t *all_transfers ) : - m_AllTransfers( all_transfers ), m_nTests( 0 ) -{ - m_pResults = (RayTracingSingleResult *)calloc( 1, MAX_PATCHES * sizeof ( RayTracingSingleResult ) ); - m_pShooterPatches = (int *)calloc( 1, MAX_PATCHES * sizeof( int ) ); - m_pRecieverPatches = (int *)calloc( 1, MAX_PATCHES * sizeof( int ) ); -} - -CTransferMaker::~CTransferMaker() -{ - free ( m_pResults ); - free ( m_pShooterPatches ); - free (m_pRecieverPatches ); -} - -void CTransferMaker::Finish() -{ - g_RtEnv.FinishRayStream( m_RayStream ); - for ( int i = 0; i < m_nTests; ++i ) - { - if ( m_pResults[i].HitID == -1 || m_pResults[i].HitDistance >= m_pResults[i].ray_length ) - { - MakeTransfer( m_pShooterPatches[i], m_pRecieverPatches[i], m_AllTransfers ); - } - } - m_nTests = 0; -} - - -dleaf_t* PointInLeaf (int iNode, Vector const& point) -{ - if ( iNode < 0 ) - return &dleafs[ (-1-iNode) ]; - - dnode_t *node = &dnodes[iNode]; - dplane_t *plane = &dplanes[ node->planenum ]; - - float dist = DotProduct (point, plane->normal) - plane->dist; - if ( dist > TEST_EPSILON ) - { - return PointInLeaf( node->children[0], point ); - } - else if ( dist < -TEST_EPSILON ) - { - return PointInLeaf( node->children[1], point ); - } - else - { - dleaf_t *pTest = PointInLeaf( node->children[0], point ); - if ( pTest->cluster != -1 ) - return pTest; - - return PointInLeaf( node->children[1], point ); - } -} - - -int ClusterFromPoint( Vector const& point ) -{ - dleaf_t *leaf = PointInLeaf( 0, point ); - - return leaf->cluster; -} - -void PvsForOrigin (Vector& org, byte *pvs) -{ - int visofs; - int cluster; - - if (!visdatasize) - { - memset (pvs, 255, (dvis->numclusters+7)/8 ); - return; - } - - cluster = ClusterFromPoint( org ); - if ( cluster < 0 ) - { - visofs = -1; - } - else - { - visofs = dvis->bitofs[ cluster ][DVIS_PVS]; - } - - if (visofs == -1) - Error ("visofs == -1"); - - DecompressVis (&dvisdata[visofs], pvs); -} - - -void TestPatchToPatch( int ndxPatch1, int ndxPatch2, int head, transfer_t *transfers, CTransferMaker &transferMaker, int iThread ) -{ - Vector tmp; - - // - // get patches - // - if( ndxPatch1 == g_Patches.InvalidIndex() || ndxPatch2 == g_Patches.InvalidIndex() ) - return; - - CPatch *patch = &g_Patches.Element( ndxPatch1 ); - CPatch *patch2 = &g_Patches.Element( ndxPatch2 ); - - if (patch2->child1 != g_Patches.InvalidIndex() ) - { - // check to see if we should use a child node instead - - VectorSubtract( patch->origin, patch2->origin, tmp ); - // SQRT( 1/4 ) - // FIXME: should be based on form-factor (ie. include visible angle, etc) - if ( DotProduct(tmp, tmp) * 0.0625 < patch2->area ) - { - TestPatchToPatch( ndxPatch1, patch2->child1, head, transfers, transferMaker, iThread ); - TestPatchToPatch( ndxPatch1, patch2->child2, head, transfers, transferMaker, iThread ); - return; - } - } - - // check vis between patch and patch2 - // if bit has not already been set - // && v2 is not behind light plane - // && v2 is visible from v1 - if ( DotProduct( patch2->origin, patch->normal ) > patch->planeDist + PLANE_TEST_EPSILON ) - { - // push out origins from face so that don't intersect their owners - Vector p1, p2; - VectorAdd( patch->origin, patch->normal, p1 ); - VectorAdd( patch2->origin, patch2->normal, p2 ); - transferMaker.TestMakeTransfer( p1, p2, ndxPatch1, ndxPatch2 ); - } -} - - -/* -============== -TestPatchToFace - -Sets vis bits for all patches in the face -============== -*/ -void TestPatchToFace (unsigned patchnum, int facenum, int head, transfer_t *transfers, CTransferMaker &transferMaker, int iThread ) -{ - if( faceParents.Element( facenum ) == g_Patches.InvalidIndex() || patchnum == g_Patches.InvalidIndex() ) - return; - - CPatch *patch = &g_Patches.Element( patchnum ); - CPatch *patch2 = &g_Patches.Element( faceParents.Element( facenum ) ); - - // if emitter is behind that face plane, skip all patches - - CPatch *pNextPatch; - - if ( patch2 && DotProduct(patch->origin, patch2->normal) > patch2->planeDist + PLANE_TEST_EPSILON ) - { - // we need to do a real test - for( ; patch2; patch2 = pNextPatch ) - { - // next patch - pNextPatch = NULL; - if( patch2->ndxNextParent != g_Patches.InvalidIndex() ) - { - pNextPatch = &g_Patches.Element( patch2->ndxNextParent ); - } - - /* - // skip patches too far away - VectorSubtract( patch->origin, patch2->origin, tmp ); - if (DotProduct( tmp, tmp ) > 512 * 512) - continue; - */ - - int ndxPatch2 = patch2 - g_Patches.Base(); - TestPatchToPatch( patchnum, ndxPatch2, head, transfers, transferMaker, iThread ); - } - } -} - - -struct ClusterDispList_t -{ - CUtlVector dispFaces; -}; - -static CUtlVector g_ClusterDispFaces; - -//----------------------------------------------------------------------------- -// Helps us find all displacements associated with a particular cluster -//----------------------------------------------------------------------------- -void AddDispsToClusterTable( void ) -{ - g_ClusterDispFaces.SetCount( g_ClusterLeaves.Count() ); - - // - // add displacement faces to the cluster table - // - for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) - { - // search for displacement faces - if( g_pFaces[ndxFace].dispinfo == -1 ) - continue; - - // - // get the clusters associated with the face - // - if( g_FacePatches.Element( ndxFace ) != g_FacePatches.InvalidIndex() ) - { - CPatch *pNextPatch = NULL; - for( CPatch *pPatch = &g_Patches.Element( g_FacePatches.Element( ndxFace ) ); pPatch; pPatch = pNextPatch ) - { - // next patch - pNextPatch = NULL; - if( pPatch->ndxNext != g_Patches.InvalidIndex() ) - { - pNextPatch = &g_Patches.Element( pPatch->ndxNext ); - } - - if( pPatch->clusterNumber != g_Patches.InvalidIndex() ) - { - int ndxDisp = g_ClusterDispFaces[pPatch->clusterNumber].dispFaces.Find( ndxFace ); - if( ndxDisp == -1 ) - { - ndxDisp = g_ClusterDispFaces[pPatch->clusterNumber].dispFaces.AddToTail(); - g_ClusterDispFaces[pPatch->clusterNumber].dispFaces[ndxDisp] = ndxFace; - } - } - } - } - } -} - - -/* -============== -BuildVisRow - -Calc vis bits from a single patch -============== -*/ -void BuildVisRow (int patchnum, byte *pvs, int head, transfer_t *transfers, CTransferMaker &transferMaker, int iThread ) -{ - int j, k, l, leafIndex; - CPatch *patch; - dleaf_t *leaf; - byte face_tested[MAX_MAP_FACES]; - byte disp_tested[MAX_MAP_FACES]; - - patch = &g_Patches.Element( patchnum ); - - memset( face_tested, 0, numfaces ) ; - memset( disp_tested, 0, numfaces ); - - for (j=0; jnumclusters; j++) - { - if ( ! ( pvs[(j)>>3] & (1<<((j)&7)) ) ) - { - continue; // not in pvs - } - - for ( leafIndex = 0; leafIndex < g_ClusterLeaves[j].leafCount; leafIndex++ ) - { - leaf = dleafs + g_ClusterLeaves[j].leafs[leafIndex]; - - for (k=0 ; knumleaffaces; k++) - { - l = dleaffaces[leaf->firstleafface + k]; - // faces can be marksurfed by multiple leaves, but - // don't bother testing again - if (face_tested[l]) - { - continue; - } - face_tested[l] = 1; - - // don't check patches on the same face - if (patch->faceNumber == l) - continue; - TestPatchToFace (patchnum, l, head, transfers, transferMaker, iThread ); - } - } - - int dispCount = g_ClusterDispFaces[j].dispFaces.Size(); - for( int ndxDisp = 0; ndxDisp < dispCount; ndxDisp++ ) - { - int ndxFace = g_ClusterDispFaces[j].dispFaces[ndxDisp]; - if( disp_tested[ndxFace] ) - continue; - - disp_tested[ndxFace] = 1; - - // don't check patches on the same face - if( patch->faceNumber == ndxFace ) - continue; - - TestPatchToFace( patchnum, ndxFace, head, transfers, transferMaker, iThread ); - } - } - - - // Msg("%d) Transfers: %5d\n", patchnum, patch->numtransfers); -} - - - -/* -=========== -BuildVisLeafs - - This is run by multiple threads -=========== -*/ - -transfer_t* BuildVisLeafs_Start() -{ - return (transfer_t *)calloc( 1, MAX_PATCHES * sizeof( transfer_t ) ); -} - - -// If PatchCB is non-null, it is called after each row is generated (used by MPI). -void BuildVisLeafs_Cluster( - int threadnum, - transfer_t *transfers, - int iCluster, - void (*PatchCB)(int iThread, int patchnum, CPatch *patch) - ) -{ - byte pvs[(MAX_MAP_CLUSTERS+7)/8]; - CPatch *patch; - int head; - unsigned patchnum; - - DecompressVis( &dvisdata[ dvis->bitofs[ iCluster ][DVIS_PVS] ], pvs); - head = 0; - - CTransferMaker transferMaker( transfers ); - - // light every patch in the cluster - if( clusterChildren.Element( iCluster ) != clusterChildren.InvalidIndex() ) - { - CPatch *pNextPatch; - for( patch = &g_Patches.Element( clusterChildren.Element( iCluster ) ); patch; patch = pNextPatch ) - { - // - // next patch - // - pNextPatch = NULL; - if( patch->ndxNextClusterChild != g_Patches.InvalidIndex() ) - { - pNextPatch = &g_Patches.Element( patch->ndxNextClusterChild ); - } - - patchnum = patch - g_Patches.Base(); - - // build to all other world clusters - BuildVisRow (patchnum, pvs, head, transfers, transferMaker, threadnum ); - transferMaker.Finish(); - - // do the transfers - MakeScales( patchnum, transfers ); - - // Let MPI aggregate the data if it's being used. - if ( PatchCB ) - PatchCB( threadnum, patchnum, patch ); - } - } -} - - -void BuildVisLeafs_End( transfer_t *transfers ) -{ - free( transfers ); -} - - -void BuildVisLeafs( int threadnum, void *pUserData ) -{ - transfer_t *transfers = BuildVisLeafs_Start(); - - while ( 1 ) - { - // - // build a minimal BSP tree that only - // covers areas relevent to the PVS - // - // JAY: Now this returns a cluster index - int iCluster = GetThreadWork(); - if ( iCluster == -1 ) - break; - - BuildVisLeafs_Cluster( threadnum, transfers, iCluster, NULL ); - } - - BuildVisLeafs_End( transfers ); -} - - -/* -============== -BuildVisMatrix -============== -*/ -void BuildVisMatrix (void) -{ - if ( g_bUseMPI ) - { - RunMPIBuildVisLeafs(); - } - else - { - RunThreadsOn (dvis->numclusters, true, BuildVisLeafs); - } -} - -void FreeVisMatrix (void) -{ - -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vrad.h" +#include "vmpi.h" +#ifdef MPI +#include "messbuf.h" +static MessageBuffer mb; +#endif + +#define HALFBIT + +extern char source[MAX_PATH]; +extern char vismatfile[_MAX_PATH]; +extern char incrementfile[_MAX_PATH]; +extern qboolean incremental; + +/* +=================================================================== + +VISIBILITY MATRIX + +Determine which patches can see each other +Use the PVS to accelerate if available +=================================================================== +*/ + +#define TEST_EPSILON 0.1 +#define PLANE_TEST_EPSILON 0.01 // patch must be this much in front of the plane to be considered "in front" +#define PATCH_FACE_OFFSET 0.1 // push patch origins off from the face by this amount to avoid self collisions + +#define STREAM_SIZE 512 + +class CTransferMaker +{ +public: + + CTransferMaker( transfer_t *all_transfers ); + ~CTransferMaker(); + + FORCEINLINE void TestMakeTransfer( Vector start, Vector stop, int ndxShooter, int ndxReciever ) + { + g_RtEnv.AddToRayStream( m_RayStream, start, stop, &m_pResults[m_nTests] ); + m_pShooterPatches[m_nTests] = ndxShooter; + m_pRecieverPatches[m_nTests] = ndxReciever; + ++m_nTests; + } + + void Finish(); + +private: + + int m_nTests; + RayTracingSingleResult *m_pResults; + int *m_pShooterPatches; + int *m_pRecieverPatches; + RayStream m_RayStream; + transfer_t *m_AllTransfers; +}; + +CTransferMaker::CTransferMaker( transfer_t *all_transfers ) : + m_AllTransfers( all_transfers ), m_nTests( 0 ) +{ + m_pResults = (RayTracingSingleResult *)calloc( 1, MAX_PATCHES * sizeof ( RayTracingSingleResult ) ); + m_pShooterPatches = (int *)calloc( 1, MAX_PATCHES * sizeof( int ) ); + m_pRecieverPatches = (int *)calloc( 1, MAX_PATCHES * sizeof( int ) ); +} + +CTransferMaker::~CTransferMaker() +{ + free ( m_pResults ); + free ( m_pShooterPatches ); + free (m_pRecieverPatches ); +} + +void CTransferMaker::Finish() +{ + g_RtEnv.FinishRayStream( m_RayStream ); + for ( int i = 0; i < m_nTests; ++i ) + { + if ( m_pResults[i].HitID == -1 || m_pResults[i].HitDistance >= m_pResults[i].ray_length ) + { + MakeTransfer( m_pShooterPatches[i], m_pRecieverPatches[i], m_AllTransfers ); + } + } + m_nTests = 0; +} + + +dleaf_t* PointInLeaf (int iNode, Vector const& point) +{ + if ( iNode < 0 ) + return &dleafs[ (-1-iNode) ]; + + dnode_t *node = &dnodes[iNode]; + dplane_t *plane = &dplanes[ node->planenum ]; + + float dist = DotProduct (point, plane->normal) - plane->dist; + if ( dist > TEST_EPSILON ) + { + return PointInLeaf( node->children[0], point ); + } + else if ( dist < -TEST_EPSILON ) + { + return PointInLeaf( node->children[1], point ); + } + else + { + dleaf_t *pTest = PointInLeaf( node->children[0], point ); + if ( pTest->cluster != -1 ) + return pTest; + + return PointInLeaf( node->children[1], point ); + } +} + + +int ClusterFromPoint( Vector const& point ) +{ + dleaf_t *leaf = PointInLeaf( 0, point ); + + return leaf->cluster; +} + +void PvsForOrigin (Vector& org, byte *pvs) +{ + int visofs; + int cluster; + + if (!visdatasize) + { + memset (pvs, 255, (dvis->numclusters+7)/8 ); + return; + } + + cluster = ClusterFromPoint( org ); + if ( cluster < 0 ) + { + visofs = -1; + } + else + { + visofs = dvis->bitofs[ cluster ][DVIS_PVS]; + } + + if (visofs == -1) + Error ("visofs == -1"); + + DecompressVis (&dvisdata[visofs], pvs); +} + + +void TestPatchToPatch( int ndxPatch1, int ndxPatch2, int head, transfer_t *transfers, CTransferMaker &transferMaker, int iThread ) +{ + Vector tmp; + + // + // get patches + // + if( ndxPatch1 == g_Patches.InvalidIndex() || ndxPatch2 == g_Patches.InvalidIndex() ) + return; + + CPatch *patch = &g_Patches.Element( ndxPatch1 ); + CPatch *patch2 = &g_Patches.Element( ndxPatch2 ); + + if (patch2->child1 != g_Patches.InvalidIndex() ) + { + // check to see if we should use a child node instead + + VectorSubtract( patch->origin, patch2->origin, tmp ); + // SQRT( 1/4 ) + // FIXME: should be based on form-factor (ie. include visible angle, etc) + if ( DotProduct(tmp, tmp) * 0.0625 < patch2->area ) + { + TestPatchToPatch( ndxPatch1, patch2->child1, head, transfers, transferMaker, iThread ); + TestPatchToPatch( ndxPatch1, patch2->child2, head, transfers, transferMaker, iThread ); + return; + } + } + + // check vis between patch and patch2 + // if bit has not already been set + // && v2 is not behind light plane + // && v2 is visible from v1 + if ( DotProduct( patch2->origin, patch->normal ) > patch->planeDist + PLANE_TEST_EPSILON ) + { + // push out origins from face so that don't intersect their owners + Vector p1, p2; + VectorAdd( patch->origin, patch->normal, p1 ); + VectorAdd( patch2->origin, patch2->normal, p2 ); + transferMaker.TestMakeTransfer( p1, p2, ndxPatch1, ndxPatch2 ); + } +} + + +/* +============== +TestPatchToFace + +Sets vis bits for all patches in the face +============== +*/ +void TestPatchToFace (unsigned patchnum, int facenum, int head, transfer_t *transfers, CTransferMaker &transferMaker, int iThread ) +{ + if( faceParents.Element( facenum ) == g_Patches.InvalidIndex() || patchnum == g_Patches.InvalidIndex() ) + return; + + CPatch *patch = &g_Patches.Element( patchnum ); + CPatch *patch2 = &g_Patches.Element( faceParents.Element( facenum ) ); + + // if emitter is behind that face plane, skip all patches + + CPatch *pNextPatch; + + if ( patch2 && DotProduct(patch->origin, patch2->normal) > patch2->planeDist + PLANE_TEST_EPSILON ) + { + // we need to do a real test + for( ; patch2; patch2 = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch2->ndxNextParent != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch2->ndxNextParent ); + } + + /* + // skip patches too far away + VectorSubtract( patch->origin, patch2->origin, tmp ); + if (DotProduct( tmp, tmp ) > 512 * 512) + continue; + */ + + int ndxPatch2 = patch2 - g_Patches.Base(); + TestPatchToPatch( patchnum, ndxPatch2, head, transfers, transferMaker, iThread ); + } + } +} + + +struct ClusterDispList_t +{ + CUtlVector dispFaces; +}; + +static CUtlVector g_ClusterDispFaces; + +//----------------------------------------------------------------------------- +// Helps us find all displacements associated with a particular cluster +//----------------------------------------------------------------------------- +void AddDispsToClusterTable( void ) +{ + g_ClusterDispFaces.SetCount( g_ClusterLeaves.Count() ); + + // + // add displacement faces to the cluster table + // + for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) + { + // search for displacement faces + if( g_pFaces[ndxFace].dispinfo == -1 ) + continue; + + // + // get the clusters associated with the face + // + if( g_FacePatches.Element( ndxFace ) != g_FacePatches.InvalidIndex() ) + { + CPatch *pNextPatch = NULL; + for( CPatch *pPatch = &g_Patches.Element( g_FacePatches.Element( ndxFace ) ); pPatch; pPatch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( pPatch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( pPatch->ndxNext ); + } + + if( pPatch->clusterNumber != g_Patches.InvalidIndex() ) + { + int ndxDisp = g_ClusterDispFaces[pPatch->clusterNumber].dispFaces.Find( ndxFace ); + if( ndxDisp == -1 ) + { + ndxDisp = g_ClusterDispFaces[pPatch->clusterNumber].dispFaces.AddToTail(); + g_ClusterDispFaces[pPatch->clusterNumber].dispFaces[ndxDisp] = ndxFace; + } + } + } + } + } +} + + +/* +============== +BuildVisRow + +Calc vis bits from a single patch +============== +*/ +void BuildVisRow (int patchnum, byte *pvs, int head, transfer_t *transfers, CTransferMaker &transferMaker, int iThread ) +{ + int j, k, l, leafIndex; + CPatch *patch; + dleaf_t *leaf; + byte face_tested[MAX_MAP_FACES]; + byte disp_tested[MAX_MAP_FACES]; + + patch = &g_Patches.Element( patchnum ); + + memset( face_tested, 0, numfaces ) ; + memset( disp_tested, 0, numfaces ); + + for (j=0; jnumclusters; j++) + { + if ( ! ( pvs[(j)>>3] & (1<<((j)&7)) ) ) + { + continue; // not in pvs + } + + for ( leafIndex = 0; leafIndex < g_ClusterLeaves[j].leafCount; leafIndex++ ) + { + leaf = dleafs + g_ClusterLeaves[j].leafs[leafIndex]; + + for (k=0 ; knumleaffaces; k++) + { + l = dleaffaces[leaf->firstleafface + k]; + // faces can be marksurfed by multiple leaves, but + // don't bother testing again + if (face_tested[l]) + { + continue; + } + face_tested[l] = 1; + + // don't check patches on the same face + if (patch->faceNumber == l) + continue; + TestPatchToFace (patchnum, l, head, transfers, transferMaker, iThread ); + } + } + + int dispCount = g_ClusterDispFaces[j].dispFaces.Size(); + for( int ndxDisp = 0; ndxDisp < dispCount; ndxDisp++ ) + { + int ndxFace = g_ClusterDispFaces[j].dispFaces[ndxDisp]; + if( disp_tested[ndxFace] ) + continue; + + disp_tested[ndxFace] = 1; + + // don't check patches on the same face + if( patch->faceNumber == ndxFace ) + continue; + + TestPatchToFace( patchnum, ndxFace, head, transfers, transferMaker, iThread ); + } + } + + + // Msg("%d) Transfers: %5d\n", patchnum, patch->numtransfers); +} + + + +/* +=========== +BuildVisLeafs + + This is run by multiple threads +=========== +*/ + +transfer_t* BuildVisLeafs_Start() +{ + return (transfer_t *)calloc( 1, MAX_PATCHES * sizeof( transfer_t ) ); +} + + +// If PatchCB is non-null, it is called after each row is generated (used by MPI). +void BuildVisLeafs_Cluster( + int threadnum, + transfer_t *transfers, + int iCluster, + void (*PatchCB)(int iThread, int patchnum, CPatch *patch) + ) +{ + byte pvs[(MAX_MAP_CLUSTERS+7)/8]; + CPatch *patch; + int head; + unsigned patchnum; + + DecompressVis( &dvisdata[ dvis->bitofs[ iCluster ][DVIS_PVS] ], pvs); + head = 0; + + CTransferMaker transferMaker( transfers ); + + // light every patch in the cluster + if( clusterChildren.Element( iCluster ) != clusterChildren.InvalidIndex() ) + { + CPatch *pNextPatch; + for( patch = &g_Patches.Element( clusterChildren.Element( iCluster ) ); patch; patch = pNextPatch ) + { + // + // next patch + // + pNextPatch = NULL; + if( patch->ndxNextClusterChild != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNextClusterChild ); + } + + patchnum = patch - g_Patches.Base(); + + // build to all other world clusters + BuildVisRow (patchnum, pvs, head, transfers, transferMaker, threadnum ); + transferMaker.Finish(); + + // do the transfers + MakeScales( patchnum, transfers ); + + // Let MPI aggregate the data if it's being used. + if ( PatchCB ) + PatchCB( threadnum, patchnum, patch ); + } + } +} + + +void BuildVisLeafs_End( transfer_t *transfers ) +{ + free( transfers ); +} + + +void BuildVisLeafs( int threadnum, void *pUserData ) +{ + transfer_t *transfers = BuildVisLeafs_Start(); + + while ( 1 ) + { + // + // build a minimal BSP tree that only + // covers areas relevent to the PVS + // + // JAY: Now this returns a cluster index + int iCluster = GetThreadWork(); + if ( iCluster == -1 ) + break; + + BuildVisLeafs_Cluster( threadnum, transfers, iCluster, NULL ); + } + + BuildVisLeafs_End( transfers ); +} + + +/* +============== +BuildVisMatrix +============== +*/ +void BuildVisMatrix (void) +{ + if ( g_bUseMPI ) + { + RunMPIBuildVisLeafs(); + } + else + { + RunThreadsOn (dvis->numclusters, true, BuildVisLeafs); + } +} + +void FreeVisMatrix (void) +{ + +} diff --git a/mp/src/utils/vrad/vismat.h b/mp/src/utils/vrad/vismat.h index 74e0ba4c..927314a6 100644 --- a/mp/src/utils/vrad/vismat.h +++ b/mp/src/utils/vrad/vismat.h @@ -1,34 +1,34 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef VISMAT_H -#define VISMAT_H -#ifdef _WIN32 -#pragma once -#endif - - - -void BuildVisLeafs( int threadnum ); - - -// MPI uses these. -struct transfer_t; -transfer_t* BuildVisLeafs_Start(); - -// If PatchCB is non-null, it is called after each row is generated (used by MPI). -void BuildVisLeafs_Cluster( - int threadnum, - transfer_t *transfers, - int iCluster, - void (*PatchCB)(int iThread, int patchnum, CPatch *patch) ); - -void BuildVisLeafs_End( transfer_t *transfers ); - - - -#endif // VISMAT_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VISMAT_H +#define VISMAT_H +#ifdef _WIN32 +#pragma once +#endif + + + +void BuildVisLeafs( int threadnum ); + + +// MPI uses these. +struct transfer_t; +transfer_t* BuildVisLeafs_Start(); + +// If PatchCB is non-null, it is called after each row is generated (used by MPI). +void BuildVisLeafs_Cluster( + int threadnum, + transfer_t *transfers, + int iCluster, + void (*PatchCB)(int iThread, int patchnum, CPatch *patch) ); + +void BuildVisLeafs_End( transfer_t *transfers ); + + + +#endif // VISMAT_H diff --git a/mp/src/utils/vrad/vrad.cpp b/mp/src/utils/vrad/vrad.cpp index 8100dc3f..70e481fb 100644 --- a/mp/src/utils/vrad/vrad.cpp +++ b/mp/src/utils/vrad/vrad.cpp @@ -1,2936 +1,2936 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -// vrad.c - -#include "vrad.h" -#include "physdll.h" -#include "lightmap.h" -#include "tier1/strtools.h" -#include "vmpi.h" -#include "macro_texture.h" -#include "vmpi_tools_shared.h" -#include "leaf_ambient_lighting.h" -#include "tools_minidump.h" -#include "loadcmdline.h" -#include "byteswap.h" - -#define ALLOWDEBUGOPTIONS (0 || _DEBUG) - -static FileHandle_t pFpTrans = NULL; - -/* - -NOTES ------ - -every surface must be divided into at least two patches each axis - -*/ - -CUtlVector g_Patches; -CUtlVector g_FacePatches; // constains all patches, children first -CUtlVector faceParents; // contains only root patches, use next parent to iterate -CUtlVector clusterChildren; -CUtlVector emitlight; -CUtlVector addlight; - -int num_sky_cameras; -sky_camera_t sky_cameras[MAX_MAP_AREAS]; -int area_sky_cameras[MAX_MAP_AREAS]; - -entity_t *face_entity[MAX_MAP_FACES]; -Vector face_offset[MAX_MAP_FACES]; // for rotating bmodels -int fakeplanes; - -unsigned numbounce = 100; // 25; /* Originally this was 8 */ - -float maxchop = 4; // coarsest allowed number of luxel widths for a patch -float minchop = 4; // "-chop" tightest number of luxel widths for a patch, used on edges -float dispchop = 8.0f; // number of luxel widths for a patch -float g_MaxDispPatchRadius = 1500.0f; // Maximum radius allowed for displacement patches -qboolean g_bDumpPatches; -bool bDumpNormals = false; -bool g_bDumpRtEnv = false; -bool bRed2Black = true; -bool g_bFastAmbient = false; -bool g_bNoSkyRecurse = false; - -int junk; - -Vector ambient( 0, 0, 0 ); - -float lightscale = 1.0; -float dlight_threshold = 0.1; // was DIRECT_LIGHT constant - -char source[MAX_PATH] = ""; -char platformPath[MAX_PATH] = ""; - -char level_name[MAX_PATH] = ""; // map filename, without extension or path info - -char global_lights[MAX_PATH] = ""; -char designer_lights[MAX_PATH] = ""; -char level_lights[MAX_PATH] = ""; - -char vismatfile[_MAX_PATH] = ""; -char incrementfile[_MAX_PATH] = ""; - -IIncremental *g_pIncremental = 0; -bool g_bInterrupt = false; // Wsed with background lighting in WC. Tells VRAD - // to stop lighting. -float g_SunAngularExtent=0.0; - -float g_flSkySampleScale = 1.0; - -bool g_bLargeDispSampleRadius = false; - -bool g_bOnlyStaticProps = false; -bool g_bShowStaticPropNormals = false; - - -float gamma_value = 0.5; -float indirect_sun = 1.0; -float reflectivityScale = 1.0; -qboolean do_extra = true; -bool debug_extra = false; -qboolean do_fast = false; -qboolean do_centersamples = false; -int extrapasses = 4; -float smoothing_threshold = 0.7071067; // cos(45.0*(M_PI/180)) -// Cosine of smoothing angle(in radians) -float coring = 1.0; // Light threshold to force to blackness(minimizes lightmaps) -qboolean texscale = true; -int dlight_map = 0; // Setting to 1 forces direct lighting into different lightmap than radiosity - -float luxeldensity = 1.0; -unsigned num_degenerate_faces; - -qboolean g_bLowPriority = false; -qboolean g_bLogHashData = false; -bool g_bNoDetailLighting = false; -double g_flStartTime; -bool g_bStaticPropLighting = false; -bool g_bStaticPropPolys = false; -bool g_bTextureShadows = false; -bool g_bDisablePropSelfShadowing = false; - - -CUtlVector g_FacesVisibleToLights; - -RayTracingEnvironment g_RtEnv; - -dface_t *g_pFaces=0; - -// this is a list of material names used on static props which shouldn't cast shadows. a -// sequential search is used since we allow substring matches. its not time critical, and this -// functionality is a stopgap until vrad starts reading .vmt files. -CUtlVector g_NonShadowCastingMaterialStrings; -/* -=================================================================== - -MISC - -=================================================================== -*/ - - -int leafparents[MAX_MAP_LEAFS]; -int nodeparents[MAX_MAP_NODES]; - -void MakeParents (int nodenum, int parent) -{ - int i, j; - dnode_t *node; - - nodeparents[nodenum] = parent; - node = &dnodes[nodenum]; - - for (i=0 ; i<2 ; i++) - { - j = node->children[i]; - if (j < 0) - leafparents[-j - 1] = nodenum; - else - MakeParents (j, nodenum); - } -} - - -/* -=================================================================== - - TEXTURE LIGHT VALUES - -=================================================================== -*/ - -typedef struct -{ - char name[256]; - Vector value; - char *filename; -} texlight_t; - -#define MAX_TEXLIGHTS 128 - -texlight_t texlights[MAX_TEXLIGHTS]; -int num_texlights; - -/* -============ -ReadLightFile -============ -*/ -void ReadLightFile (char *filename) -{ - char buf[1024]; - int file_texlights = 0; - - FileHandle_t f = g_pFileSystem->Open( filename, "r" ); - if (!f) - { - Warning("Warning: Couldn't open texlight file %s.\n", filename); - return; - } - - Msg("[Reading texlights from '%s']\n", filename); - while ( CmdLib_FGets( buf, sizeof( buf ), f ) ) - { - // check ldr/hdr - char * scan = buf; - if ( !strnicmp( "hdr:", scan, 4) ) - { - scan += 4; - if ( ! g_bHDR ) - { - continue; - } - } - if ( !strnicmp( "ldr:", scan, 4) ) - { - scan += 4; - if ( g_bHDR ) - { - continue; - } - } - - scan += strspn( scan, " \t" ); - char NoShadName[1024]; - if ( sscanf(scan,"noshadow %s",NoShadName)==1) - { - char * dot = strchr( NoShadName, '.' ); - if ( dot ) // if they specify .vmt, kill it - * dot = 0; - //printf("add %s as a non shadow casting material\n",NoShadName); - g_NonShadowCastingMaterialStrings.AddToTail( strdup( NoShadName )); - } - else if ( sscanf( scan, "forcetextureshadow %s", NoShadName ) == 1 ) - { - //printf("add %s as a non shadow casting material\n",NoShadName); - ForceTextureShadowsOnModel( NoShadName ); - } - else - { - char szTexlight[256]; - Vector value; - if ( num_texlights == MAX_TEXLIGHTS ) - Error ("Too many texlights, max = %d", MAX_TEXLIGHTS); - - int argCnt = sscanf (scan, "%s ",szTexlight ); - - if( argCnt != 1 ) - { - if ( strlen( scan ) > 4 ) - Msg( "ignoring bad texlight '%s' in %s", scan, filename ); - continue; - } - - LightForString( scan + strlen( szTexlight ) + 1, value ); - - int j = 0; - for( j; j < num_texlights; j ++ ) - { - if ( strcmp( texlights[j].name, szTexlight ) == 0 ) - { - if ( strcmp( texlights[j].filename, filename ) == 0 ) - { - Msg( "ERROR\a: Duplication of '%s' in file '%s'!\n", - texlights[j].name, texlights[j].filename ); - } - else if ( texlights[j].value[0] != value[0] - || texlights[j].value[1] != value[1] - || texlights[j].value[2] != value[2] ) - { - Warning( "Warning: Overriding '%s' from '%s' with '%s'!\n", - texlights[j].name, texlights[j].filename, filename ); - } - else - { - Warning( "Warning: Redundant '%s' def in '%s' AND '%s'!\n", - texlights[j].name, texlights[j].filename, filename ); - } - break; - } - } - strcpy( texlights[j].name, szTexlight ); - VectorCopy( value, texlights[j].value ); - texlights[j].filename = filename; - file_texlights ++; - - num_texlights = max( num_texlights, j + 1 ); - } - } - qprintf ( "[%i texlights parsed from '%s']\n\n", file_texlights, filename); - g_pFileSystem->Close( f ); -} - - -/* -============ -LightForTexture -============ -*/ -void LightForTexture( const char *name, Vector& result ) -{ - int i; - - result[ 0 ] = result[ 1 ] = result[ 2 ] = 0; - - char baseFilename[ MAX_PATH ]; - - if ( Q_strncmp( "maps/", name, 5 ) == 0 ) - { - // this might be a patch texture for cubemaps. try to parse out the original filename. - if ( Q_strncmp( level_name, name + 5, Q_strlen( level_name ) ) == 0 ) - { - const char *base = name + 5 + Q_strlen( level_name ); - if ( *base == '/' ) - { - ++base; // step past the path separator - - // now we've gotten rid of the 'maps/level_name/' part, so we're left with - // 'originalName_%d_%d_%d'. - strcpy( baseFilename, base ); - bool foundSeparators = true; - for ( int i=0; i<3; ++i ) - { - char *underscore = Q_strrchr( baseFilename, '_' ); - if ( underscore && *underscore ) - { - *underscore = '\0'; - } - else - { - foundSeparators = false; - } - } - - if ( foundSeparators ) - { - name = baseFilename; - } - } - } - } - - for (i=0 ; inumedges); - w->numpoints = f->numedges; - - for (i=0 ; inumedges ; i++) - { - se = dsurfedges[f->firstedge + i]; - if (se < 0) - v = dedges[-se].v[1]; - else - v = dedges[se].v[0]; - - dv = &dvertexes[v]; - VectorAdd (dv->point, origin, w->p[i]); - } - - RemoveColinearPoints (w); - - return w; -} - -/* -============= -BaseLightForFace -============= -*/ -void BaseLightForFace( dface_t *f, Vector& light, float *parea, Vector& reflectivity ) -{ - texinfo_t *tx; - dtexdata_t *texdata; - - // - // check for light emited by texture - // - tx = &texinfo[f->texinfo]; - texdata = &dtexdata[tx->texdata]; - - LightForTexture (TexDataStringTable_GetString( texdata->nameStringTableID ), light); - - - *parea = texdata->height * texdata->width; - - VectorScale( texdata->reflectivity, reflectivityScale, reflectivity ); - - // always keep this less than 1 or the solution will not converge - for ( int i = 0; i < 3; i++ ) - { - if ( reflectivity[i] > 0.99 ) - reflectivity[i] = 0.99; - } -} - -qboolean IsSky (dface_t *f) -{ - texinfo_t *tx; - - tx = &texinfo[f->texinfo]; - if (tx->flags & SURF_SKY) - return true; - return false; -} - -#ifdef STATIC_FOG -/*============= -IsFog -=============*/ -qboolean IsFog( dface_t *f ) -{ - texinfo_t *tx; - - tx = &texinfo[f->texinfo]; - - // % denotes a fog texture - if( tx->texture[0] == '%' ) - return true; - - return false; -} -#endif - - -void ProcessSkyCameras() -{ - int i; - num_sky_cameras = 0; - for (i = 0; i < numareas; ++i) - { - area_sky_cameras[i] = -1; - } - - for (i = 0; i < num_entities; ++i) - { - entity_t *e = &entities[i]; - const char *name = ValueForKey (e, "classname"); - if (stricmp (name, "sky_camera")) - continue; - - Vector origin; - GetVectorForKey( e, "origin", origin ); - int node = PointLeafnum( origin ); - int area = -1; - if (node >= 0 && node < numleafs) area = dleafs[node].area; - float scale = FloatForKey( e, "scale" ); - - if (scale > 0.0f) - { - sky_cameras[num_sky_cameras].origin = origin; - sky_cameras[num_sky_cameras].sky_to_world = scale; - sky_cameras[num_sky_cameras].world_to_sky = 1.0f / scale; - sky_cameras[num_sky_cameras].area = area; - - if (area >= 0 && area < numareas) - { - area_sky_cameras[area] = num_sky_cameras; - } - - ++num_sky_cameras; - } - } - -} - - -/* -============= -MakePatchForFace -============= -*/ -float totalarea; -void MakePatchForFace (int fn, winding_t *w) -{ - dface_t *f = g_pFaces + fn; - float area; - CPatch *patch; - Vector centroid(0,0,0); - int i, j; - texinfo_t *tx; - - // get texture info - tx = &texinfo[f->texinfo]; - - // No patches at all for fog! -#ifdef STATIC_FOG - if ( IsFog( f ) ) - return; -#endif - - // the sky needs patches or the form factors don't work out correctly - // if (IsSky( f ) ) - // return; - - area = WindingArea (w); - if (area <= 0) - { - num_degenerate_faces++; - // Msg("degenerate face\n"); - return; - } - - totalarea += area; - - // get a patch - int ndxPatch = g_Patches.AddToTail(); - patch = &g_Patches[ndxPatch]; - memset( patch, 0, sizeof( CPatch ) ); - patch->ndxNext = g_Patches.InvalidIndex(); - patch->ndxNextParent = g_Patches.InvalidIndex(); - patch->ndxNextClusterChild = g_Patches.InvalidIndex(); - patch->child1 = g_Patches.InvalidIndex(); - patch->child2 = g_Patches.InvalidIndex(); - patch->parent = g_Patches.InvalidIndex(); - patch->needsBumpmap = tx->flags & SURF_BUMPLIGHT ? true : false; - - // link and save patch data - patch->ndxNext = g_FacePatches.Element( fn ); - g_FacePatches[fn] = ndxPatch; -// patch->next = face_g_Patches[fn]; -// face_g_Patches[fn] = patch; - - // compute a separate scale for chop - since the patch "scale" is the texture scale - // we want textures with higher resolution lighting to be chopped up more - float chopscale[2]; - chopscale[0] = chopscale[1] = 16.0f; - if ( texscale ) - { - // Compute the texture "scale" in s,t - for( i=0; i<2; i++ ) - { - patch->scale[i] = 0.0f; - chopscale[i] = 0.0f; - for( j=0; j<3; j++ ) - { - patch->scale[i] += - tx->textureVecsTexelsPerWorldUnits[i][j] * - tx->textureVecsTexelsPerWorldUnits[i][j]; - chopscale[i] += - tx->lightmapVecsLuxelsPerWorldUnits[i][j] * - tx->lightmapVecsLuxelsPerWorldUnits[i][j]; - } - patch->scale[i] = sqrt( patch->scale[i] ); - chopscale[i] = sqrt( chopscale[i] ); - } - } - else - { - patch->scale[0] = patch->scale[1] = 1.0f; - } - - patch->area = area; - - patch->sky = IsSky( f ); - - // chop scaled up lightmaps coarser - patch->luxscale = ((chopscale[0]+chopscale[1])/2); - patch->chop = maxchop; - - -#ifdef STATIC_FOG - patch->fog = FALSE; -#endif - - patch->winding = w; - - patch->plane = &dplanes[f->planenum]; - - // make a new plane to adjust for origined bmodels - if (face_offset[fn][0] || face_offset[fn][1] || face_offset[fn][2] ) - { - dplane_t *pl; - - // origin offset faces must create new planes - if (numplanes + fakeplanes >= MAX_MAP_PLANES) - { - Error ("numplanes + fakeplanes >= MAX_MAP_PLANES"); - } - pl = &dplanes[numplanes + fakeplanes]; - fakeplanes++; - - *pl = *(patch->plane); - pl->dist += DotProduct (face_offset[fn], pl->normal); - patch->plane = pl; - } - - patch->faceNumber = fn; - WindingCenter (w, patch->origin); - - // Save "center" for generating the face normals later. - VectorSubtract( patch->origin, face_offset[fn], face_centroids[fn] ); - - VectorCopy( patch->plane->normal, patch->normal ); - - WindingBounds (w, patch->face_mins, patch->face_maxs); - VectorCopy( patch->face_mins, patch->mins ); - VectorCopy( patch->face_maxs, patch->maxs ); - - BaseLightForFace( f, patch->baselight, &patch->basearea, patch->reflectivity ); - - // Chop all texlights very fine. - if ( !VectorCompare( patch->baselight, vec3_origin ) ) - { - // patch->chop = do_extra ? maxchop / 2 : maxchop; - tx->flags |= SURF_LIGHT; - } - - // get rid of do extra functionality on displacement surfaces - if( ValidDispFace( f ) ) - { - patch->chop = maxchop; - } - - // FIXME: If we wanted to add a dependency from vrad to the material system, - // we could do this. It would add a bunch of file accesses, though: - - /* - // Check for a material var which would override the patch chop - bool bFound; - const char *pMaterialName = TexDataStringTable_GetString( dtexdata[ tx->texdata ].nameStringTableID ); - MaterialSystemMaterial_t hMaterial = FindMaterial( pMaterialName, &bFound, false ); - if ( bFound ) - { - const char *pChopValue = GetMaterialVar( hMaterial, "%chop" ); - if ( pChopValue ) - { - float flChopValue; - if ( sscanf( pChopValue, "%f", &flChopValue ) > 0 ) - { - patch->chop = flChopValue; - } - } - } - */ -} - - -entity_t *EntityForModel (int modnum) -{ - int i; - char *s; - char name[16]; - - sprintf (name, "*%i", modnum); - // search the entities for one using modnum - for (i=0 ; inumfaces ; j++) - { - fn = mod->firstface + j; - face_entity[fn] = ent; - VectorCopy (origin, face_offset[fn]); - f = &g_pFaces[fn]; - if( f->dispinfo == -1 ) - { - w = WindingFromFace (f, origin ); - MakePatchForFace( fn, w ); - } - } - } - - if (num_degenerate_faces > 0) - { - qprintf("%d degenerate faces\n", num_degenerate_faces ); - } - - qprintf ("%i square feet [%.2f square inches]\n", (int)(totalarea/144), totalarea ); - - // make the displacement surface patches - StaticDispMgr()->MakePatches(); -} - -/* -======================================================================= - -SUBDIVIDE - -======================================================================= -*/ - - -//----------------------------------------------------------------------------- -// Purpose: does this surface take/emit light -//----------------------------------------------------------------------------- -bool PreventSubdivision( CPatch *patch ) -{ - dface_t *f = g_pFaces + patch->faceNumber; - texinfo_t *tx = &texinfo[f->texinfo]; - - if (tx->flags & SURF_NOCHOP) - return true; - - if (tx->flags & SURF_NOLIGHT && !(tx->flags & SURF_LIGHT)) - return true; - - return false; -} - - -//----------------------------------------------------------------------------- -// Purpose: subdivide the "parent" patch -//----------------------------------------------------------------------------- -int CreateChildPatch( int nParentIndex, winding_t *pWinding, float flArea, const Vector &vecCenter ) -{ - int nChildIndex = g_Patches.AddToTail(); - - CPatch *child = &g_Patches[nChildIndex]; - CPatch *parent = &g_Patches[nParentIndex]; - - // copy all elements of parent patch to children - *child = *parent; - - // Set up links - child->ndxNext = g_Patches.InvalidIndex(); - child->ndxNextParent = g_Patches.InvalidIndex(); - child->ndxNextClusterChild = g_Patches.InvalidIndex(); - child->child1 = g_Patches.InvalidIndex(); - child->child2 = g_Patches.InvalidIndex(); - child->parent = nParentIndex; - child->m_IterationKey = 0; - - child->winding = pWinding; - child->area = flArea; - - VectorCopy( vecCenter, child->origin ); - if ( ValidDispFace( g_pFaces + child->faceNumber ) ) - { - // shouldn't get here anymore!! - Msg( "SubdividePatch: Error - Should not be here!\n" ); - StaticDispMgr()->GetDispSurfNormal( child->faceNumber, child->origin, child->normal, true ); - } - else - { - GetPhongNormal( child->faceNumber, child->origin, child->normal ); - } - - child->planeDist = child->plane->dist; - WindingBounds(child->winding, child->mins, child->maxs); - - if ( !VectorCompare( child->baselight, vec3_origin ) ) - { - // don't check edges on surf lights - return nChildIndex; - } - - // Subdivide patch towards minchop if on the edge of the face - Vector total; - VectorSubtract( child->maxs, child->mins, total ); - VectorScale( total, child->luxscale, total ); - if ( child->chop > minchop && (total[0] < child->chop) && (total[1] < child->chop) && (total[2] < child->chop) ) - { - for ( int i=0; i<3; ++i ) - { - if ( (child->face_maxs[i] == child->maxs[i] || child->face_mins[i] == child->mins[i] ) - && total[i] > minchop ) - { - child->chop = max( minchop, child->chop / 2 ); - break; - } - } - } - - return nChildIndex; -} - - -//----------------------------------------------------------------------------- -// Purpose: subdivide the "parent" patch -//----------------------------------------------------------------------------- -void SubdividePatch( int ndxPatch ) -{ - winding_t *w, *o1, *o2; - Vector total; - Vector split; - vec_t dist; - vec_t widest = -1; - int i, widest_axis = -1; - bool bSubdivide = false; - - // get the current patch - CPatch *patch = &g_Patches.Element( ndxPatch ); - if ( !patch ) - return; - - // never subdivide sky patches - if ( patch->sky ) - return; - - // get the patch winding - w = patch->winding; - - // subdivide along the widest axis - VectorSubtract (patch->maxs, patch->mins, total); - VectorScale( total, patch->luxscale, total ); - for (i=0 ; i<3 ; i++) - { - if ( total[i] > widest ) - { - widest_axis = i; - widest = total[i]; - } - - if ( (total[i] >= patch->chop) && (total[i] >= minchop) ) - { - bSubdivide = true; - } - } - - if ((!bSubdivide) && widest_axis != -1) - { - // make more square - if (total[widest_axis] > total[(widest_axis + 1) % 3] * 2 && total[widest_axis] > total[(widest_axis + 2) % 3] * 2) - { - if (patch->chop > minchop) - { - bSubdivide = true; - patch->chop = max( minchop, patch->chop / 2 ); - } - } - } - - if ( !bSubdivide ) - return; - - // split the winding - VectorCopy (vec3_origin, split); - split[widest_axis] = 1; - dist = (patch->mins[widest_axis] + patch->maxs[widest_axis])*0.5f; - ClipWindingEpsilon (w, split, dist, ON_EPSILON, &o1, &o2); - - // calculate the area of the patches to see if they are "significant" - Vector center1, center2; - float area1 = WindingAreaAndBalancePoint( o1, center1 ); - float area2 = WindingAreaAndBalancePoint( o2, center2 ); - - if( area1 == 0 || area2 == 0 ) - { - Msg( "zero area child patch\n" ); - return; - } - - // create new child patches - int ndxChild1Patch = CreateChildPatch( ndxPatch, o1, area1, center1 ); - int ndxChild2Patch = CreateChildPatch( ndxPatch, o2, area2, center2 ); - - // FIXME: This could go into CreateChildPatch if child1, child2 were stored in the patch as child[0], child[1] - patch = &g_Patches.Element( ndxPatch ); - patch->child1 = ndxChild1Patch; - patch->child2 = ndxChild2Patch; - - SubdividePatch( ndxChild1Patch ); - SubdividePatch( ndxChild2Patch ); -} - - -/* -============= -SubdividePatches -============= -*/ -void SubdividePatches (void) -{ - unsigned i, num; - - if (numbounce == 0) - return; - - unsigned int uiPatchCount = g_Patches.Size(); - qprintf ("%i patches before subdivision\n", uiPatchCount); - - for (i = 0; i < uiPatchCount; i++) - { - CPatch *pCur = &g_Patches.Element( i ); - pCur->planeDist = pCur->plane->dist; - - pCur->ndxNextParent = faceParents.Element( pCur->faceNumber ); - faceParents[pCur->faceNumber] = pCur - g_Patches.Base(); - } - - for (i=0 ; i< uiPatchCount; i++) - { - CPatch *patch = &g_Patches.Element( i ); - patch->parent = -1; - if ( PreventSubdivision(patch) ) - continue; - - if (!do_fast) - { - if( g_pFaces[patch->faceNumber].dispinfo == -1 ) - { - SubdividePatch( i ); - } - else - { - StaticDispMgr()->SubdividePatch( i ); - } - } - } - - // fixup next pointers - for (i = 0; i < (unsigned)numfaces; i++) - { - g_FacePatches[i] = g_FacePatches.InvalidIndex(); - } - - uiPatchCount = g_Patches.Size(); - for (i = 0; i < uiPatchCount; i++) - { - CPatch *pCur = &g_Patches.Element( i ); - pCur->ndxNext = g_FacePatches.Element( pCur->faceNumber ); - g_FacePatches[pCur->faceNumber] = pCur - g_Patches.Base(); - -#if 0 - CPatch *prev; - prev = face_g_Patches[g_Patches[i].faceNumber]; - g_Patches[i].next = prev; - face_g_Patches[g_Patches[i].faceNumber] = &g_Patches[i]; -#endif - } - - // Cache off the leaf number: - // We have to do this after subdivision because some patches span leaves. - // (only the faces for model #0 are split by it's BSP which is what governs the PVS, and the leaves we're interested in) - // Sub models (1-255) are only split for the BSP that their model forms. - // When those patches are subdivided their origins can end up in a different leaf. - // The engine will split (clip) those faces at run time to the world BSP because the models - // are dynamic and can be moved. In the software renderer, they must be split exactly in order - // to sort per polygon. - for ( i = 0; i < uiPatchCount; i++ ) - { - g_Patches[i].clusterNumber = ClusterFromPoint( g_Patches[i].origin ); - - // - // test for point in solid space (can happen with detail and displacement surfaces) - // - if( g_Patches[i].clusterNumber == -1 ) - { - for( int j = 0; j < g_Patches[i].winding->numpoints; j++ ) - { - int clusterNumber = ClusterFromPoint( g_Patches[i].winding->p[j] ); - if( clusterNumber != -1 ) - { - g_Patches[i].clusterNumber = clusterNumber; - break; - } - } - } - } - - // build the list of patches that need to be lit - for ( num = 0; num < uiPatchCount; num++ ) - { - // do them in reverse order - i = uiPatchCount - num - 1; - - // skip patches with children - CPatch *pCur = &g_Patches.Element( i ); - if( pCur->child1 == g_Patches.InvalidIndex() ) - { - if( pCur->clusterNumber != - 1 ) - { - pCur->ndxNextClusterChild = clusterChildren.Element( pCur->clusterNumber ); - clusterChildren[pCur->clusterNumber] = pCur - g_Patches.Base(); - } - } - -#if 0 - if (g_Patches[i].child1 == g_Patches.InvalidIndex() ) - { - if( g_Patches[i].clusterNumber != -1 ) - { - g_Patches[i].nextclusterchild = cluster_children[g_Patches[i].clusterNumber]; - cluster_children[g_Patches[i].clusterNumber] = &g_Patches[i]; - } - } -#endif - } - - qprintf ("%i patches after subdivision\n", uiPatchCount); -} - - -//===================================================================== - -/* -============= -MakeScales - - This is the primary time sink. - It can be run multi threaded. -============= -*/ -int total_transfer; -int max_transfer; - - -//----------------------------------------------------------------------------- -// Purpose: Computes the form factor from a polygon patch to a differential patch -// using formula 81 of Philip Dutre's Global Illumination Compendium, -// phil@graphics.cornell.edu, http://www.graphics.cornell.edu/~phil/GI/ -//----------------------------------------------------------------------------- -float FormFactorPolyToDiff ( CPatch *pPolygon, CPatch* pDifferential ) -{ - winding_t *pWinding = pPolygon->winding; - - float flFormFactor = 0.0f; - - for ( int iPoint = 0; iPoint < pWinding->numpoints; iPoint++ ) - { - int iNextPoint = ( iPoint < pWinding->numpoints - 1 ) ? iPoint + 1 : 0; - - Vector vGammaVector, vVector1, vVector2; - VectorSubtract( pWinding->p[ iPoint ], pDifferential->origin, vVector1 ); - VectorSubtract( pWinding->p[ iNextPoint ], pDifferential->origin, vVector2 ); - VectorNormalize( vVector1 ); - VectorNormalize( vVector2 ); - CrossProduct( vVector1, vVector2, vGammaVector ); - float flSinAlpha = VectorNormalize( vGammaVector ); - if (flSinAlpha < -1.0f || flSinAlpha > 1.0f) - return 0.0f; - vGammaVector *= asin( flSinAlpha ); - - flFormFactor += DotProduct( vGammaVector, pDifferential->normal ); - } - - flFormFactor *= ( 0.5f / pPolygon->area ); // divide by pi later, multiply by area later - - return flFormFactor; -} - - -//----------------------------------------------------------------------------- -// Purpose: Computes the form factor from a differential element to a differential -// element. This is okay when the distance between patches is 5 times -// greater than patch size. Lecture slides by Pat Hanrahan, -// http://graphics.stanford.edu/courses/cs348b-00/lectures/lecture17/radiosity.2.pdf -//----------------------------------------------------------------------------- -float FormFactorDiffToDiff ( CPatch *pDiff1, CPatch* pDiff2 ) -{ - Vector vDelta; - VectorSubtract( pDiff1->origin, pDiff2->origin, vDelta ); - float flLength = VectorNormalize( vDelta ); - - return -DotProduct( vDelta, pDiff1->normal ) * DotProduct( vDelta, pDiff2->normal ) / ( flLength * flLength ); -} - - - -void MakeTransfer( int ndxPatch1, int ndxPatch2, transfer_t *all_transfers ) -//void MakeTransfer (CPatch *patch, CPatch *patch2, transfer_t *all_transfers ) -{ - Vector delta; - vec_t scale; - float trans; - transfer_t *transfer; - - // - // get patches - // - if( ndxPatch1 == g_Patches.InvalidIndex() || ndxPatch2 == g_Patches.InvalidIndex() ) - return; - - CPatch *pPatch1 = &g_Patches.Element( ndxPatch1 ); - CPatch *pPatch2 = &g_Patches.Element( ndxPatch2 ); - - if (IsSky( &g_pFaces[ pPatch2->faceNumber ] ) ) - return; - - // overflow check! - if ( pPatch1->numtransfers >= MAX_PATCHES) - { - return; - } - - // hack for patch areas that area <= 0 (degenerate) - if ( pPatch2->area <= 0) - { - return; - } - - transfer = &all_transfers[pPatch1->numtransfers]; - - scale = FormFactorDiffToDiff( pPatch2, pPatch1 ); - - // patch normals may be > 90 due to smoothing groups - if (scale <= 0) - { - //Msg("scale <= 0\n"); - return; - } - - // Test 5 times rule - Vector vDelta; - VectorSubtract( pPatch1->origin, pPatch2->origin, vDelta ); - float flThreshold = ( M_PI * 0.04 ) * DotProduct( vDelta, vDelta ); - - if (flThreshold < pPatch2->area) - { - scale = FormFactorPolyToDiff( pPatch2, pPatch1 ); - if (scale <= 0.0) - return; - } - - trans = (pPatch2->area*scale); - - if (trans <= TRANSFER_EPSILON) - { - return; - } - - transfer->patch = pPatch2 - g_Patches.Base(); - - // FIXME: why is this not trans? - transfer->transfer = trans; - -#if 0 - // DEBUG! Dump patches and transfer connection for displacements. This creates a lot of data, so only - // use it when you really want it - that is why it is #if-ed out. - if ( g_bDumpPatches ) - { - if ( !pFpTrans ) - { - pFpTrans = g_pFileSystem->Open( "trans.txt", "w" ); - } - Vector light = pPatch1->totallight.light[0] + pPatch1->directlight; - WriteWinding( pFpTrans, pPatch1->winding, light ); - light = pPatch2->totallight.light[0] + pPatch2->directlight; - WriteWinding( pFpTrans, pPatch2->winding, light ); - WriteLine( pFpTrans, pPatch1->origin, pPatch2->origin, Vector( 255, 0, 255 ) ); - } -#endif - - pPatch1->numtransfers++; -} - - -void MakeScales ( int ndxPatch, transfer_t *all_transfers ) -{ - int j; - float total; - transfer_t *t, *t2; - total = 0; - - if( ndxPatch == g_Patches.InvalidIndex() ) - return; - CPatch *patch = &g_Patches.Element( ndxPatch ); - - // copy the transfers out - if (patch->numtransfers) - { - if (patch->numtransfers > max_transfer) - { - max_transfer = patch->numtransfers; - } - - - patch->transfers = ( transfer_t* )calloc (1, patch->numtransfers * sizeof(transfer_t)); - if (!patch->transfers) - Error ("Memory allocation failure"); - - // get total transfer energy - t2 = all_transfers; - - // overflow check! - for (j=0 ; jnumtransfers ; j++, t2++) - { - total += t2->transfer; - } - - // the total transfer should be PI, but we need to correct errors due to overlaping surfaces - if (total > M_PI) - total = 1.0f/total; - else - total = 1.0f/M_PI; - - t = patch->transfers; - t2 = all_transfers; - for (j=0 ; jnumtransfers ; j++, t++, t2++) - { - t->transfer = t2->transfer*total; - t->patch = t2->patch; - } - if (patch->numtransfers > max_transfer) - { - max_transfer = patch->numtransfers; - } - } - else - { - // Error - patch has no transfers - // patch->totallight[2] = 255; - } - - ThreadLock (); - total_transfer += patch->numtransfers; - ThreadUnlock (); -} - -/* -============= -WriteWorld -============= -*/ -void WriteWorld (char *name, int iBump) -{ - unsigned j; - FileHandle_t out; - CPatch *patch; - - out = g_pFileSystem->Open( name, "w" ); - if (!out) - Error ("Couldn't open %s", name); - - unsigned int uiPatchCount = g_Patches.Size(); - for (j=0; jchild1 != g_Patches.InvalidIndex() ) - continue; - - if( patch->clusterNumber == -1 ) - { - Vector vGreen; - VectorClear( vGreen ); - vGreen[1] = 256.0f; - WriteWinding( out, patch->winding, vGreen ); - } - else - { - Vector light = patch->totallight.light[iBump] + patch->directlight; - WriteWinding( out, patch->winding, light ); - if( bDumpNormals ) - { - WriteNormal( out, patch->origin, patch->plane->normal, 15.0f, patch->plane->normal * 255.0f ); - } - } - } - - g_pFileSystem->Close( out ); -} - -void WriteRTEnv (char *name) -{ - FileHandle_t out; - - out = g_pFileSystem->Open( name, "w" ); - if (!out) - Error ("Couldn't open %s", name); - - winding_t *triw = AllocWinding( 3 ); - triw->numpoints = 3; - - for( int i = 0; i < g_RtEnv.OptimizedTriangleList.Size(); i++ ) - { - triw->p[0] = g_RtEnv.OptimizedTriangleList[i].Vertex( 0); - triw->p[1] = g_RtEnv.OptimizedTriangleList[i].Vertex( 1); - triw->p[2] = g_RtEnv.OptimizedTriangleList[i].Vertex( 2); - int id = g_RtEnv.OptimizedTriangleList[i].m_Data.m_GeometryData.m_nTriangleID; - Vector color(0, 0, 0); - if (id & TRACE_ID_OPAQUE) color.Init(0, 255, 0); - if (id & TRACE_ID_SKY) color.Init(0, 0, 255); - if (id & TRACE_ID_STATICPROP) color.Init(255, 0, 0); - WriteWinding(out, triw, color); - } - FreeWinding(triw); - - g_pFileSystem->Close( out ); -} - -void WriteWinding (FileHandle_t out, winding_t *w, Vector& color ) -{ - int i; - - CmdLib_FPrintf (out, "%i\n", w->numpoints); - for (i=0 ; inumpoints ; i++) - { - CmdLib_FPrintf (out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", - w->p[i][0], - w->p[i][1], - w->p[i][2], - color[ 0 ] / 256, - color[ 1 ] / 256, - color[ 2 ] / 256 ); - } -} - - -void WriteNormal( FileHandle_t out, Vector const &nPos, Vector const &nDir, - float length, Vector const &color ) -{ - CmdLib_FPrintf( out, "2\n" ); - CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", - nPos.x, nPos.y, nPos.z, - color.x / 256, color.y / 256, color.z / 256 ); - CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", - nPos.x + ( nDir.x * length ), - nPos.y + ( nDir.y * length ), - nPos.z + ( nDir.z * length ), - color.x / 256, color.y / 256, color.z / 256 ); -} - -void WriteLine( FileHandle_t out, const Vector &vecPos1, const Vector &vecPos2, const Vector &color ) -{ - CmdLib_FPrintf( out, "2\n" ); - CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", - vecPos1.x, vecPos1.y, vecPos1.z, - color.x / 256, color.y / 256, color.z / 256 ); - CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", - vecPos2.x, vecPos2.y, vecPos2.z, - color.x / 256, color.y / 256, color.z / 256 ); -} - -void WriteTrace( const char *pFileName, const FourRays &rays, const RayTracingResult& result ) -{ - FileHandle_t out; - - out = g_pFileSystem->Open( pFileName, "a" ); - if (!out) - Error ("Couldn't open %s", pFileName); - - // Draws rays - for ( int i = 0; i < 4; ++i ) - { - Vector vecOrigin = rays.origin.Vec(i); - Vector vecEnd = rays.direction.Vec(i); - VectorNormalize( vecEnd ); - vecEnd *= SubFloat( result.HitDistance, i ); - vecEnd += vecOrigin; - WriteLine( out, vecOrigin, vecEnd, Vector( 256, 0, 0 ) ); - WriteNormal( out, vecEnd, result.surface_normal.Vec(i), 10.0f, Vector( 256, 265, 0 ) ); - } - - g_pFileSystem->Close( out ); -} - - -/* -============= -CollectLight -============= -*/ -// patch's totallight += new light received to each patch -// patch's emitlight = addlight (newly received light from GatherLight) -// patch's addlight = 0 -// pull received light from children. -void CollectLight( Vector& total ) -{ - int i, j; - CPatch *patch; - - VectorFill( total, 0 ); - - // process patches in reverse order so that children are processed before their parents - unsigned int uiPatchCount = g_Patches.Size(); - for( i = uiPatchCount - 1; i >= 0; i-- ) - { - patch = &g_Patches.Element( i ); - int normalCount = patch->needsBumpmap ? NUM_BUMP_VECTS+1 : 1; - // sky's never collect light, it is just dropped - if (patch->sky) - { - VectorFill( emitlight[ i ], 0 ); - } - else if ( patch->child1 == g_Patches.InvalidIndex() ) - { - // This is a leaf node. - for ( j = 0; j < normalCount; j++ ) - { - VectorAdd( patch->totallight.light[j], addlight[i].light[j], patch->totallight.light[j] ); - } - VectorCopy( addlight[i].light[0], emitlight[i] ); - VectorAdd( total, emitlight[i], total ); - } - else - { - // This is an interior node. - // Pull received light from children. - float s1, s2; - CPatch *child1; - CPatch *child2; - - child1 = &g_Patches[patch->child1]; - child2 = &g_Patches[patch->child2]; - - // BUG: This doesn't do anything? - if ((int)patch->area != (int)(child1->area + child2->area)) - s1 = 0; - - s1 = child1->area / (child1->area + child2->area); - s2 = child2->area / (child1->area + child2->area); - - // patch->totallight = s1 * child1->totallight + s2 * child2->totallight - for ( j = 0; j < normalCount; j++ ) - { - VectorScale( child1->totallight.light[j], s1, patch->totallight.light[j] ); - VectorMA( patch->totallight.light[j], s2, child2->totallight.light[j], patch->totallight.light[j] ); - } - - // patch->emitlight = s1 * child1->emitlight + s2 * child2->emitlight - VectorScale( emitlight[patch->child1], s1, emitlight[i] ); - VectorMA( emitlight[i], s2, emitlight[patch->child2], emitlight[i] ); - } - for ( j = 0; j < NUM_BUMP_VECTS+1; j++ ) - { - VectorFill( addlight[ i ].light[j], 0 ); - } - } -} - -/* -============= -GatherLight - -Get light from other patches - Run multi-threaded -============= -*/ - -#ifdef _WIN32 -#pragma warning (disable:4701) -#endif - -extern void GetBumpNormals( const float* sVect, const float* tVect, const Vector& flatNormal, - const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] ); - - -void PreGetBumpNormalsForDisp( texinfo_t *pTexinfo, Vector &vecU, Vector &vecV, Vector &vecNormal ) -{ - Vector vecTexU( pTexinfo->textureVecsTexelsPerWorldUnits[0][0], pTexinfo->textureVecsTexelsPerWorldUnits[0][1], pTexinfo->textureVecsTexelsPerWorldUnits[0][2] ); - Vector vecTexV( pTexinfo->textureVecsTexelsPerWorldUnits[1][0], pTexinfo->textureVecsTexelsPerWorldUnits[1][1], pTexinfo->textureVecsTexelsPerWorldUnits[1][2] ); - Vector vecLightU( pTexinfo->lightmapVecsLuxelsPerWorldUnits[0][0], pTexinfo->lightmapVecsLuxelsPerWorldUnits[0][1], pTexinfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); - Vector vecLightV( pTexinfo->lightmapVecsLuxelsPerWorldUnits[1][0], pTexinfo->lightmapVecsLuxelsPerWorldUnits[1][1], pTexinfo->lightmapVecsLuxelsPerWorldUnits[1][2] ); - - VectorNormalize( vecTexU ); - VectorNormalize( vecTexV ); - VectorNormalize( vecLightU ); - VectorNormalize( vecLightV ); - - bool bDoConversion = false; - if ( fabs( vecTexU.Dot( vecLightU ) ) < 0.999f ) - { - bDoConversion = true; - } - - if ( fabs( vecTexV.Dot( vecLightV ) ) < 0.999f ) - { - bDoConversion = true; - } - - if ( bDoConversion ) - { - matrix3x4_t matTex( vecTexU, vecTexV, vecNormal, vec3_origin ); - matrix3x4_t matLight( vecLightU, vecLightV, vecNormal, vec3_origin ); - matrix3x4_t matTmp; - ConcatTransforms ( matLight, matTex, matTmp ); - MatrixGetColumn( matTmp, 0, vecU ); - MatrixGetColumn( matTmp, 1, vecV ); - MatrixGetColumn( matTmp, 2, vecNormal ); - - Assert( fabs( vecTexU.Dot( vecTexV ) ) <= 0.001f ); - return; - } - - vecU = vecTexU; - vecV = vecTexV; -} - -void GatherLight (int threadnum, void *pUserData) -{ - int i, j, k; - transfer_t *trans; - int num; - CPatch *patch; - Vector sum, v; - - while (1) - { - j = GetThreadWork (); - if (j == -1) - break; - - patch = &g_Patches[j]; - - trans = patch->transfers; - num = patch->numtransfers; - if ( patch->needsBumpmap ) - { - Vector delta; - Vector bumpSum[NUM_BUMP_VECTS+1]; - Vector normals[NUM_BUMP_VECTS+1]; - - // Disps - bool bDisp = ( g_pFaces[patch->faceNumber].dispinfo != -1 ); - if ( bDisp ) - { - normals[0] = patch->normal; - texinfo_t *pTexinfo = &texinfo[g_pFaces[patch->faceNumber].texinfo]; - Vector vecTexU, vecTexV; - PreGetBumpNormalsForDisp( pTexinfo, vecTexU, vecTexV, normals[0] ); - - // use facenormal along with the smooth normal to build the three bump map vectors - GetBumpNormals( vecTexU, vecTexV, normals[0], normals[0], &normals[1] ); - } - else - { - GetPhongNormal( patch->faceNumber, patch->origin, normals[0] ); - - texinfo_t *pTexinfo = &texinfo[g_pFaces[patch->faceNumber].texinfo]; - // use facenormal along with the smooth normal to build the three bump map vectors - GetBumpNormals( pTexinfo->textureVecsTexelsPerWorldUnits[0], - pTexinfo->textureVecsTexelsPerWorldUnits[1], patch->normal, - normals[0], &normals[1] ); - } - - // force the base lightmap to use the flat normal instead of the phong normal - // FIXME: why does the patch not use the phong normal? - normals[0] = patch->normal; - - for ( i = 0; i < NUM_BUMP_VECTS+1; i++ ) - { - VectorFill( bumpSum[i], 0 ); - } - - float dot; - for (k=0 ; kpatch]; - - // get vector to other patch - VectorSubtract (patch2->origin, patch->origin, delta); - VectorNormalize (delta); - // find light emitted from other patch - for(i=0; i<3; i++) - { - v[i] = emitlight[trans->patch][i] * patch2->reflectivity[i]; - } - // remove normal already factored into transfer steradian - float scale = 1.0f / DotProduct (delta, patch->normal); - VectorScale( v, trans->transfer * scale, v ); - - Vector bumpTransfer; - for ( i = 0; i < NUM_BUMP_VECTS+1; i++ ) - { - dot = DotProduct( delta, normals[i] ); - if ( dot <= 0 ) - { -// Assert( i > 0 ); // if this hits, then the transfer shouldn't be here. It doesn't face the flat normal of this face! - continue; - } - bumpTransfer = v * dot; - VectorAdd( bumpSum[i], bumpTransfer, bumpSum[i] ); - } - } - for ( i = 0; i < NUM_BUMP_VECTS+1; i++ ) - { - VectorCopy( bumpSum[i], addlight[j].light[i] ); - } - } - else - { - VectorFill( sum, 0 ); - for (k=0 ; kpatch][i] * g_Patches[trans->patch].reflectivity[i]; - } - VectorScale( v, trans->transfer, v ); - VectorAdd( sum, v, sum ); - } - VectorCopy( sum, addlight[j].light[0] ); - } - } -} - -#ifdef _WIN32 -#pragma warning (default:4701) -#endif - - -/* -============= -BounceLight -============= -*/ -void BounceLight (void) -{ - unsigned i; - Vector added; - char name[64]; - qboolean bouncing = numbounce > 0; - - unsigned int uiPatchCount = g_Patches.Size(); - for (i=0 ; iOpen( "lightemit.txt", "w" ); - - unsigned int uiPatchCount = g_Patches.Size(); - for (i=0 ; iClose( dFp ); - - for (i=0; iemitlight to receiver->addlight - unsigned int uiPatchCount = g_Patches.Size(); - RunThreadsOn (uiPatchCount, true, GatherLight); - // move newly received light (addlight) to light to be sent out (emitlight) - // start at children and pull light up to parents - // light is always received to leaf patches - CollectLight( added ); - - qprintf ("\tBounce #%i added RGB(%.0f, %.0f, %.0f)\n", i+1, added[0], added[1], added[2] ); - - if ( i+1 == numbounce || (added[0] < 1.0 && added[1] < 1.0 && added[2] < 1.0) ) - bouncing = false; - - i++; - if ( g_bDumpPatches && !bouncing && i != 1) - { - sprintf (name, "bounce%i.txt", i); - WriteWorld (name, 0); - } - } -} - - - -//----------------------------------------------------------------------------- -// Purpose: Counts the number of clusters in a map with no visibility -// Output : int -//----------------------------------------------------------------------------- -int CountClusters( void ) -{ - int clusterCount = 0; - - for ( int i = 0; i < numleafs; i++ ) - { - if ( dleafs[i].cluster > clusterCount ) - clusterCount = dleafs[i].cluster; - } - - return clusterCount + 1; -} - - -/* -============= -RadWorld -============= -*/ -void RadWorld_Start() -{ - unsigned i; - - if (luxeldensity < 1.0) - { - // Remember the old lightmap vectors. - float oldLightmapVecs[MAX_MAP_TEXINFO][2][4]; - for (i = 0; i < texinfo.Count(); i++) - { - for( int j=0; j < 2; j++ ) - { - for( int k=0; k < 3; k++ ) - { - oldLightmapVecs[i][j][k] = texinfo[i].lightmapVecsLuxelsPerWorldUnits[j][k]; - } - } - } - - // rescale luxels to be no denser than "luxeldensity" - for (i = 0; i < texinfo.Count(); i++) - { - texinfo_t *tx = &texinfo[i]; - - for (int j = 0; j < 2; j++ ) - { - Vector tmp( tx->lightmapVecsLuxelsPerWorldUnits[j][0], tx->lightmapVecsLuxelsPerWorldUnits[j][1], tx->lightmapVecsLuxelsPerWorldUnits[j][2] ); - float scale = VectorNormalize( tmp ); - // only rescale them if the current scale is "tighter" than the desired scale - // FIXME: since this writes out to the BSP file every run, once it's set high it can't be reset - // to a lower value. - if (fabs( scale ) > luxeldensity) - { - if (scale < 0) - { - scale = -luxeldensity; - } - else - { - scale = luxeldensity; - } - VectorScale( tmp, scale, tmp ); - tx->lightmapVecsLuxelsPerWorldUnits[j][0] = tmp.x; - tx->lightmapVecsLuxelsPerWorldUnits[j][1] = tmp.y; - tx->lightmapVecsLuxelsPerWorldUnits[j][2] = tmp.z; - } - } - } - - UpdateAllFaceLightmapExtents(); - } - - MakeParents (0, -1); - - BuildClusterTable(); - - // turn each face into a single patch - MakePatches (); - PairEdges (); - - // store the vertex normals calculated in PairEdges - // so that the can be written to the bsp file for - // use in the engine - SaveVertexNormals(); - - // subdivide patches to a maximum dimension - SubdividePatches (); - - // add displacement faces to cluster table - AddDispsToClusterTable(); - - // create directlights out of patches and lights - CreateDirectLights (); - - // set up sky cameras - ProcessSkyCameras(); -} - - -// This function should fill in the indices into g_pFaces[] for the faces -// with displacements that touch the specified leaf. -void STUB_GetDisplacementsTouchingLeaf( int iLeaf, CUtlVector &dispFaces ) -{ -} - - -void BuildFacesVisibleToLights( bool bAllVisible ) -{ - g_FacesVisibleToLights.SetSize( numfaces/8 + 1 ); - - if( bAllVisible ) - { - memset( g_FacesVisibleToLights.Base(), 0xFF, g_FacesVisibleToLights.Count() ); - return; - } - - // First merge all the light PVSes. - CUtlVector aggregate; - aggregate.SetSize( (dvis->numclusters/8) + 1 ); - memset( aggregate.Base(), 0, aggregate.Count() ); - - int nDWords = aggregate.Count() / 4; - int nBytes = aggregate.Count() - nDWords*4; - - for( directlight_t *dl = activelights; dl != NULL; dl = dl->next ) - { - byte *pIn = dl->pvs; - byte *pOut = aggregate.Base(); - for( int iDWord=0; iDWord < nDWords; iDWord++ ) - { - *((unsigned long*)pOut) |= *((unsigned long*)pIn); - pIn += 4; - pOut += 4; - } - - for( int iByte=0; iByte < nBytes; iByte++ ) - { - *pOut |= *pIn; - ++pOut; - ++pIn; - } - } - - - // Now tag any faces that are visible to this monster PVS. - for( int iCluster=0; iCluster < dvis->numclusters; iCluster++ ) - { - if( g_ClusterLeaves[iCluster].leafCount ) - { - if( aggregate[iCluster>>3] & (1 << (iCluster & 7)) ) - { - for ( int i = 0; i < g_ClusterLeaves[iCluster].leafCount; i++ ) - { - int iLeaf = g_ClusterLeaves[iCluster].leafs[i]; - - // Tag all the faces. - int iFace; - for( iFace=0; iFace < dleafs[iLeaf].numleaffaces; iFace++ ) - { - int index = dleafs[iLeaf].firstleafface + iFace; - index = dleaffaces[index]; - - assert( index < numfaces ); - g_FacesVisibleToLights[index >> 3] |= (1 << (index & 7)); - } - - // Fill in STUB_GetDisplacementsTouchingLeaf when it's available - // so displacements get relit. - CUtlVector dispFaces; - STUB_GetDisplacementsTouchingLeaf( iLeaf, dispFaces ); - for( iFace=0; iFace < dispFaces.Count(); iFace++ ) - { - int index = dispFaces[iFace]; - g_FacesVisibleToLights[index >> 3] |= (1 << (index & 7)); - } - } - } - } - } - - // For stats.. figure out how many faces it's going to touch. - int nFacesToProcess = 0; - for( int i=0; i < numfaces; i++ ) - { - if( g_FacesVisibleToLights[i>>3] & (1 << (i & 7)) ) - ++nFacesToProcess; - } -} - - - -void MakeAllScales (void) -{ - // determine visibility between patches - BuildVisMatrix (); - - // release visibility matrix - FreeVisMatrix (); - - Msg("transfers %d, max %d\n", total_transfer, max_transfer ); - - qprintf ("transfer lists: %5.1f megs\n" - , (float)total_transfer * sizeof(transfer_t) / (1024*1024)); -} - - -// Helper function. This can be useful to visualize the world and faces and see which face -// corresponds to which dface. -#if 0 - #include "iscratchpad3d.h" - void ScratchPad_DrawWorld() - { - IScratchPad3D *pPad = ScratchPad3D_Create(); - pPad->SetAutoFlush( false ); - - for ( int i=0; i < numfaces; i++ ) - { - dface_t *f = &g_pFaces[i]; - - // Draw the face's outline, then put text for its face index on it too. - CUtlVector points; - for ( int iEdge = 0; iEdge < f->numedges; iEdge++ ) - { - int v; - int se = dsurfedges[f->firstedge + iEdge]; - if ( se < 0 ) - v = dedges[-se].v[1]; - else - v = dedges[se].v[0]; - - dvertex_t *dv = &dvertexes[v]; - points.AddToTail( dv->point ); - } - - // Draw the outline. - Vector vCenter( 0, 0, 0 ); - for ( iEdge=0; iEdge < points.Count(); iEdge++ ) - { - pPad->DrawLine( CSPVert( points[iEdge] ), CSPVert( points[(iEdge+1)%points.Count()] ) ); - vCenter += points[iEdge]; - } - vCenter /= points.Count(); - - // Draw the text. - char str[512]; - Q_snprintf( str, sizeof( str ), "%d", i ); - - CTextParams params; - - params.m_bCentered = true; - params.m_bOutline = true; - params.m_flLetterWidth = 2; - params.m_vColor.Init( 1, 0, 0 ); - - VectorAngles( dplanes[f->planenum].normal, params.m_vAngles ); - params.m_bTwoSided = true; - - params.m_vPos = vCenter; - - pPad->DrawText( str, params ); - } - - pPad->Release(); - } -#endif - - -bool RadWorld_Go() -{ - g_iCurFace = 0; - - InitMacroTexture( source ); - - if( g_pIncremental ) - { - g_pIncremental->PrepareForLighting(); - - // Cull out faces that aren't visible to any of the lights that we're updating with. - BuildFacesVisibleToLights( false ); - } - else - { - // Mark all faces visible.. when not doing incremental lighting, it's highly - // likely that all faces are going to be touched by at least one light so don't - // waste time here. - BuildFacesVisibleToLights( true ); - } - - // build initial facelights - if (g_bUseMPI) - { - // RunThreadsOnIndividual (numfaces, true, BuildFacelights); - RunMPIBuildFacelights(); - } - else - { - RunThreadsOnIndividual (numfaces, true, BuildFacelights); - } - - // Was the process interrupted? - if( g_pIncremental && (g_iCurFace != numfaces) ) - return false; - - // Figure out the offset into lightmap data for each face. - PrecompLightmapOffsets(); - - // If we're doing incremental lighting, stop here. - if( g_pIncremental ) - { - g_pIncremental->Finalize(); - } - else - { - // free up the direct lights now that we have facelights - ExportDirectLightsToWorldLights(); - - if ( g_bDumpPatches ) - { - for( int iBump = 0; iBump < 4; ++iBump ) - { - char szName[64]; - sprintf ( szName, "bounce0_%d.txt", iBump ); - WriteWorld( szName, iBump ); - } - } - - if (numbounce > 0) - { - // allocate memory for emitlight/addlight - emitlight.SetSize( g_Patches.Size() ); - memset( emitlight.Base(), 0, g_Patches.Size() * sizeof( Vector ) ); - addlight.SetSize( g_Patches.Size() ); - memset( addlight.Base(), 0, g_Patches.Size() * sizeof( bumplights_t ) ); - - MakeAllScales (); - - // spread light around - BounceLight (); - } - - // - // displacement surface luxel accumulation (make threaded!!!) - // - StaticDispMgr()->StartTimer( "Build Patch/Sample Hash Table(s)....." ); - StaticDispMgr()->InsertSamplesDataIntoHashTable(); - StaticDispMgr()->InsertPatchSampleDataIntoHashTable(); - StaticDispMgr()->EndTimer(); - - // blend bounced light into direct light and save - VMPI_SetCurrentStage( "FinalLightFace" ); - if ( !g_bUseMPI || g_bMPIMaster ) - RunThreadsOnIndividual (numfaces, true, FinalLightFace); - - // Distribute the lighting data to workers. - VMPI_DistributeLightData(); - - Msg("FinalLightFace Done\n"); fflush(stdout); - } - - return true; -} - -// declare the sample file pointer -- the whole debug print system should -// be reworked at some point!! -FileHandle_t pFileSamples[4][4]; - -void LoadPhysicsDLL( void ) -{ - PhysicsDLLPath( "VPHYSICS.DLL" ); -} - - -void InitDumpPatchesFiles() -{ - for( int iStyle = 0; iStyle < 4; ++iStyle ) - { - for ( int iBump = 0; iBump < 4; ++iBump ) - { - char szFilename[MAX_PATH]; - sprintf( szFilename, "samples_style%d_bump%d.txt", iStyle, iBump ); - pFileSamples[iStyle][iBump] = g_pFileSystem->Open( szFilename, "w" ); - if( !pFileSamples[iStyle][iBump] ) - { - Error( "Can't open %s for -dump.\n", szFilename ); - } - } - } -} - - -void VRAD_LoadBSP( char const *pFilename ) -{ - ThreadSetDefault (); - - g_flStartTime = Plat_FloatTime(); - - if( g_bLowPriority ) - { - SetLowPriority(); - } - - strcpy( level_name, source ); - - // This must come after InitFileSystem because the file system pointer might change. - if ( g_bDumpPatches ) - InitDumpPatchesFiles(); - - // This part is just for VMPI. VMPI's file system needs the basedir in front of all filenames, - // so we prepend qdir here. - strcpy( source, ExpandPath( source ) ); - - if ( !g_bUseMPI ) - { - // Setup the logfile. - char logFile[512]; - _snprintf( logFile, sizeof(logFile), "%s.log", source ); - SetSpewFunctionLogFile( logFile ); - } - - LoadPhysicsDLL(); - - // Set the required global lights filename and try looking in qproject - strcpy( global_lights, "lights.rad" ); - if ( !g_pFileSystem->FileExists( global_lights ) ) - { - // Otherwise, try looking in the BIN directory from which we were run from - Msg( "Could not find lights.rad in %s.\nTrying VRAD BIN directory instead...\n", - global_lights ); - GetModuleFileName( NULL, global_lights, sizeof( global_lights ) ); - Q_ExtractFilePath( global_lights, global_lights, sizeof( global_lights ) ); - strcat( global_lights, "lights.rad" ); - } - - // Set the optional level specific lights filename - strcpy( level_lights, source ); - - Q_DefaultExtension( level_lights, ".rad", sizeof( level_lights ) ); - if ( !g_pFileSystem->FileExists( level_lights ) ) - *level_lights = 0; - - ReadLightFile(global_lights); // Required - if ( *designer_lights ) ReadLightFile(designer_lights); // Command-line - if ( *level_lights ) ReadLightFile(level_lights); // Optional & implied - - strcpy(incrementfile, source); - Q_DefaultExtension(incrementfile, ".r0", sizeof(incrementfile)); - Q_DefaultExtension(source, ".bsp", sizeof( source )); - - GetPlatformMapPath( source, platformPath, 0, MAX_PATH ); - - Msg( "Loading %s\n", platformPath ); - VMPI_SetCurrentStage( "LoadBSPFile" ); - LoadBSPFile (platformPath); - - // now, set whether or not static prop lighting is present - if (g_bStaticPropLighting) - g_LevelFlags |= g_bHDR? LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_HDR : LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_NONHDR; - else - { - g_LevelFlags &= ~( LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_HDR | LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_NONHDR ); - } - - // now, we need to set our face ptr depending upon hdr, and if hdr, init it - if (g_bHDR) - { - g_pFaces = dfaces_hdr; - if (numfaces_hdr==0) - { - numfaces_hdr = numfaces; - memcpy( dfaces_hdr, dfaces, numfaces*sizeof(dfaces[0]) ); - } - } - else - { - g_pFaces = dfaces; - } - - - ParseEntities (); - ExtractBrushEntityShadowCasters(); - - StaticPropMgr()->Init(); - StaticDispMgr()->Init(); - - if (!visdatasize) - { - Msg("No vis information, direct lighting only.\n"); - numbounce = 0; - ambient[0] = ambient[1] = ambient[2] = 0.1f; - dvis->numclusters = CountClusters(); - } - - // - // patches and referencing data (ensure capacity) - // - // TODO: change the maxes to the amount from the bsp!! - // -// g_Patches.EnsureCapacity( MAX_PATCHES ); - - g_FacePatches.SetSize( MAX_MAP_FACES ); - faceParents.SetSize( MAX_MAP_FACES ); - clusterChildren.SetSize( MAX_MAP_CLUSTERS ); - - int ndx; - for ( ndx = 0; ndx < MAX_MAP_FACES; ndx++ ) - { - g_FacePatches[ndx] = g_FacePatches.InvalidIndex(); - faceParents[ndx] = faceParents.InvalidIndex(); - } - - for ( ndx = 0; ndx < MAX_MAP_CLUSTERS; ndx++ ) - { - clusterChildren[ndx] = clusterChildren.InvalidIndex(); - } - - // Setup ray tracer - AddBrushesForRayTrace(); - StaticDispMgr()->AddPolysForRayTrace(); - StaticPropMgr()->AddPolysForRayTrace(); - - // Dump raytracer for glview - if ( g_bDumpRtEnv ) - WriteRTEnv("trace.txt"); - - // Build acceleration structure - printf ( "Setting up ray-trace acceleration structure... "); - float start = Plat_FloatTime(); - g_RtEnv.SetupAccelerationStructure(); - float end = Plat_FloatTime(); - printf ( "Done (%.2f seconds)\n", end-start ); - -#if 0 // To test only k-d build - exit(0); -#endif - - RadWorld_Start(); - - // Setup incremental lighting. - if( g_pIncremental ) - { - if( !g_pIncremental->Init( source, incrementfile ) ) - { - Error( "Unable to load incremental lighting file in %s.\n", incrementfile ); - return; - } - } -} - - -void VRAD_ComputeOtherLighting() -{ - // Compute lighting for the bsp file - if ( !g_bNoDetailLighting ) - { - ComputeDetailPropLighting( THREADINDEX_MAIN ); - } - - ComputePerLeafAmbientLighting(); - - // bake the static props high quality vertex lighting into the bsp - if ( !do_fast && g_bStaticPropLighting ) - { - StaticPropMgr()->ComputeLighting( THREADINDEX_MAIN ); - } -} - -extern void CloseDispLuxels(); - -void VRAD_Finish() -{ - Msg( "Ready to Finish\n" ); - fflush( stdout ); - - if ( verbose ) - { - PrintBSPFileSizes(); - } - - Msg( "Writing %s\n", platformPath ); - VMPI_SetCurrentStage( "WriteBSPFile" ); - WriteBSPFile(platformPath); - - if ( g_bDumpPatches ) - { - for ( int iStyle = 0; iStyle < 4; ++iStyle ) - { - for ( int iBump = 0; iBump < 4; ++iBump ) - { - g_pFileSystem->Close( pFileSamples[iStyle][iBump] ); - } - } - } - - CloseDispLuxels(); - - StaticPropMgr()->Shutdown(); - - double end = Plat_FloatTime(); - - char str[512]; - GetHourMinuteSecondsString( (int)( end - g_flStartTime ), str, sizeof( str ) ); - Msg( "%s elapsed\n", str ); - - ReleasePakFileLumps(); -} - - -// Run startup code like initialize mathlib (called from main() and from the -// WorldCraft interface into vrad). -void VRAD_Init() -{ - MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false ); - InstallAllocationFunctions(); - InstallSpewFunction(); -} - - -int ParseCommandLine( int argc, char **argv, bool *onlydetail ) -{ - *onlydetail = false; - - // default to LDR - SetHDRMode( false ); - int i; - for( i=1 ; i 1.0) - luxeldensity = 1.0 / luxeldensity; - } - else - { - Warning("Error: expected a value after '-luxeldensity'\n" ); - return 1; - } - } - else if( !Q_stricmp( argv[i], "-low" ) ) - { - g_bLowPriority = true; - } - else if( !Q_stricmp( argv[i], "-loghash" ) ) - { - g_bLogHashData = true; - } - else if( !Q_stricmp( argv[i], "-onlydetail" ) ) - { - *onlydetail = true; - } - else if (!Q_stricmp(argv[i],"-softsun")) - { - if ( ++i < argc ) - { - g_SunAngularExtent=atof(argv[i]); - g_SunAngularExtent=sin((M_PI/180.0)*g_SunAngularExtent); - printf("sun extent=%f\n",g_SunAngularExtent); - } - else - { - Warning("Error: expected an angular extent value (0..180) '-softsun'\n" ); - return 1; - } - } - else if ( !Q_stricmp( argv[i], "-maxdispsamplesize" ) ) - { - if ( ++i < argc ) - { - g_flMaxDispSampleSize = ( float )atof( argv[i] ); - } - else - { - Warning( "Error: expected a sample size after '-maxdispsamplesize'\n" ); - return 1; - } - } - else if ( stricmp( argv[i], "-StopOnExit" ) == 0 ) - { - g_bStopOnExit = true; - } - else if ( stricmp( argv[i], "-steam" ) == 0 ) - { - } - else if ( stricmp( argv[i], "-allowdebug" ) == 0 ) - { - // Don't need to do anything, just don't error out. - } - else if ( !Q_stricmp( argv[i], CMDLINEOPTION_NOVCONFIG ) ) - { - } - else if ( !Q_stricmp( argv[i], "-vproject" ) || !Q_stricmp( argv[i], "-game" ) ) - { - ++i; - } - else if ( !Q_stricmp( argv[i], "-FullMinidumps" ) ) - { - EnableFullMinidumps( true ); - } - else if ( !Q_stricmp( argv[i], "-hdr" ) ) - { - SetHDRMode( true ); - } - else if ( !Q_stricmp( argv[i], "-ldr" ) ) - { - SetHDRMode( false ); - } - else if (!Q_stricmp(argv[i],"-maxchop")) - { - if ( ++i < argc ) - { - maxchop = (float)atof (argv[i]); - if ( maxchop < 1 ) - { - Warning("Error: expected positive value after '-maxchop'\n" ); - return 1; - } - } - else - { - Warning("Error: expected a value after '-maxchop'\n" ); - return 1; - } - } - else if (!Q_stricmp(argv[i],"-chop")) - { - if ( ++i < argc ) - { - minchop = (float)atof (argv[i]); - if ( minchop < 1 ) - { - Warning("Error: expected positive value after '-chop'\n" ); - return 1; - } - minchop = min( minchop, maxchop ); - } - else - { - Warning("Error: expected a value after '-chop'\n" ); - return 1; - } - } - else if ( !Q_stricmp( argv[i], "-dispchop" ) ) - { - if ( ++i < argc ) - { - dispchop = ( float )atof( argv[i] ); - if ( dispchop < 1.0f ) - { - Warning( "Error: expected positive value after '-dipschop'\n" ); - return 1; - } - } - else - { - Warning( "Error: expected a value after '-dispchop'\n" ); - return 1; - } - } - else if ( !Q_stricmp( argv[i], "-disppatchradius" ) ) - { - if ( ++i < argc ) - { - g_MaxDispPatchRadius = ( float )atof( argv[i] ); - if ( g_MaxDispPatchRadius < 10.0f ) - { - Warning( "Error: g_MaxDispPatchRadius < 10.0\n" ); - return 1; - } - } - else - { - Warning( "Error: expected a value after '-disppatchradius'\n" ); - return 1; - } - } - -#if ALLOWDEBUGOPTIONS - else if (!Q_stricmp(argv[i],"-scale")) - { - if ( ++i < argc ) - { - lightscale = (float)atof (argv[i]); - } - else - { - Warning("Error: expected a value after '-scale'\n" ); - return 1; - } - } - else if (!Q_stricmp(argv[i],"-ambient")) - { - if ( i+3 < argc ) - { - ambient[0] = (float)atof (argv[++i]) * 128; - ambient[1] = (float)atof (argv[++i]) * 128; - ambient[2] = (float)atof (argv[++i]) * 128; - } - else - { - Warning("Error: expected three color values after '-ambient'\n" ); - return 1; - } - } - else if (!Q_stricmp(argv[i],"-dlight")) - { - if ( ++i < argc ) - { - dlight_threshold = (float)atof (argv[i]); - } - else - { - Warning("Error: expected a value after '-dlight'\n" ); - return 1; - } - } - else if (!Q_stricmp(argv[i],"-sky")) - { - if ( ++i < argc ) - { - indirect_sun = (float)atof (argv[i]); - } - else - { - Warning("Error: expected a value after '-sky'\n" ); - return 1; - } - } - else if (!Q_stricmp(argv[i],"-notexscale")) - { - texscale = false; - } - else if (!Q_stricmp(argv[i],"-coring")) - { - if ( ++i < argc ) - { - coring = (float)atof( argv[i] ); - } - else - { - Warning("Error: expected a light threshold after '-coring'\n" ); - return 1; - } - } -#endif - // NOTE: the -mpi checks must come last here because they allow the previous argument - // to be -mpi as well. If it game before something else like -game, then if the previous - // argument was -mpi and the current argument was something valid like -game, it would skip it. - else if ( !Q_strncasecmp( argv[i], "-mpi", 4 ) || !Q_strncasecmp( argv[i-1], "-mpi", 4 ) ) - { - if ( stricmp( argv[i], "-mpi" ) == 0 ) - g_bUseMPI = true; - - // Any other args that start with -mpi are ok too. - if ( i == argc - 1 && V_stricmp( argv[i], "-mpi_ListParams" ) != 0 ) - break; - } - else - { - break; - } - } - - return i; -} - - -void PrintCommandLine( int argc, char **argv ) -{ - Warning( "Command line: " ); - for ( int z=0; z < argc; z++ ) - { - Warning( "\"%s\" ", argv[z] ); - } - Warning( "\n\n" ); -} - - -void PrintUsage( int argc, char **argv ) -{ - PrintCommandLine( argc, argv ); - - Warning( - "usage : vrad [options...] bspfile\n" - "example: vrad c:\\hl2\\hl2\\maps\\test\n" - "\n" - "Common options:\n" - "\n" - " -v (or -verbose): Turn on verbose output (also shows more command\n" - " -bounce # : Set max number of bounces (default: 100).\n" - " -fast : Quick and dirty lighting.\n" - " -fastambient : Per-leaf ambient sampling is lower quality to save compute time.\n" - " -final : High quality processing. equivalent to -extrasky 16.\n" - " -extrasky n : trace N times as many rays for indirect light and sky ambient.\n" - " -low : Run as an idle-priority process.\n" - " -mpi : Use VMPI to distribute computations.\n" - " -rederror : Show errors in red.\n" - "\n" - " -vproject : Override the VPROJECT environment variable.\n" - " -game : Same as -vproject.\n" - "\n" - "Other options:\n" - " -novconfig : Don't bring up graphical UI on vproject errors.\n" - " -dump : Write debugging .txt files.\n" - " -dumpnormals : Write normals to debug files.\n" - " -dumptrace : Write ray-tracing environment to debug files.\n" - " -threads : Control the number of threads vbsp uses (defaults to the #\n" - " or processors on your machine).\n" - " -lights : Load a lights file in addition to lights.rad and the\n" - " level lights file.\n" - " -noextra : Disable supersampling.\n" - " -debugextra : Places debugging data in lightmaps to visualize\n" - " supersampling.\n" - " -smooth # : Set the threshold for smoothing groups, in degrees\n" - " (default 45).\n" - " -dlightmap : Force direct lighting into different lightmap than\n" - " radiosity.\n" - " -stoponexit : Wait for a keypress on exit.\n" - " -mpi_pw : Use a password to choose a specific set of VMPI workers.\n" - " -nodetaillight : Don't light detail props.\n" - " -centersamples : Move sample centers.\n" - " -luxeldensity # : Rescale all luxels by the specified amount (default: 1.0).\n" - " The number specified must be less than 1.0 or it will be\n" - " ignored.\n" - " -loghash : Log the sample hash table to samplehash.txt.\n" - " -onlydetail : Only light detail props and per-leaf lighting.\n" - " -maxdispsamplesize #: Set max displacement sample size (default: 512).\n" - " -softsun : Treat the sun as an area light source of size degrees." - " Produces soft shadows.\n" - " Recommended values are between 0 and 5. Default is 0.\n" - " -FullMinidumps : Write large minidumps on crash.\n" - " -chop : Smallest number of luxel widths for a bounce patch, used on edges\n" - " -maxchop : Coarsest allowed number of luxel widths for a patch, used in face interiors\n" - "\n" - " -LargeDispSampleRadius: This can be used if there are splotches of bounced light\n" - " on terrain. The compile will take longer, but it will gather\n" - " light across a wider area.\n" - " -StaticPropLighting : generate backed static prop vertex lighting\n" - " -StaticPropPolys : Perform shadow tests of static props at polygon precision\n" - " -OnlyStaticProps : Only perform direct static prop lighting (vrad debug option)\n" - " -StaticPropNormals : when lighting static props, just show their normal vector\n" - " -textureshadows : Allows texture alpha channels to block light - rays intersecting alpha surfaces will sample the texture\n" - " -noskyboxrecurse : Turn off recursion into 3d skybox (skybox shadows on world)\n" - " -nossprops : Globally disable self-shadowing on static props\n" - "\n" -#if 1 // Disabled for the initial SDK release with VMPI so we can get feedback from selected users. - ); -#else - " -mpi_ListParams : Show a list of VMPI parameters.\n" - "\n" - ); - - // Show VMPI parameters? - for ( int i=1; i < argc; i++ ) - { - if ( V_stricmp( argv[i], "-mpi_ListParams" ) == 0 ) - { - Warning( "VMPI-specific options:\n\n" ); - - bool bIsSDKMode = VMPI_IsSDKMode(); - for ( int i=k_eVMPICmdLineParam_FirstParam+1; i < k_eVMPICmdLineParam_LastParam; i++ ) - { - if ( (VMPI_GetParamFlags( (EVMPICmdLineParam)i ) & VMPI_PARAM_SDK_HIDDEN) && bIsSDKMode ) - continue; - - Warning( "[%s]\n", VMPI_GetParamString( (EVMPICmdLineParam)i ) ); - Warning( VMPI_GetParamHelpString( (EVMPICmdLineParam)i ) ); - Warning( "\n\n" ); - } - break; - } - } -#endif -} - - -int RunVRAD( int argc, char **argv ) -{ -#if defined(_MSC_VER) && ( _MSC_VER >= 1310 ) - Msg("Valve Software - vrad.exe SSE (" __DATE__ ")\n" ); -#else - Msg("Valve Software - vrad.exe (" __DATE__ ")\n" ); -#endif - - Msg("\n Valve Radiosity Simulator \n"); - - verbose = true; // Originally FALSE - - bool onlydetail; - int i = ParseCommandLine( argc, argv, &onlydetail ); - if (i != argc - 1) - { - PrintUsage( argc, argv ); - DeleteCmdLine( argc, argv ); - CmdLib_Exit( 1 ); - } - - VRAD_LoadBSP( argv[i] ); - - if ( (! onlydetail) && (! g_bOnlyStaticProps ) ) - { - RadWorld_Go(); - } - - VRAD_ComputeOtherLighting(); - - VRAD_Finish(); - - VMPI_SetCurrentStage( "master done" ); - - DeleteCmdLine( argc, argv ); - CmdLib_Cleanup(); - return 0; -} - - -int VRAD_Main(int argc, char **argv) -{ - g_pFileSystem = NULL; // Safeguard against using it before it's properly initialized. - - VRAD_Init(); - - // This must come first. - VRAD_SetupMPI( argc, argv ); - - // Initialize the filesystem, so additional commandline options can be loaded - Q_StripExtension( argv[ argc - 1 ], source, sizeof( source ) ); - CmdLib_InitFileSystem( argv[ argc - 1 ] ); - Q_FileBase( source, source, sizeof( source ) ); - -#if !defined( _DEBUG ) - if ( g_bUseMPI && !g_bMPIMaster ) - { - SetupToolsMinidumpHandler( VMPI_ExceptionFilter ); - } - else -#endif - { - LoadCmdLineFromFile( argc, argv, source, "vrad" ); // Don't do this if we're a VMPI worker.. - SetupDefaultToolsMinidumpHandler(); - } - - return RunVRAD( argc, argv ); -} - - - - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +// vrad.c + +#include "vrad.h" +#include "physdll.h" +#include "lightmap.h" +#include "tier1/strtools.h" +#include "vmpi.h" +#include "macro_texture.h" +#include "vmpi_tools_shared.h" +#include "leaf_ambient_lighting.h" +#include "tools_minidump.h" +#include "loadcmdline.h" +#include "byteswap.h" + +#define ALLOWDEBUGOPTIONS (0 || _DEBUG) + +static FileHandle_t pFpTrans = NULL; + +/* + +NOTES +----- + +every surface must be divided into at least two patches each axis + +*/ + +CUtlVector g_Patches; +CUtlVector g_FacePatches; // constains all patches, children first +CUtlVector faceParents; // contains only root patches, use next parent to iterate +CUtlVector clusterChildren; +CUtlVector emitlight; +CUtlVector addlight; + +int num_sky_cameras; +sky_camera_t sky_cameras[MAX_MAP_AREAS]; +int area_sky_cameras[MAX_MAP_AREAS]; + +entity_t *face_entity[MAX_MAP_FACES]; +Vector face_offset[MAX_MAP_FACES]; // for rotating bmodels +int fakeplanes; + +unsigned numbounce = 100; // 25; /* Originally this was 8 */ + +float maxchop = 4; // coarsest allowed number of luxel widths for a patch +float minchop = 4; // "-chop" tightest number of luxel widths for a patch, used on edges +float dispchop = 8.0f; // number of luxel widths for a patch +float g_MaxDispPatchRadius = 1500.0f; // Maximum radius allowed for displacement patches +qboolean g_bDumpPatches; +bool bDumpNormals = false; +bool g_bDumpRtEnv = false; +bool bRed2Black = true; +bool g_bFastAmbient = false; +bool g_bNoSkyRecurse = false; + +int junk; + +Vector ambient( 0, 0, 0 ); + +float lightscale = 1.0; +float dlight_threshold = 0.1; // was DIRECT_LIGHT constant + +char source[MAX_PATH] = ""; +char platformPath[MAX_PATH] = ""; + +char level_name[MAX_PATH] = ""; // map filename, without extension or path info + +char global_lights[MAX_PATH] = ""; +char designer_lights[MAX_PATH] = ""; +char level_lights[MAX_PATH] = ""; + +char vismatfile[_MAX_PATH] = ""; +char incrementfile[_MAX_PATH] = ""; + +IIncremental *g_pIncremental = 0; +bool g_bInterrupt = false; // Wsed with background lighting in WC. Tells VRAD + // to stop lighting. +float g_SunAngularExtent=0.0; + +float g_flSkySampleScale = 1.0; + +bool g_bLargeDispSampleRadius = false; + +bool g_bOnlyStaticProps = false; +bool g_bShowStaticPropNormals = false; + + +float gamma_value = 0.5; +float indirect_sun = 1.0; +float reflectivityScale = 1.0; +qboolean do_extra = true; +bool debug_extra = false; +qboolean do_fast = false; +qboolean do_centersamples = false; +int extrapasses = 4; +float smoothing_threshold = 0.7071067; // cos(45.0*(M_PI/180)) +// Cosine of smoothing angle(in radians) +float coring = 1.0; // Light threshold to force to blackness(minimizes lightmaps) +qboolean texscale = true; +int dlight_map = 0; // Setting to 1 forces direct lighting into different lightmap than radiosity + +float luxeldensity = 1.0; +unsigned num_degenerate_faces; + +qboolean g_bLowPriority = false; +qboolean g_bLogHashData = false; +bool g_bNoDetailLighting = false; +double g_flStartTime; +bool g_bStaticPropLighting = false; +bool g_bStaticPropPolys = false; +bool g_bTextureShadows = false; +bool g_bDisablePropSelfShadowing = false; + + +CUtlVector g_FacesVisibleToLights; + +RayTracingEnvironment g_RtEnv; + +dface_t *g_pFaces=0; + +// this is a list of material names used on static props which shouldn't cast shadows. a +// sequential search is used since we allow substring matches. its not time critical, and this +// functionality is a stopgap until vrad starts reading .vmt files. +CUtlVector g_NonShadowCastingMaterialStrings; +/* +=================================================================== + +MISC + +=================================================================== +*/ + + +int leafparents[MAX_MAP_LEAFS]; +int nodeparents[MAX_MAP_NODES]; + +void MakeParents (int nodenum, int parent) +{ + int i, j; + dnode_t *node; + + nodeparents[nodenum] = parent; + node = &dnodes[nodenum]; + + for (i=0 ; i<2 ; i++) + { + j = node->children[i]; + if (j < 0) + leafparents[-j - 1] = nodenum; + else + MakeParents (j, nodenum); + } +} + + +/* +=================================================================== + + TEXTURE LIGHT VALUES + +=================================================================== +*/ + +typedef struct +{ + char name[256]; + Vector value; + char *filename; +} texlight_t; + +#define MAX_TEXLIGHTS 128 + +texlight_t texlights[MAX_TEXLIGHTS]; +int num_texlights; + +/* +============ +ReadLightFile +============ +*/ +void ReadLightFile (char *filename) +{ + char buf[1024]; + int file_texlights = 0; + + FileHandle_t f = g_pFileSystem->Open( filename, "r" ); + if (!f) + { + Warning("Warning: Couldn't open texlight file %s.\n", filename); + return; + } + + Msg("[Reading texlights from '%s']\n", filename); + while ( CmdLib_FGets( buf, sizeof( buf ), f ) ) + { + // check ldr/hdr + char * scan = buf; + if ( !strnicmp( "hdr:", scan, 4) ) + { + scan += 4; + if ( ! g_bHDR ) + { + continue; + } + } + if ( !strnicmp( "ldr:", scan, 4) ) + { + scan += 4; + if ( g_bHDR ) + { + continue; + } + } + + scan += strspn( scan, " \t" ); + char NoShadName[1024]; + if ( sscanf(scan,"noshadow %s",NoShadName)==1) + { + char * dot = strchr( NoShadName, '.' ); + if ( dot ) // if they specify .vmt, kill it + * dot = 0; + //printf("add %s as a non shadow casting material\n",NoShadName); + g_NonShadowCastingMaterialStrings.AddToTail( strdup( NoShadName )); + } + else if ( sscanf( scan, "forcetextureshadow %s", NoShadName ) == 1 ) + { + //printf("add %s as a non shadow casting material\n",NoShadName); + ForceTextureShadowsOnModel( NoShadName ); + } + else + { + char szTexlight[256]; + Vector value; + if ( num_texlights == MAX_TEXLIGHTS ) + Error ("Too many texlights, max = %d", MAX_TEXLIGHTS); + + int argCnt = sscanf (scan, "%s ",szTexlight ); + + if( argCnt != 1 ) + { + if ( strlen( scan ) > 4 ) + Msg( "ignoring bad texlight '%s' in %s", scan, filename ); + continue; + } + + LightForString( scan + strlen( szTexlight ) + 1, value ); + + int j = 0; + for( j; j < num_texlights; j ++ ) + { + if ( strcmp( texlights[j].name, szTexlight ) == 0 ) + { + if ( strcmp( texlights[j].filename, filename ) == 0 ) + { + Msg( "ERROR\a: Duplication of '%s' in file '%s'!\n", + texlights[j].name, texlights[j].filename ); + } + else if ( texlights[j].value[0] != value[0] + || texlights[j].value[1] != value[1] + || texlights[j].value[2] != value[2] ) + { + Warning( "Warning: Overriding '%s' from '%s' with '%s'!\n", + texlights[j].name, texlights[j].filename, filename ); + } + else + { + Warning( "Warning: Redundant '%s' def in '%s' AND '%s'!\n", + texlights[j].name, texlights[j].filename, filename ); + } + break; + } + } + strcpy( texlights[j].name, szTexlight ); + VectorCopy( value, texlights[j].value ); + texlights[j].filename = filename; + file_texlights ++; + + num_texlights = max( num_texlights, j + 1 ); + } + } + qprintf ( "[%i texlights parsed from '%s']\n\n", file_texlights, filename); + g_pFileSystem->Close( f ); +} + + +/* +============ +LightForTexture +============ +*/ +void LightForTexture( const char *name, Vector& result ) +{ + int i; + + result[ 0 ] = result[ 1 ] = result[ 2 ] = 0; + + char baseFilename[ MAX_PATH ]; + + if ( Q_strncmp( "maps/", name, 5 ) == 0 ) + { + // this might be a patch texture for cubemaps. try to parse out the original filename. + if ( Q_strncmp( level_name, name + 5, Q_strlen( level_name ) ) == 0 ) + { + const char *base = name + 5 + Q_strlen( level_name ); + if ( *base == '/' ) + { + ++base; // step past the path separator + + // now we've gotten rid of the 'maps/level_name/' part, so we're left with + // 'originalName_%d_%d_%d'. + strcpy( baseFilename, base ); + bool foundSeparators = true; + for ( int i=0; i<3; ++i ) + { + char *underscore = Q_strrchr( baseFilename, '_' ); + if ( underscore && *underscore ) + { + *underscore = '\0'; + } + else + { + foundSeparators = false; + } + } + + if ( foundSeparators ) + { + name = baseFilename; + } + } + } + } + + for (i=0 ; inumedges); + w->numpoints = f->numedges; + + for (i=0 ; inumedges ; i++) + { + se = dsurfedges[f->firstedge + i]; + if (se < 0) + v = dedges[-se].v[1]; + else + v = dedges[se].v[0]; + + dv = &dvertexes[v]; + VectorAdd (dv->point, origin, w->p[i]); + } + + RemoveColinearPoints (w); + + return w; +} + +/* +============= +BaseLightForFace +============= +*/ +void BaseLightForFace( dface_t *f, Vector& light, float *parea, Vector& reflectivity ) +{ + texinfo_t *tx; + dtexdata_t *texdata; + + // + // check for light emited by texture + // + tx = &texinfo[f->texinfo]; + texdata = &dtexdata[tx->texdata]; + + LightForTexture (TexDataStringTable_GetString( texdata->nameStringTableID ), light); + + + *parea = texdata->height * texdata->width; + + VectorScale( texdata->reflectivity, reflectivityScale, reflectivity ); + + // always keep this less than 1 or the solution will not converge + for ( int i = 0; i < 3; i++ ) + { + if ( reflectivity[i] > 0.99 ) + reflectivity[i] = 0.99; + } +} + +qboolean IsSky (dface_t *f) +{ + texinfo_t *tx; + + tx = &texinfo[f->texinfo]; + if (tx->flags & SURF_SKY) + return true; + return false; +} + +#ifdef STATIC_FOG +/*============= +IsFog +=============*/ +qboolean IsFog( dface_t *f ) +{ + texinfo_t *tx; + + tx = &texinfo[f->texinfo]; + + // % denotes a fog texture + if( tx->texture[0] == '%' ) + return true; + + return false; +} +#endif + + +void ProcessSkyCameras() +{ + int i; + num_sky_cameras = 0; + for (i = 0; i < numareas; ++i) + { + area_sky_cameras[i] = -1; + } + + for (i = 0; i < num_entities; ++i) + { + entity_t *e = &entities[i]; + const char *name = ValueForKey (e, "classname"); + if (stricmp (name, "sky_camera")) + continue; + + Vector origin; + GetVectorForKey( e, "origin", origin ); + int node = PointLeafnum( origin ); + int area = -1; + if (node >= 0 && node < numleafs) area = dleafs[node].area; + float scale = FloatForKey( e, "scale" ); + + if (scale > 0.0f) + { + sky_cameras[num_sky_cameras].origin = origin; + sky_cameras[num_sky_cameras].sky_to_world = scale; + sky_cameras[num_sky_cameras].world_to_sky = 1.0f / scale; + sky_cameras[num_sky_cameras].area = area; + + if (area >= 0 && area < numareas) + { + area_sky_cameras[area] = num_sky_cameras; + } + + ++num_sky_cameras; + } + } + +} + + +/* +============= +MakePatchForFace +============= +*/ +float totalarea; +void MakePatchForFace (int fn, winding_t *w) +{ + dface_t *f = g_pFaces + fn; + float area; + CPatch *patch; + Vector centroid(0,0,0); + int i, j; + texinfo_t *tx; + + // get texture info + tx = &texinfo[f->texinfo]; + + // No patches at all for fog! +#ifdef STATIC_FOG + if ( IsFog( f ) ) + return; +#endif + + // the sky needs patches or the form factors don't work out correctly + // if (IsSky( f ) ) + // return; + + area = WindingArea (w); + if (area <= 0) + { + num_degenerate_faces++; + // Msg("degenerate face\n"); + return; + } + + totalarea += area; + + // get a patch + int ndxPatch = g_Patches.AddToTail(); + patch = &g_Patches[ndxPatch]; + memset( patch, 0, sizeof( CPatch ) ); + patch->ndxNext = g_Patches.InvalidIndex(); + patch->ndxNextParent = g_Patches.InvalidIndex(); + patch->ndxNextClusterChild = g_Patches.InvalidIndex(); + patch->child1 = g_Patches.InvalidIndex(); + patch->child2 = g_Patches.InvalidIndex(); + patch->parent = g_Patches.InvalidIndex(); + patch->needsBumpmap = tx->flags & SURF_BUMPLIGHT ? true : false; + + // link and save patch data + patch->ndxNext = g_FacePatches.Element( fn ); + g_FacePatches[fn] = ndxPatch; +// patch->next = face_g_Patches[fn]; +// face_g_Patches[fn] = patch; + + // compute a separate scale for chop - since the patch "scale" is the texture scale + // we want textures with higher resolution lighting to be chopped up more + float chopscale[2]; + chopscale[0] = chopscale[1] = 16.0f; + if ( texscale ) + { + // Compute the texture "scale" in s,t + for( i=0; i<2; i++ ) + { + patch->scale[i] = 0.0f; + chopscale[i] = 0.0f; + for( j=0; j<3; j++ ) + { + patch->scale[i] += + tx->textureVecsTexelsPerWorldUnits[i][j] * + tx->textureVecsTexelsPerWorldUnits[i][j]; + chopscale[i] += + tx->lightmapVecsLuxelsPerWorldUnits[i][j] * + tx->lightmapVecsLuxelsPerWorldUnits[i][j]; + } + patch->scale[i] = sqrt( patch->scale[i] ); + chopscale[i] = sqrt( chopscale[i] ); + } + } + else + { + patch->scale[0] = patch->scale[1] = 1.0f; + } + + patch->area = area; + + patch->sky = IsSky( f ); + + // chop scaled up lightmaps coarser + patch->luxscale = ((chopscale[0]+chopscale[1])/2); + patch->chop = maxchop; + + +#ifdef STATIC_FOG + patch->fog = FALSE; +#endif + + patch->winding = w; + + patch->plane = &dplanes[f->planenum]; + + // make a new plane to adjust for origined bmodels + if (face_offset[fn][0] || face_offset[fn][1] || face_offset[fn][2] ) + { + dplane_t *pl; + + // origin offset faces must create new planes + if (numplanes + fakeplanes >= MAX_MAP_PLANES) + { + Error ("numplanes + fakeplanes >= MAX_MAP_PLANES"); + } + pl = &dplanes[numplanes + fakeplanes]; + fakeplanes++; + + *pl = *(patch->plane); + pl->dist += DotProduct (face_offset[fn], pl->normal); + patch->plane = pl; + } + + patch->faceNumber = fn; + WindingCenter (w, patch->origin); + + // Save "center" for generating the face normals later. + VectorSubtract( patch->origin, face_offset[fn], face_centroids[fn] ); + + VectorCopy( patch->plane->normal, patch->normal ); + + WindingBounds (w, patch->face_mins, patch->face_maxs); + VectorCopy( patch->face_mins, patch->mins ); + VectorCopy( patch->face_maxs, patch->maxs ); + + BaseLightForFace( f, patch->baselight, &patch->basearea, patch->reflectivity ); + + // Chop all texlights very fine. + if ( !VectorCompare( patch->baselight, vec3_origin ) ) + { + // patch->chop = do_extra ? maxchop / 2 : maxchop; + tx->flags |= SURF_LIGHT; + } + + // get rid of do extra functionality on displacement surfaces + if( ValidDispFace( f ) ) + { + patch->chop = maxchop; + } + + // FIXME: If we wanted to add a dependency from vrad to the material system, + // we could do this. It would add a bunch of file accesses, though: + + /* + // Check for a material var which would override the patch chop + bool bFound; + const char *pMaterialName = TexDataStringTable_GetString( dtexdata[ tx->texdata ].nameStringTableID ); + MaterialSystemMaterial_t hMaterial = FindMaterial( pMaterialName, &bFound, false ); + if ( bFound ) + { + const char *pChopValue = GetMaterialVar( hMaterial, "%chop" ); + if ( pChopValue ) + { + float flChopValue; + if ( sscanf( pChopValue, "%f", &flChopValue ) > 0 ) + { + patch->chop = flChopValue; + } + } + } + */ +} + + +entity_t *EntityForModel (int modnum) +{ + int i; + char *s; + char name[16]; + + sprintf (name, "*%i", modnum); + // search the entities for one using modnum + for (i=0 ; inumfaces ; j++) + { + fn = mod->firstface + j; + face_entity[fn] = ent; + VectorCopy (origin, face_offset[fn]); + f = &g_pFaces[fn]; + if( f->dispinfo == -1 ) + { + w = WindingFromFace (f, origin ); + MakePatchForFace( fn, w ); + } + } + } + + if (num_degenerate_faces > 0) + { + qprintf("%d degenerate faces\n", num_degenerate_faces ); + } + + qprintf ("%i square feet [%.2f square inches]\n", (int)(totalarea/144), totalarea ); + + // make the displacement surface patches + StaticDispMgr()->MakePatches(); +} + +/* +======================================================================= + +SUBDIVIDE + +======================================================================= +*/ + + +//----------------------------------------------------------------------------- +// Purpose: does this surface take/emit light +//----------------------------------------------------------------------------- +bool PreventSubdivision( CPatch *patch ) +{ + dface_t *f = g_pFaces + patch->faceNumber; + texinfo_t *tx = &texinfo[f->texinfo]; + + if (tx->flags & SURF_NOCHOP) + return true; + + if (tx->flags & SURF_NOLIGHT && !(tx->flags & SURF_LIGHT)) + return true; + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: subdivide the "parent" patch +//----------------------------------------------------------------------------- +int CreateChildPatch( int nParentIndex, winding_t *pWinding, float flArea, const Vector &vecCenter ) +{ + int nChildIndex = g_Patches.AddToTail(); + + CPatch *child = &g_Patches[nChildIndex]; + CPatch *parent = &g_Patches[nParentIndex]; + + // copy all elements of parent patch to children + *child = *parent; + + // Set up links + child->ndxNext = g_Patches.InvalidIndex(); + child->ndxNextParent = g_Patches.InvalidIndex(); + child->ndxNextClusterChild = g_Patches.InvalidIndex(); + child->child1 = g_Patches.InvalidIndex(); + child->child2 = g_Patches.InvalidIndex(); + child->parent = nParentIndex; + child->m_IterationKey = 0; + + child->winding = pWinding; + child->area = flArea; + + VectorCopy( vecCenter, child->origin ); + if ( ValidDispFace( g_pFaces + child->faceNumber ) ) + { + // shouldn't get here anymore!! + Msg( "SubdividePatch: Error - Should not be here!\n" ); + StaticDispMgr()->GetDispSurfNormal( child->faceNumber, child->origin, child->normal, true ); + } + else + { + GetPhongNormal( child->faceNumber, child->origin, child->normal ); + } + + child->planeDist = child->plane->dist; + WindingBounds(child->winding, child->mins, child->maxs); + + if ( !VectorCompare( child->baselight, vec3_origin ) ) + { + // don't check edges on surf lights + return nChildIndex; + } + + // Subdivide patch towards minchop if on the edge of the face + Vector total; + VectorSubtract( child->maxs, child->mins, total ); + VectorScale( total, child->luxscale, total ); + if ( child->chop > minchop && (total[0] < child->chop) && (total[1] < child->chop) && (total[2] < child->chop) ) + { + for ( int i=0; i<3; ++i ) + { + if ( (child->face_maxs[i] == child->maxs[i] || child->face_mins[i] == child->mins[i] ) + && total[i] > minchop ) + { + child->chop = max( minchop, child->chop / 2 ); + break; + } + } + } + + return nChildIndex; +} + + +//----------------------------------------------------------------------------- +// Purpose: subdivide the "parent" patch +//----------------------------------------------------------------------------- +void SubdividePatch( int ndxPatch ) +{ + winding_t *w, *o1, *o2; + Vector total; + Vector split; + vec_t dist; + vec_t widest = -1; + int i, widest_axis = -1; + bool bSubdivide = false; + + // get the current patch + CPatch *patch = &g_Patches.Element( ndxPatch ); + if ( !patch ) + return; + + // never subdivide sky patches + if ( patch->sky ) + return; + + // get the patch winding + w = patch->winding; + + // subdivide along the widest axis + VectorSubtract (patch->maxs, patch->mins, total); + VectorScale( total, patch->luxscale, total ); + for (i=0 ; i<3 ; i++) + { + if ( total[i] > widest ) + { + widest_axis = i; + widest = total[i]; + } + + if ( (total[i] >= patch->chop) && (total[i] >= minchop) ) + { + bSubdivide = true; + } + } + + if ((!bSubdivide) && widest_axis != -1) + { + // make more square + if (total[widest_axis] > total[(widest_axis + 1) % 3] * 2 && total[widest_axis] > total[(widest_axis + 2) % 3] * 2) + { + if (patch->chop > minchop) + { + bSubdivide = true; + patch->chop = max( minchop, patch->chop / 2 ); + } + } + } + + if ( !bSubdivide ) + return; + + // split the winding + VectorCopy (vec3_origin, split); + split[widest_axis] = 1; + dist = (patch->mins[widest_axis] + patch->maxs[widest_axis])*0.5f; + ClipWindingEpsilon (w, split, dist, ON_EPSILON, &o1, &o2); + + // calculate the area of the patches to see if they are "significant" + Vector center1, center2; + float area1 = WindingAreaAndBalancePoint( o1, center1 ); + float area2 = WindingAreaAndBalancePoint( o2, center2 ); + + if( area1 == 0 || area2 == 0 ) + { + Msg( "zero area child patch\n" ); + return; + } + + // create new child patches + int ndxChild1Patch = CreateChildPatch( ndxPatch, o1, area1, center1 ); + int ndxChild2Patch = CreateChildPatch( ndxPatch, o2, area2, center2 ); + + // FIXME: This could go into CreateChildPatch if child1, child2 were stored in the patch as child[0], child[1] + patch = &g_Patches.Element( ndxPatch ); + patch->child1 = ndxChild1Patch; + patch->child2 = ndxChild2Patch; + + SubdividePatch( ndxChild1Patch ); + SubdividePatch( ndxChild2Patch ); +} + + +/* +============= +SubdividePatches +============= +*/ +void SubdividePatches (void) +{ + unsigned i, num; + + if (numbounce == 0) + return; + + unsigned int uiPatchCount = g_Patches.Size(); + qprintf ("%i patches before subdivision\n", uiPatchCount); + + for (i = 0; i < uiPatchCount; i++) + { + CPatch *pCur = &g_Patches.Element( i ); + pCur->planeDist = pCur->plane->dist; + + pCur->ndxNextParent = faceParents.Element( pCur->faceNumber ); + faceParents[pCur->faceNumber] = pCur - g_Patches.Base(); + } + + for (i=0 ; i< uiPatchCount; i++) + { + CPatch *patch = &g_Patches.Element( i ); + patch->parent = -1; + if ( PreventSubdivision(patch) ) + continue; + + if (!do_fast) + { + if( g_pFaces[patch->faceNumber].dispinfo == -1 ) + { + SubdividePatch( i ); + } + else + { + StaticDispMgr()->SubdividePatch( i ); + } + } + } + + // fixup next pointers + for (i = 0; i < (unsigned)numfaces; i++) + { + g_FacePatches[i] = g_FacePatches.InvalidIndex(); + } + + uiPatchCount = g_Patches.Size(); + for (i = 0; i < uiPatchCount; i++) + { + CPatch *pCur = &g_Patches.Element( i ); + pCur->ndxNext = g_FacePatches.Element( pCur->faceNumber ); + g_FacePatches[pCur->faceNumber] = pCur - g_Patches.Base(); + +#if 0 + CPatch *prev; + prev = face_g_Patches[g_Patches[i].faceNumber]; + g_Patches[i].next = prev; + face_g_Patches[g_Patches[i].faceNumber] = &g_Patches[i]; +#endif + } + + // Cache off the leaf number: + // We have to do this after subdivision because some patches span leaves. + // (only the faces for model #0 are split by it's BSP which is what governs the PVS, and the leaves we're interested in) + // Sub models (1-255) are only split for the BSP that their model forms. + // When those patches are subdivided their origins can end up in a different leaf. + // The engine will split (clip) those faces at run time to the world BSP because the models + // are dynamic and can be moved. In the software renderer, they must be split exactly in order + // to sort per polygon. + for ( i = 0; i < uiPatchCount; i++ ) + { + g_Patches[i].clusterNumber = ClusterFromPoint( g_Patches[i].origin ); + + // + // test for point in solid space (can happen with detail and displacement surfaces) + // + if( g_Patches[i].clusterNumber == -1 ) + { + for( int j = 0; j < g_Patches[i].winding->numpoints; j++ ) + { + int clusterNumber = ClusterFromPoint( g_Patches[i].winding->p[j] ); + if( clusterNumber != -1 ) + { + g_Patches[i].clusterNumber = clusterNumber; + break; + } + } + } + } + + // build the list of patches that need to be lit + for ( num = 0; num < uiPatchCount; num++ ) + { + // do them in reverse order + i = uiPatchCount - num - 1; + + // skip patches with children + CPatch *pCur = &g_Patches.Element( i ); + if( pCur->child1 == g_Patches.InvalidIndex() ) + { + if( pCur->clusterNumber != - 1 ) + { + pCur->ndxNextClusterChild = clusterChildren.Element( pCur->clusterNumber ); + clusterChildren[pCur->clusterNumber] = pCur - g_Patches.Base(); + } + } + +#if 0 + if (g_Patches[i].child1 == g_Patches.InvalidIndex() ) + { + if( g_Patches[i].clusterNumber != -1 ) + { + g_Patches[i].nextclusterchild = cluster_children[g_Patches[i].clusterNumber]; + cluster_children[g_Patches[i].clusterNumber] = &g_Patches[i]; + } + } +#endif + } + + qprintf ("%i patches after subdivision\n", uiPatchCount); +} + + +//===================================================================== + +/* +============= +MakeScales + + This is the primary time sink. + It can be run multi threaded. +============= +*/ +int total_transfer; +int max_transfer; + + +//----------------------------------------------------------------------------- +// Purpose: Computes the form factor from a polygon patch to a differential patch +// using formula 81 of Philip Dutre's Global Illumination Compendium, +// phil@graphics.cornell.edu, http://www.graphics.cornell.edu/~phil/GI/ +//----------------------------------------------------------------------------- +float FormFactorPolyToDiff ( CPatch *pPolygon, CPatch* pDifferential ) +{ + winding_t *pWinding = pPolygon->winding; + + float flFormFactor = 0.0f; + + for ( int iPoint = 0; iPoint < pWinding->numpoints; iPoint++ ) + { + int iNextPoint = ( iPoint < pWinding->numpoints - 1 ) ? iPoint + 1 : 0; + + Vector vGammaVector, vVector1, vVector2; + VectorSubtract( pWinding->p[ iPoint ], pDifferential->origin, vVector1 ); + VectorSubtract( pWinding->p[ iNextPoint ], pDifferential->origin, vVector2 ); + VectorNormalize( vVector1 ); + VectorNormalize( vVector2 ); + CrossProduct( vVector1, vVector2, vGammaVector ); + float flSinAlpha = VectorNormalize( vGammaVector ); + if (flSinAlpha < -1.0f || flSinAlpha > 1.0f) + return 0.0f; + vGammaVector *= asin( flSinAlpha ); + + flFormFactor += DotProduct( vGammaVector, pDifferential->normal ); + } + + flFormFactor *= ( 0.5f / pPolygon->area ); // divide by pi later, multiply by area later + + return flFormFactor; +} + + +//----------------------------------------------------------------------------- +// Purpose: Computes the form factor from a differential element to a differential +// element. This is okay when the distance between patches is 5 times +// greater than patch size. Lecture slides by Pat Hanrahan, +// http://graphics.stanford.edu/courses/cs348b-00/lectures/lecture17/radiosity.2.pdf +//----------------------------------------------------------------------------- +float FormFactorDiffToDiff ( CPatch *pDiff1, CPatch* pDiff2 ) +{ + Vector vDelta; + VectorSubtract( pDiff1->origin, pDiff2->origin, vDelta ); + float flLength = VectorNormalize( vDelta ); + + return -DotProduct( vDelta, pDiff1->normal ) * DotProduct( vDelta, pDiff2->normal ) / ( flLength * flLength ); +} + + + +void MakeTransfer( int ndxPatch1, int ndxPatch2, transfer_t *all_transfers ) +//void MakeTransfer (CPatch *patch, CPatch *patch2, transfer_t *all_transfers ) +{ + Vector delta; + vec_t scale; + float trans; + transfer_t *transfer; + + // + // get patches + // + if( ndxPatch1 == g_Patches.InvalidIndex() || ndxPatch2 == g_Patches.InvalidIndex() ) + return; + + CPatch *pPatch1 = &g_Patches.Element( ndxPatch1 ); + CPatch *pPatch2 = &g_Patches.Element( ndxPatch2 ); + + if (IsSky( &g_pFaces[ pPatch2->faceNumber ] ) ) + return; + + // overflow check! + if ( pPatch1->numtransfers >= MAX_PATCHES) + { + return; + } + + // hack for patch areas that area <= 0 (degenerate) + if ( pPatch2->area <= 0) + { + return; + } + + transfer = &all_transfers[pPatch1->numtransfers]; + + scale = FormFactorDiffToDiff( pPatch2, pPatch1 ); + + // patch normals may be > 90 due to smoothing groups + if (scale <= 0) + { + //Msg("scale <= 0\n"); + return; + } + + // Test 5 times rule + Vector vDelta; + VectorSubtract( pPatch1->origin, pPatch2->origin, vDelta ); + float flThreshold = ( M_PI * 0.04 ) * DotProduct( vDelta, vDelta ); + + if (flThreshold < pPatch2->area) + { + scale = FormFactorPolyToDiff( pPatch2, pPatch1 ); + if (scale <= 0.0) + return; + } + + trans = (pPatch2->area*scale); + + if (trans <= TRANSFER_EPSILON) + { + return; + } + + transfer->patch = pPatch2 - g_Patches.Base(); + + // FIXME: why is this not trans? + transfer->transfer = trans; + +#if 0 + // DEBUG! Dump patches and transfer connection for displacements. This creates a lot of data, so only + // use it when you really want it - that is why it is #if-ed out. + if ( g_bDumpPatches ) + { + if ( !pFpTrans ) + { + pFpTrans = g_pFileSystem->Open( "trans.txt", "w" ); + } + Vector light = pPatch1->totallight.light[0] + pPatch1->directlight; + WriteWinding( pFpTrans, pPatch1->winding, light ); + light = pPatch2->totallight.light[0] + pPatch2->directlight; + WriteWinding( pFpTrans, pPatch2->winding, light ); + WriteLine( pFpTrans, pPatch1->origin, pPatch2->origin, Vector( 255, 0, 255 ) ); + } +#endif + + pPatch1->numtransfers++; +} + + +void MakeScales ( int ndxPatch, transfer_t *all_transfers ) +{ + int j; + float total; + transfer_t *t, *t2; + total = 0; + + if( ndxPatch == g_Patches.InvalidIndex() ) + return; + CPatch *patch = &g_Patches.Element( ndxPatch ); + + // copy the transfers out + if (patch->numtransfers) + { + if (patch->numtransfers > max_transfer) + { + max_transfer = patch->numtransfers; + } + + + patch->transfers = ( transfer_t* )calloc (1, patch->numtransfers * sizeof(transfer_t)); + if (!patch->transfers) + Error ("Memory allocation failure"); + + // get total transfer energy + t2 = all_transfers; + + // overflow check! + for (j=0 ; jnumtransfers ; j++, t2++) + { + total += t2->transfer; + } + + // the total transfer should be PI, but we need to correct errors due to overlaping surfaces + if (total > M_PI) + total = 1.0f/total; + else + total = 1.0f/M_PI; + + t = patch->transfers; + t2 = all_transfers; + for (j=0 ; jnumtransfers ; j++, t++, t2++) + { + t->transfer = t2->transfer*total; + t->patch = t2->patch; + } + if (patch->numtransfers > max_transfer) + { + max_transfer = patch->numtransfers; + } + } + else + { + // Error - patch has no transfers + // patch->totallight[2] = 255; + } + + ThreadLock (); + total_transfer += patch->numtransfers; + ThreadUnlock (); +} + +/* +============= +WriteWorld +============= +*/ +void WriteWorld (char *name, int iBump) +{ + unsigned j; + FileHandle_t out; + CPatch *patch; + + out = g_pFileSystem->Open( name, "w" ); + if (!out) + Error ("Couldn't open %s", name); + + unsigned int uiPatchCount = g_Patches.Size(); + for (j=0; jchild1 != g_Patches.InvalidIndex() ) + continue; + + if( patch->clusterNumber == -1 ) + { + Vector vGreen; + VectorClear( vGreen ); + vGreen[1] = 256.0f; + WriteWinding( out, patch->winding, vGreen ); + } + else + { + Vector light = patch->totallight.light[iBump] + patch->directlight; + WriteWinding( out, patch->winding, light ); + if( bDumpNormals ) + { + WriteNormal( out, patch->origin, patch->plane->normal, 15.0f, patch->plane->normal * 255.0f ); + } + } + } + + g_pFileSystem->Close( out ); +} + +void WriteRTEnv (char *name) +{ + FileHandle_t out; + + out = g_pFileSystem->Open( name, "w" ); + if (!out) + Error ("Couldn't open %s", name); + + winding_t *triw = AllocWinding( 3 ); + triw->numpoints = 3; + + for( int i = 0; i < g_RtEnv.OptimizedTriangleList.Size(); i++ ) + { + triw->p[0] = g_RtEnv.OptimizedTriangleList[i].Vertex( 0); + triw->p[1] = g_RtEnv.OptimizedTriangleList[i].Vertex( 1); + triw->p[2] = g_RtEnv.OptimizedTriangleList[i].Vertex( 2); + int id = g_RtEnv.OptimizedTriangleList[i].m_Data.m_GeometryData.m_nTriangleID; + Vector color(0, 0, 0); + if (id & TRACE_ID_OPAQUE) color.Init(0, 255, 0); + if (id & TRACE_ID_SKY) color.Init(0, 0, 255); + if (id & TRACE_ID_STATICPROP) color.Init(255, 0, 0); + WriteWinding(out, triw, color); + } + FreeWinding(triw); + + g_pFileSystem->Close( out ); +} + +void WriteWinding (FileHandle_t out, winding_t *w, Vector& color ) +{ + int i; + + CmdLib_FPrintf (out, "%i\n", w->numpoints); + for (i=0 ; inumpoints ; i++) + { + CmdLib_FPrintf (out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + w->p[i][0], + w->p[i][1], + w->p[i][2], + color[ 0 ] / 256, + color[ 1 ] / 256, + color[ 2 ] / 256 ); + } +} + + +void WriteNormal( FileHandle_t out, Vector const &nPos, Vector const &nDir, + float length, Vector const &color ) +{ + CmdLib_FPrintf( out, "2\n" ); + CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + nPos.x, nPos.y, nPos.z, + color.x / 256, color.y / 256, color.z / 256 ); + CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + nPos.x + ( nDir.x * length ), + nPos.y + ( nDir.y * length ), + nPos.z + ( nDir.z * length ), + color.x / 256, color.y / 256, color.z / 256 ); +} + +void WriteLine( FileHandle_t out, const Vector &vecPos1, const Vector &vecPos2, const Vector &color ) +{ + CmdLib_FPrintf( out, "2\n" ); + CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + vecPos1.x, vecPos1.y, vecPos1.z, + color.x / 256, color.y / 256, color.z / 256 ); + CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + vecPos2.x, vecPos2.y, vecPos2.z, + color.x / 256, color.y / 256, color.z / 256 ); +} + +void WriteTrace( const char *pFileName, const FourRays &rays, const RayTracingResult& result ) +{ + FileHandle_t out; + + out = g_pFileSystem->Open( pFileName, "a" ); + if (!out) + Error ("Couldn't open %s", pFileName); + + // Draws rays + for ( int i = 0; i < 4; ++i ) + { + Vector vecOrigin = rays.origin.Vec(i); + Vector vecEnd = rays.direction.Vec(i); + VectorNormalize( vecEnd ); + vecEnd *= SubFloat( result.HitDistance, i ); + vecEnd += vecOrigin; + WriteLine( out, vecOrigin, vecEnd, Vector( 256, 0, 0 ) ); + WriteNormal( out, vecEnd, result.surface_normal.Vec(i), 10.0f, Vector( 256, 265, 0 ) ); + } + + g_pFileSystem->Close( out ); +} + + +/* +============= +CollectLight +============= +*/ +// patch's totallight += new light received to each patch +// patch's emitlight = addlight (newly received light from GatherLight) +// patch's addlight = 0 +// pull received light from children. +void CollectLight( Vector& total ) +{ + int i, j; + CPatch *patch; + + VectorFill( total, 0 ); + + // process patches in reverse order so that children are processed before their parents + unsigned int uiPatchCount = g_Patches.Size(); + for( i = uiPatchCount - 1; i >= 0; i-- ) + { + patch = &g_Patches.Element( i ); + int normalCount = patch->needsBumpmap ? NUM_BUMP_VECTS+1 : 1; + // sky's never collect light, it is just dropped + if (patch->sky) + { + VectorFill( emitlight[ i ], 0 ); + } + else if ( patch->child1 == g_Patches.InvalidIndex() ) + { + // This is a leaf node. + for ( j = 0; j < normalCount; j++ ) + { + VectorAdd( patch->totallight.light[j], addlight[i].light[j], patch->totallight.light[j] ); + } + VectorCopy( addlight[i].light[0], emitlight[i] ); + VectorAdd( total, emitlight[i], total ); + } + else + { + // This is an interior node. + // Pull received light from children. + float s1, s2; + CPatch *child1; + CPatch *child2; + + child1 = &g_Patches[patch->child1]; + child2 = &g_Patches[patch->child2]; + + // BUG: This doesn't do anything? + if ((int)patch->area != (int)(child1->area + child2->area)) + s1 = 0; + + s1 = child1->area / (child1->area + child2->area); + s2 = child2->area / (child1->area + child2->area); + + // patch->totallight = s1 * child1->totallight + s2 * child2->totallight + for ( j = 0; j < normalCount; j++ ) + { + VectorScale( child1->totallight.light[j], s1, patch->totallight.light[j] ); + VectorMA( patch->totallight.light[j], s2, child2->totallight.light[j], patch->totallight.light[j] ); + } + + // patch->emitlight = s1 * child1->emitlight + s2 * child2->emitlight + VectorScale( emitlight[patch->child1], s1, emitlight[i] ); + VectorMA( emitlight[i], s2, emitlight[patch->child2], emitlight[i] ); + } + for ( j = 0; j < NUM_BUMP_VECTS+1; j++ ) + { + VectorFill( addlight[ i ].light[j], 0 ); + } + } +} + +/* +============= +GatherLight + +Get light from other patches + Run multi-threaded +============= +*/ + +#ifdef _WIN32 +#pragma warning (disable:4701) +#endif + +extern void GetBumpNormals( const float* sVect, const float* tVect, const Vector& flatNormal, + const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] ); + + +void PreGetBumpNormalsForDisp( texinfo_t *pTexinfo, Vector &vecU, Vector &vecV, Vector &vecNormal ) +{ + Vector vecTexU( pTexinfo->textureVecsTexelsPerWorldUnits[0][0], pTexinfo->textureVecsTexelsPerWorldUnits[0][1], pTexinfo->textureVecsTexelsPerWorldUnits[0][2] ); + Vector vecTexV( pTexinfo->textureVecsTexelsPerWorldUnits[1][0], pTexinfo->textureVecsTexelsPerWorldUnits[1][1], pTexinfo->textureVecsTexelsPerWorldUnits[1][2] ); + Vector vecLightU( pTexinfo->lightmapVecsLuxelsPerWorldUnits[0][0], pTexinfo->lightmapVecsLuxelsPerWorldUnits[0][1], pTexinfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); + Vector vecLightV( pTexinfo->lightmapVecsLuxelsPerWorldUnits[1][0], pTexinfo->lightmapVecsLuxelsPerWorldUnits[1][1], pTexinfo->lightmapVecsLuxelsPerWorldUnits[1][2] ); + + VectorNormalize( vecTexU ); + VectorNormalize( vecTexV ); + VectorNormalize( vecLightU ); + VectorNormalize( vecLightV ); + + bool bDoConversion = false; + if ( fabs( vecTexU.Dot( vecLightU ) ) < 0.999f ) + { + bDoConversion = true; + } + + if ( fabs( vecTexV.Dot( vecLightV ) ) < 0.999f ) + { + bDoConversion = true; + } + + if ( bDoConversion ) + { + matrix3x4_t matTex( vecTexU, vecTexV, vecNormal, vec3_origin ); + matrix3x4_t matLight( vecLightU, vecLightV, vecNormal, vec3_origin ); + matrix3x4_t matTmp; + ConcatTransforms ( matLight, matTex, matTmp ); + MatrixGetColumn( matTmp, 0, vecU ); + MatrixGetColumn( matTmp, 1, vecV ); + MatrixGetColumn( matTmp, 2, vecNormal ); + + Assert( fabs( vecTexU.Dot( vecTexV ) ) <= 0.001f ); + return; + } + + vecU = vecTexU; + vecV = vecTexV; +} + +void GatherLight (int threadnum, void *pUserData) +{ + int i, j, k; + transfer_t *trans; + int num; + CPatch *patch; + Vector sum, v; + + while (1) + { + j = GetThreadWork (); + if (j == -1) + break; + + patch = &g_Patches[j]; + + trans = patch->transfers; + num = patch->numtransfers; + if ( patch->needsBumpmap ) + { + Vector delta; + Vector bumpSum[NUM_BUMP_VECTS+1]; + Vector normals[NUM_BUMP_VECTS+1]; + + // Disps + bool bDisp = ( g_pFaces[patch->faceNumber].dispinfo != -1 ); + if ( bDisp ) + { + normals[0] = patch->normal; + texinfo_t *pTexinfo = &texinfo[g_pFaces[patch->faceNumber].texinfo]; + Vector vecTexU, vecTexV; + PreGetBumpNormalsForDisp( pTexinfo, vecTexU, vecTexV, normals[0] ); + + // use facenormal along with the smooth normal to build the three bump map vectors + GetBumpNormals( vecTexU, vecTexV, normals[0], normals[0], &normals[1] ); + } + else + { + GetPhongNormal( patch->faceNumber, patch->origin, normals[0] ); + + texinfo_t *pTexinfo = &texinfo[g_pFaces[patch->faceNumber].texinfo]; + // use facenormal along with the smooth normal to build the three bump map vectors + GetBumpNormals( pTexinfo->textureVecsTexelsPerWorldUnits[0], + pTexinfo->textureVecsTexelsPerWorldUnits[1], patch->normal, + normals[0], &normals[1] ); + } + + // force the base lightmap to use the flat normal instead of the phong normal + // FIXME: why does the patch not use the phong normal? + normals[0] = patch->normal; + + for ( i = 0; i < NUM_BUMP_VECTS+1; i++ ) + { + VectorFill( bumpSum[i], 0 ); + } + + float dot; + for (k=0 ; kpatch]; + + // get vector to other patch + VectorSubtract (patch2->origin, patch->origin, delta); + VectorNormalize (delta); + // find light emitted from other patch + for(i=0; i<3; i++) + { + v[i] = emitlight[trans->patch][i] * patch2->reflectivity[i]; + } + // remove normal already factored into transfer steradian + float scale = 1.0f / DotProduct (delta, patch->normal); + VectorScale( v, trans->transfer * scale, v ); + + Vector bumpTransfer; + for ( i = 0; i < NUM_BUMP_VECTS+1; i++ ) + { + dot = DotProduct( delta, normals[i] ); + if ( dot <= 0 ) + { +// Assert( i > 0 ); // if this hits, then the transfer shouldn't be here. It doesn't face the flat normal of this face! + continue; + } + bumpTransfer = v * dot; + VectorAdd( bumpSum[i], bumpTransfer, bumpSum[i] ); + } + } + for ( i = 0; i < NUM_BUMP_VECTS+1; i++ ) + { + VectorCopy( bumpSum[i], addlight[j].light[i] ); + } + } + else + { + VectorFill( sum, 0 ); + for (k=0 ; kpatch][i] * g_Patches[trans->patch].reflectivity[i]; + } + VectorScale( v, trans->transfer, v ); + VectorAdd( sum, v, sum ); + } + VectorCopy( sum, addlight[j].light[0] ); + } + } +} + +#ifdef _WIN32 +#pragma warning (default:4701) +#endif + + +/* +============= +BounceLight +============= +*/ +void BounceLight (void) +{ + unsigned i; + Vector added; + char name[64]; + qboolean bouncing = numbounce > 0; + + unsigned int uiPatchCount = g_Patches.Size(); + for (i=0 ; iOpen( "lightemit.txt", "w" ); + + unsigned int uiPatchCount = g_Patches.Size(); + for (i=0 ; iClose( dFp ); + + for (i=0; iemitlight to receiver->addlight + unsigned int uiPatchCount = g_Patches.Size(); + RunThreadsOn (uiPatchCount, true, GatherLight); + // move newly received light (addlight) to light to be sent out (emitlight) + // start at children and pull light up to parents + // light is always received to leaf patches + CollectLight( added ); + + qprintf ("\tBounce #%i added RGB(%.0f, %.0f, %.0f)\n", i+1, added[0], added[1], added[2] ); + + if ( i+1 == numbounce || (added[0] < 1.0 && added[1] < 1.0 && added[2] < 1.0) ) + bouncing = false; + + i++; + if ( g_bDumpPatches && !bouncing && i != 1) + { + sprintf (name, "bounce%i.txt", i); + WriteWorld (name, 0); + } + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Counts the number of clusters in a map with no visibility +// Output : int +//----------------------------------------------------------------------------- +int CountClusters( void ) +{ + int clusterCount = 0; + + for ( int i = 0; i < numleafs; i++ ) + { + if ( dleafs[i].cluster > clusterCount ) + clusterCount = dleafs[i].cluster; + } + + return clusterCount + 1; +} + + +/* +============= +RadWorld +============= +*/ +void RadWorld_Start() +{ + unsigned i; + + if (luxeldensity < 1.0) + { + // Remember the old lightmap vectors. + float oldLightmapVecs[MAX_MAP_TEXINFO][2][4]; + for (i = 0; i < texinfo.Count(); i++) + { + for( int j=0; j < 2; j++ ) + { + for( int k=0; k < 3; k++ ) + { + oldLightmapVecs[i][j][k] = texinfo[i].lightmapVecsLuxelsPerWorldUnits[j][k]; + } + } + } + + // rescale luxels to be no denser than "luxeldensity" + for (i = 0; i < texinfo.Count(); i++) + { + texinfo_t *tx = &texinfo[i]; + + for (int j = 0; j < 2; j++ ) + { + Vector tmp( tx->lightmapVecsLuxelsPerWorldUnits[j][0], tx->lightmapVecsLuxelsPerWorldUnits[j][1], tx->lightmapVecsLuxelsPerWorldUnits[j][2] ); + float scale = VectorNormalize( tmp ); + // only rescale them if the current scale is "tighter" than the desired scale + // FIXME: since this writes out to the BSP file every run, once it's set high it can't be reset + // to a lower value. + if (fabs( scale ) > luxeldensity) + { + if (scale < 0) + { + scale = -luxeldensity; + } + else + { + scale = luxeldensity; + } + VectorScale( tmp, scale, tmp ); + tx->lightmapVecsLuxelsPerWorldUnits[j][0] = tmp.x; + tx->lightmapVecsLuxelsPerWorldUnits[j][1] = tmp.y; + tx->lightmapVecsLuxelsPerWorldUnits[j][2] = tmp.z; + } + } + } + + UpdateAllFaceLightmapExtents(); + } + + MakeParents (0, -1); + + BuildClusterTable(); + + // turn each face into a single patch + MakePatches (); + PairEdges (); + + // store the vertex normals calculated in PairEdges + // so that the can be written to the bsp file for + // use in the engine + SaveVertexNormals(); + + // subdivide patches to a maximum dimension + SubdividePatches (); + + // add displacement faces to cluster table + AddDispsToClusterTable(); + + // create directlights out of patches and lights + CreateDirectLights (); + + // set up sky cameras + ProcessSkyCameras(); +} + + +// This function should fill in the indices into g_pFaces[] for the faces +// with displacements that touch the specified leaf. +void STUB_GetDisplacementsTouchingLeaf( int iLeaf, CUtlVector &dispFaces ) +{ +} + + +void BuildFacesVisibleToLights( bool bAllVisible ) +{ + g_FacesVisibleToLights.SetSize( numfaces/8 + 1 ); + + if( bAllVisible ) + { + memset( g_FacesVisibleToLights.Base(), 0xFF, g_FacesVisibleToLights.Count() ); + return; + } + + // First merge all the light PVSes. + CUtlVector aggregate; + aggregate.SetSize( (dvis->numclusters/8) + 1 ); + memset( aggregate.Base(), 0, aggregate.Count() ); + + int nDWords = aggregate.Count() / 4; + int nBytes = aggregate.Count() - nDWords*4; + + for( directlight_t *dl = activelights; dl != NULL; dl = dl->next ) + { + byte *pIn = dl->pvs; + byte *pOut = aggregate.Base(); + for( int iDWord=0; iDWord < nDWords; iDWord++ ) + { + *((unsigned long*)pOut) |= *((unsigned long*)pIn); + pIn += 4; + pOut += 4; + } + + for( int iByte=0; iByte < nBytes; iByte++ ) + { + *pOut |= *pIn; + ++pOut; + ++pIn; + } + } + + + // Now tag any faces that are visible to this monster PVS. + for( int iCluster=0; iCluster < dvis->numclusters; iCluster++ ) + { + if( g_ClusterLeaves[iCluster].leafCount ) + { + if( aggregate[iCluster>>3] & (1 << (iCluster & 7)) ) + { + for ( int i = 0; i < g_ClusterLeaves[iCluster].leafCount; i++ ) + { + int iLeaf = g_ClusterLeaves[iCluster].leafs[i]; + + // Tag all the faces. + int iFace; + for( iFace=0; iFace < dleafs[iLeaf].numleaffaces; iFace++ ) + { + int index = dleafs[iLeaf].firstleafface + iFace; + index = dleaffaces[index]; + + assert( index < numfaces ); + g_FacesVisibleToLights[index >> 3] |= (1 << (index & 7)); + } + + // Fill in STUB_GetDisplacementsTouchingLeaf when it's available + // so displacements get relit. + CUtlVector dispFaces; + STUB_GetDisplacementsTouchingLeaf( iLeaf, dispFaces ); + for( iFace=0; iFace < dispFaces.Count(); iFace++ ) + { + int index = dispFaces[iFace]; + g_FacesVisibleToLights[index >> 3] |= (1 << (index & 7)); + } + } + } + } + } + + // For stats.. figure out how many faces it's going to touch. + int nFacesToProcess = 0; + for( int i=0; i < numfaces; i++ ) + { + if( g_FacesVisibleToLights[i>>3] & (1 << (i & 7)) ) + ++nFacesToProcess; + } +} + + + +void MakeAllScales (void) +{ + // determine visibility between patches + BuildVisMatrix (); + + // release visibility matrix + FreeVisMatrix (); + + Msg("transfers %d, max %d\n", total_transfer, max_transfer ); + + qprintf ("transfer lists: %5.1f megs\n" + , (float)total_transfer * sizeof(transfer_t) / (1024*1024)); +} + + +// Helper function. This can be useful to visualize the world and faces and see which face +// corresponds to which dface. +#if 0 + #include "iscratchpad3d.h" + void ScratchPad_DrawWorld() + { + IScratchPad3D *pPad = ScratchPad3D_Create(); + pPad->SetAutoFlush( false ); + + for ( int i=0; i < numfaces; i++ ) + { + dface_t *f = &g_pFaces[i]; + + // Draw the face's outline, then put text for its face index on it too. + CUtlVector points; + for ( int iEdge = 0; iEdge < f->numedges; iEdge++ ) + { + int v; + int se = dsurfedges[f->firstedge + iEdge]; + if ( se < 0 ) + v = dedges[-se].v[1]; + else + v = dedges[se].v[0]; + + dvertex_t *dv = &dvertexes[v]; + points.AddToTail( dv->point ); + } + + // Draw the outline. + Vector vCenter( 0, 0, 0 ); + for ( iEdge=0; iEdge < points.Count(); iEdge++ ) + { + pPad->DrawLine( CSPVert( points[iEdge] ), CSPVert( points[(iEdge+1)%points.Count()] ) ); + vCenter += points[iEdge]; + } + vCenter /= points.Count(); + + // Draw the text. + char str[512]; + Q_snprintf( str, sizeof( str ), "%d", i ); + + CTextParams params; + + params.m_bCentered = true; + params.m_bOutline = true; + params.m_flLetterWidth = 2; + params.m_vColor.Init( 1, 0, 0 ); + + VectorAngles( dplanes[f->planenum].normal, params.m_vAngles ); + params.m_bTwoSided = true; + + params.m_vPos = vCenter; + + pPad->DrawText( str, params ); + } + + pPad->Release(); + } +#endif + + +bool RadWorld_Go() +{ + g_iCurFace = 0; + + InitMacroTexture( source ); + + if( g_pIncremental ) + { + g_pIncremental->PrepareForLighting(); + + // Cull out faces that aren't visible to any of the lights that we're updating with. + BuildFacesVisibleToLights( false ); + } + else + { + // Mark all faces visible.. when not doing incremental lighting, it's highly + // likely that all faces are going to be touched by at least one light so don't + // waste time here. + BuildFacesVisibleToLights( true ); + } + + // build initial facelights + if (g_bUseMPI) + { + // RunThreadsOnIndividual (numfaces, true, BuildFacelights); + RunMPIBuildFacelights(); + } + else + { + RunThreadsOnIndividual (numfaces, true, BuildFacelights); + } + + // Was the process interrupted? + if( g_pIncremental && (g_iCurFace != numfaces) ) + return false; + + // Figure out the offset into lightmap data for each face. + PrecompLightmapOffsets(); + + // If we're doing incremental lighting, stop here. + if( g_pIncremental ) + { + g_pIncremental->Finalize(); + } + else + { + // free up the direct lights now that we have facelights + ExportDirectLightsToWorldLights(); + + if ( g_bDumpPatches ) + { + for( int iBump = 0; iBump < 4; ++iBump ) + { + char szName[64]; + sprintf ( szName, "bounce0_%d.txt", iBump ); + WriteWorld( szName, iBump ); + } + } + + if (numbounce > 0) + { + // allocate memory for emitlight/addlight + emitlight.SetSize( g_Patches.Size() ); + memset( emitlight.Base(), 0, g_Patches.Size() * sizeof( Vector ) ); + addlight.SetSize( g_Patches.Size() ); + memset( addlight.Base(), 0, g_Patches.Size() * sizeof( bumplights_t ) ); + + MakeAllScales (); + + // spread light around + BounceLight (); + } + + // + // displacement surface luxel accumulation (make threaded!!!) + // + StaticDispMgr()->StartTimer( "Build Patch/Sample Hash Table(s)....." ); + StaticDispMgr()->InsertSamplesDataIntoHashTable(); + StaticDispMgr()->InsertPatchSampleDataIntoHashTable(); + StaticDispMgr()->EndTimer(); + + // blend bounced light into direct light and save + VMPI_SetCurrentStage( "FinalLightFace" ); + if ( !g_bUseMPI || g_bMPIMaster ) + RunThreadsOnIndividual (numfaces, true, FinalLightFace); + + // Distribute the lighting data to workers. + VMPI_DistributeLightData(); + + Msg("FinalLightFace Done\n"); fflush(stdout); + } + + return true; +} + +// declare the sample file pointer -- the whole debug print system should +// be reworked at some point!! +FileHandle_t pFileSamples[4][4]; + +void LoadPhysicsDLL( void ) +{ + PhysicsDLLPath( "VPHYSICS.DLL" ); +} + + +void InitDumpPatchesFiles() +{ + for( int iStyle = 0; iStyle < 4; ++iStyle ) + { + for ( int iBump = 0; iBump < 4; ++iBump ) + { + char szFilename[MAX_PATH]; + sprintf( szFilename, "samples_style%d_bump%d.txt", iStyle, iBump ); + pFileSamples[iStyle][iBump] = g_pFileSystem->Open( szFilename, "w" ); + if( !pFileSamples[iStyle][iBump] ) + { + Error( "Can't open %s for -dump.\n", szFilename ); + } + } + } +} + + +void VRAD_LoadBSP( char const *pFilename ) +{ + ThreadSetDefault (); + + g_flStartTime = Plat_FloatTime(); + + if( g_bLowPriority ) + { + SetLowPriority(); + } + + strcpy( level_name, source ); + + // This must come after InitFileSystem because the file system pointer might change. + if ( g_bDumpPatches ) + InitDumpPatchesFiles(); + + // This part is just for VMPI. VMPI's file system needs the basedir in front of all filenames, + // so we prepend qdir here. + strcpy( source, ExpandPath( source ) ); + + if ( !g_bUseMPI ) + { + // Setup the logfile. + char logFile[512]; + _snprintf( logFile, sizeof(logFile), "%s.log", source ); + SetSpewFunctionLogFile( logFile ); + } + + LoadPhysicsDLL(); + + // Set the required global lights filename and try looking in qproject + strcpy( global_lights, "lights.rad" ); + if ( !g_pFileSystem->FileExists( global_lights ) ) + { + // Otherwise, try looking in the BIN directory from which we were run from + Msg( "Could not find lights.rad in %s.\nTrying VRAD BIN directory instead...\n", + global_lights ); + GetModuleFileName( NULL, global_lights, sizeof( global_lights ) ); + Q_ExtractFilePath( global_lights, global_lights, sizeof( global_lights ) ); + strcat( global_lights, "lights.rad" ); + } + + // Set the optional level specific lights filename + strcpy( level_lights, source ); + + Q_DefaultExtension( level_lights, ".rad", sizeof( level_lights ) ); + if ( !g_pFileSystem->FileExists( level_lights ) ) + *level_lights = 0; + + ReadLightFile(global_lights); // Required + if ( *designer_lights ) ReadLightFile(designer_lights); // Command-line + if ( *level_lights ) ReadLightFile(level_lights); // Optional & implied + + strcpy(incrementfile, source); + Q_DefaultExtension(incrementfile, ".r0", sizeof(incrementfile)); + Q_DefaultExtension(source, ".bsp", sizeof( source )); + + GetPlatformMapPath( source, platformPath, 0, MAX_PATH ); + + Msg( "Loading %s\n", platformPath ); + VMPI_SetCurrentStage( "LoadBSPFile" ); + LoadBSPFile (platformPath); + + // now, set whether or not static prop lighting is present + if (g_bStaticPropLighting) + g_LevelFlags |= g_bHDR? LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_HDR : LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_NONHDR; + else + { + g_LevelFlags &= ~( LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_HDR | LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_NONHDR ); + } + + // now, we need to set our face ptr depending upon hdr, and if hdr, init it + if (g_bHDR) + { + g_pFaces = dfaces_hdr; + if (numfaces_hdr==0) + { + numfaces_hdr = numfaces; + memcpy( dfaces_hdr, dfaces, numfaces*sizeof(dfaces[0]) ); + } + } + else + { + g_pFaces = dfaces; + } + + + ParseEntities (); + ExtractBrushEntityShadowCasters(); + + StaticPropMgr()->Init(); + StaticDispMgr()->Init(); + + if (!visdatasize) + { + Msg("No vis information, direct lighting only.\n"); + numbounce = 0; + ambient[0] = ambient[1] = ambient[2] = 0.1f; + dvis->numclusters = CountClusters(); + } + + // + // patches and referencing data (ensure capacity) + // + // TODO: change the maxes to the amount from the bsp!! + // +// g_Patches.EnsureCapacity( MAX_PATCHES ); + + g_FacePatches.SetSize( MAX_MAP_FACES ); + faceParents.SetSize( MAX_MAP_FACES ); + clusterChildren.SetSize( MAX_MAP_CLUSTERS ); + + int ndx; + for ( ndx = 0; ndx < MAX_MAP_FACES; ndx++ ) + { + g_FacePatches[ndx] = g_FacePatches.InvalidIndex(); + faceParents[ndx] = faceParents.InvalidIndex(); + } + + for ( ndx = 0; ndx < MAX_MAP_CLUSTERS; ndx++ ) + { + clusterChildren[ndx] = clusterChildren.InvalidIndex(); + } + + // Setup ray tracer + AddBrushesForRayTrace(); + StaticDispMgr()->AddPolysForRayTrace(); + StaticPropMgr()->AddPolysForRayTrace(); + + // Dump raytracer for glview + if ( g_bDumpRtEnv ) + WriteRTEnv("trace.txt"); + + // Build acceleration structure + printf ( "Setting up ray-trace acceleration structure... "); + float start = Plat_FloatTime(); + g_RtEnv.SetupAccelerationStructure(); + float end = Plat_FloatTime(); + printf ( "Done (%.2f seconds)\n", end-start ); + +#if 0 // To test only k-d build + exit(0); +#endif + + RadWorld_Start(); + + // Setup incremental lighting. + if( g_pIncremental ) + { + if( !g_pIncremental->Init( source, incrementfile ) ) + { + Error( "Unable to load incremental lighting file in %s.\n", incrementfile ); + return; + } + } +} + + +void VRAD_ComputeOtherLighting() +{ + // Compute lighting for the bsp file + if ( !g_bNoDetailLighting ) + { + ComputeDetailPropLighting( THREADINDEX_MAIN ); + } + + ComputePerLeafAmbientLighting(); + + // bake the static props high quality vertex lighting into the bsp + if ( !do_fast && g_bStaticPropLighting ) + { + StaticPropMgr()->ComputeLighting( THREADINDEX_MAIN ); + } +} + +extern void CloseDispLuxels(); + +void VRAD_Finish() +{ + Msg( "Ready to Finish\n" ); + fflush( stdout ); + + if ( verbose ) + { + PrintBSPFileSizes(); + } + + Msg( "Writing %s\n", platformPath ); + VMPI_SetCurrentStage( "WriteBSPFile" ); + WriteBSPFile(platformPath); + + if ( g_bDumpPatches ) + { + for ( int iStyle = 0; iStyle < 4; ++iStyle ) + { + for ( int iBump = 0; iBump < 4; ++iBump ) + { + g_pFileSystem->Close( pFileSamples[iStyle][iBump] ); + } + } + } + + CloseDispLuxels(); + + StaticPropMgr()->Shutdown(); + + double end = Plat_FloatTime(); + + char str[512]; + GetHourMinuteSecondsString( (int)( end - g_flStartTime ), str, sizeof( str ) ); + Msg( "%s elapsed\n", str ); + + ReleasePakFileLumps(); +} + + +// Run startup code like initialize mathlib (called from main() and from the +// WorldCraft interface into vrad). +void VRAD_Init() +{ + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false ); + InstallAllocationFunctions(); + InstallSpewFunction(); +} + + +int ParseCommandLine( int argc, char **argv, bool *onlydetail ) +{ + *onlydetail = false; + + // default to LDR + SetHDRMode( false ); + int i; + for( i=1 ; i 1.0) + luxeldensity = 1.0 / luxeldensity; + } + else + { + Warning("Error: expected a value after '-luxeldensity'\n" ); + return 1; + } + } + else if( !Q_stricmp( argv[i], "-low" ) ) + { + g_bLowPriority = true; + } + else if( !Q_stricmp( argv[i], "-loghash" ) ) + { + g_bLogHashData = true; + } + else if( !Q_stricmp( argv[i], "-onlydetail" ) ) + { + *onlydetail = true; + } + else if (!Q_stricmp(argv[i],"-softsun")) + { + if ( ++i < argc ) + { + g_SunAngularExtent=atof(argv[i]); + g_SunAngularExtent=sin((M_PI/180.0)*g_SunAngularExtent); + printf("sun extent=%f\n",g_SunAngularExtent); + } + else + { + Warning("Error: expected an angular extent value (0..180) '-softsun'\n" ); + return 1; + } + } + else if ( !Q_stricmp( argv[i], "-maxdispsamplesize" ) ) + { + if ( ++i < argc ) + { + g_flMaxDispSampleSize = ( float )atof( argv[i] ); + } + else + { + Warning( "Error: expected a sample size after '-maxdispsamplesize'\n" ); + return 1; + } + } + else if ( stricmp( argv[i], "-StopOnExit" ) == 0 ) + { + g_bStopOnExit = true; + } + else if ( stricmp( argv[i], "-steam" ) == 0 ) + { + } + else if ( stricmp( argv[i], "-allowdebug" ) == 0 ) + { + // Don't need to do anything, just don't error out. + } + else if ( !Q_stricmp( argv[i], CMDLINEOPTION_NOVCONFIG ) ) + { + } + else if ( !Q_stricmp( argv[i], "-vproject" ) || !Q_stricmp( argv[i], "-game" ) ) + { + ++i; + } + else if ( !Q_stricmp( argv[i], "-FullMinidumps" ) ) + { + EnableFullMinidumps( true ); + } + else if ( !Q_stricmp( argv[i], "-hdr" ) ) + { + SetHDRMode( true ); + } + else if ( !Q_stricmp( argv[i], "-ldr" ) ) + { + SetHDRMode( false ); + } + else if (!Q_stricmp(argv[i],"-maxchop")) + { + if ( ++i < argc ) + { + maxchop = (float)atof (argv[i]); + if ( maxchop < 1 ) + { + Warning("Error: expected positive value after '-maxchop'\n" ); + return 1; + } + } + else + { + Warning("Error: expected a value after '-maxchop'\n" ); + return 1; + } + } + else if (!Q_stricmp(argv[i],"-chop")) + { + if ( ++i < argc ) + { + minchop = (float)atof (argv[i]); + if ( minchop < 1 ) + { + Warning("Error: expected positive value after '-chop'\n" ); + return 1; + } + minchop = min( minchop, maxchop ); + } + else + { + Warning("Error: expected a value after '-chop'\n" ); + return 1; + } + } + else if ( !Q_stricmp( argv[i], "-dispchop" ) ) + { + if ( ++i < argc ) + { + dispchop = ( float )atof( argv[i] ); + if ( dispchop < 1.0f ) + { + Warning( "Error: expected positive value after '-dipschop'\n" ); + return 1; + } + } + else + { + Warning( "Error: expected a value after '-dispchop'\n" ); + return 1; + } + } + else if ( !Q_stricmp( argv[i], "-disppatchradius" ) ) + { + if ( ++i < argc ) + { + g_MaxDispPatchRadius = ( float )atof( argv[i] ); + if ( g_MaxDispPatchRadius < 10.0f ) + { + Warning( "Error: g_MaxDispPatchRadius < 10.0\n" ); + return 1; + } + } + else + { + Warning( "Error: expected a value after '-disppatchradius'\n" ); + return 1; + } + } + +#if ALLOWDEBUGOPTIONS + else if (!Q_stricmp(argv[i],"-scale")) + { + if ( ++i < argc ) + { + lightscale = (float)atof (argv[i]); + } + else + { + Warning("Error: expected a value after '-scale'\n" ); + return 1; + } + } + else if (!Q_stricmp(argv[i],"-ambient")) + { + if ( i+3 < argc ) + { + ambient[0] = (float)atof (argv[++i]) * 128; + ambient[1] = (float)atof (argv[++i]) * 128; + ambient[2] = (float)atof (argv[++i]) * 128; + } + else + { + Warning("Error: expected three color values after '-ambient'\n" ); + return 1; + } + } + else if (!Q_stricmp(argv[i],"-dlight")) + { + if ( ++i < argc ) + { + dlight_threshold = (float)atof (argv[i]); + } + else + { + Warning("Error: expected a value after '-dlight'\n" ); + return 1; + } + } + else if (!Q_stricmp(argv[i],"-sky")) + { + if ( ++i < argc ) + { + indirect_sun = (float)atof (argv[i]); + } + else + { + Warning("Error: expected a value after '-sky'\n" ); + return 1; + } + } + else if (!Q_stricmp(argv[i],"-notexscale")) + { + texscale = false; + } + else if (!Q_stricmp(argv[i],"-coring")) + { + if ( ++i < argc ) + { + coring = (float)atof( argv[i] ); + } + else + { + Warning("Error: expected a light threshold after '-coring'\n" ); + return 1; + } + } +#endif + // NOTE: the -mpi checks must come last here because they allow the previous argument + // to be -mpi as well. If it game before something else like -game, then if the previous + // argument was -mpi and the current argument was something valid like -game, it would skip it. + else if ( !Q_strncasecmp( argv[i], "-mpi", 4 ) || !Q_strncasecmp( argv[i-1], "-mpi", 4 ) ) + { + if ( stricmp( argv[i], "-mpi" ) == 0 ) + g_bUseMPI = true; + + // Any other args that start with -mpi are ok too. + if ( i == argc - 1 && V_stricmp( argv[i], "-mpi_ListParams" ) != 0 ) + break; + } + else + { + break; + } + } + + return i; +} + + +void PrintCommandLine( int argc, char **argv ) +{ + Warning( "Command line: " ); + for ( int z=0; z < argc; z++ ) + { + Warning( "\"%s\" ", argv[z] ); + } + Warning( "\n\n" ); +} + + +void PrintUsage( int argc, char **argv ) +{ + PrintCommandLine( argc, argv ); + + Warning( + "usage : vrad [options...] bspfile\n" + "example: vrad c:\\hl2\\hl2\\maps\\test\n" + "\n" + "Common options:\n" + "\n" + " -v (or -verbose): Turn on verbose output (also shows more command\n" + " -bounce # : Set max number of bounces (default: 100).\n" + " -fast : Quick and dirty lighting.\n" + " -fastambient : Per-leaf ambient sampling is lower quality to save compute time.\n" + " -final : High quality processing. equivalent to -extrasky 16.\n" + " -extrasky n : trace N times as many rays for indirect light and sky ambient.\n" + " -low : Run as an idle-priority process.\n" + " -mpi : Use VMPI to distribute computations.\n" + " -rederror : Show errors in red.\n" + "\n" + " -vproject : Override the VPROJECT environment variable.\n" + " -game : Same as -vproject.\n" + "\n" + "Other options:\n" + " -novconfig : Don't bring up graphical UI on vproject errors.\n" + " -dump : Write debugging .txt files.\n" + " -dumpnormals : Write normals to debug files.\n" + " -dumptrace : Write ray-tracing environment to debug files.\n" + " -threads : Control the number of threads vbsp uses (defaults to the #\n" + " or processors on your machine).\n" + " -lights : Load a lights file in addition to lights.rad and the\n" + " level lights file.\n" + " -noextra : Disable supersampling.\n" + " -debugextra : Places debugging data in lightmaps to visualize\n" + " supersampling.\n" + " -smooth # : Set the threshold for smoothing groups, in degrees\n" + " (default 45).\n" + " -dlightmap : Force direct lighting into different lightmap than\n" + " radiosity.\n" + " -stoponexit : Wait for a keypress on exit.\n" + " -mpi_pw : Use a password to choose a specific set of VMPI workers.\n" + " -nodetaillight : Don't light detail props.\n" + " -centersamples : Move sample centers.\n" + " -luxeldensity # : Rescale all luxels by the specified amount (default: 1.0).\n" + " The number specified must be less than 1.0 or it will be\n" + " ignored.\n" + " -loghash : Log the sample hash table to samplehash.txt.\n" + " -onlydetail : Only light detail props and per-leaf lighting.\n" + " -maxdispsamplesize #: Set max displacement sample size (default: 512).\n" + " -softsun : Treat the sun as an area light source of size degrees." + " Produces soft shadows.\n" + " Recommended values are between 0 and 5. Default is 0.\n" + " -FullMinidumps : Write large minidumps on crash.\n" + " -chop : Smallest number of luxel widths for a bounce patch, used on edges\n" + " -maxchop : Coarsest allowed number of luxel widths for a patch, used in face interiors\n" + "\n" + " -LargeDispSampleRadius: This can be used if there are splotches of bounced light\n" + " on terrain. The compile will take longer, but it will gather\n" + " light across a wider area.\n" + " -StaticPropLighting : generate backed static prop vertex lighting\n" + " -StaticPropPolys : Perform shadow tests of static props at polygon precision\n" + " -OnlyStaticProps : Only perform direct static prop lighting (vrad debug option)\n" + " -StaticPropNormals : when lighting static props, just show their normal vector\n" + " -textureshadows : Allows texture alpha channels to block light - rays intersecting alpha surfaces will sample the texture\n" + " -noskyboxrecurse : Turn off recursion into 3d skybox (skybox shadows on world)\n" + " -nossprops : Globally disable self-shadowing on static props\n" + "\n" +#if 1 // Disabled for the initial SDK release with VMPI so we can get feedback from selected users. + ); +#else + " -mpi_ListParams : Show a list of VMPI parameters.\n" + "\n" + ); + + // Show VMPI parameters? + for ( int i=1; i < argc; i++ ) + { + if ( V_stricmp( argv[i], "-mpi_ListParams" ) == 0 ) + { + Warning( "VMPI-specific options:\n\n" ); + + bool bIsSDKMode = VMPI_IsSDKMode(); + for ( int i=k_eVMPICmdLineParam_FirstParam+1; i < k_eVMPICmdLineParam_LastParam; i++ ) + { + if ( (VMPI_GetParamFlags( (EVMPICmdLineParam)i ) & VMPI_PARAM_SDK_HIDDEN) && bIsSDKMode ) + continue; + + Warning( "[%s]\n", VMPI_GetParamString( (EVMPICmdLineParam)i ) ); + Warning( VMPI_GetParamHelpString( (EVMPICmdLineParam)i ) ); + Warning( "\n\n" ); + } + break; + } + } +#endif +} + + +int RunVRAD( int argc, char **argv ) +{ +#if defined(_MSC_VER) && ( _MSC_VER >= 1310 ) + Msg("Valve Software - vrad.exe SSE (" __DATE__ ")\n" ); +#else + Msg("Valve Software - vrad.exe (" __DATE__ ")\n" ); +#endif + + Msg("\n Valve Radiosity Simulator \n"); + + verbose = true; // Originally FALSE + + bool onlydetail; + int i = ParseCommandLine( argc, argv, &onlydetail ); + if (i != argc - 1) + { + PrintUsage( argc, argv ); + DeleteCmdLine( argc, argv ); + CmdLib_Exit( 1 ); + } + + VRAD_LoadBSP( argv[i] ); + + if ( (! onlydetail) && (! g_bOnlyStaticProps ) ) + { + RadWorld_Go(); + } + + VRAD_ComputeOtherLighting(); + + VRAD_Finish(); + + VMPI_SetCurrentStage( "master done" ); + + DeleteCmdLine( argc, argv ); + CmdLib_Cleanup(); + return 0; +} + + +int VRAD_Main(int argc, char **argv) +{ + g_pFileSystem = NULL; // Safeguard against using it before it's properly initialized. + + VRAD_Init(); + + // This must come first. + VRAD_SetupMPI( argc, argv ); + + // Initialize the filesystem, so additional commandline options can be loaded + Q_StripExtension( argv[ argc - 1 ], source, sizeof( source ) ); + CmdLib_InitFileSystem( argv[ argc - 1 ] ); + Q_FileBase( source, source, sizeof( source ) ); + +#if !defined( _DEBUG ) + if ( g_bUseMPI && !g_bMPIMaster ) + { + SetupToolsMinidumpHandler( VMPI_ExceptionFilter ); + } + else +#endif + { + LoadCmdLineFromFile( argc, argv, source, "vrad" ); // Don't do this if we're a VMPI worker.. + SetupDefaultToolsMinidumpHandler(); + } + + return RunVRAD( argc, argv ); +} + + + + + diff --git a/mp/src/utils/vrad/vrad.h b/mp/src/utils/vrad/vrad.h index b39d66c8..95fcd151 100644 --- a/mp/src/utils/vrad/vrad.h +++ b/mp/src/utils/vrad/vrad.h @@ -1,610 +1,610 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef VRAD_H -#define VRAD_H -#pragma once - - -#include "commonmacros.h" -#include "worldsize.h" -#include "cmdlib.h" -#include "mathlib/mathlib.h" -#include "bsplib.h" -#include "polylib.h" -#include "threads.h" -#include "builddisp.h" -#include "VRAD_DispColl.h" -#include "UtlMemory.h" -#include "UtlHash.h" -#include "utlvector.h" -#include "iincremental.h" -#include "raytrace.h" - - -#ifdef _WIN32 -#include -#endif - -#include -#include - -#pragma warning(disable: 4142 4028) -#include -#pragma warning(default: 4142 4028) - -#include -#include -#include - - -// Can remove these options if they don't generate problems. -//#define SAMPLEHASH_USE_AREA_PATCHES // Add patches to sample hash based on their AABB instead of as a single point. -#define SAMPLEHASH_QUERY_ONCE // Big optimization - causes way less sample hash queries. - -extern float dispchop; // "-dispchop" tightest number of luxel widths for a patch, used on edges -extern float g_MaxDispPatchRadius; - -//----------------------------------------------------------------------------- -// forward declarations -//----------------------------------------------------------------------------- - -struct Ray_t; - -#define TRANSFER_EPSILON 0.0000001 - -struct directlight_t -{ - int index; - - directlight_t *next; - dworldlight_t light; - - byte *pvs; // accumulated domain of the light - int facenum; // domain of attached lights - int texdata; // texture source of traced lights - - Vector snormal; - Vector tnormal; - float sscale; - float tscale; - float soffset; - float toffset; - - int dorecalc; // position, vector, spot angle, etc. - IncrementalLightID m_IncrementalID; - - // hard-falloff lights (lights that fade to an actual zero). between m_flStartFadeDistance and - // m_flEndFadeDistance, a smoothstep to zero will be done, so that the light goes to zero at - // the end. - float m_flStartFadeDistance; - float m_flEndFadeDistance; - float m_flCapDist; // max distance to feed in - - directlight_t(void) - { - m_flEndFadeDistance = -1.0; // end= 0 && - m_vecLighting.y >= 0 && - m_vecLighting.z >= 0 && - m_vecLighting.x < 1e10 && - m_vecLighting.y < 1e10 && - m_vecLighting.z < 1e10 ); - } - - FORCEINLINE void Zero( void ) - { - m_vecLighting.Init( 0, 0, 0 ); - m_flDirectSunAmount = 0.0; - } - - FORCEINLINE void Scale( float m_flScale ) - { - m_vecLighting *= m_flScale; - m_flDirectSunAmount *= m_flScale; - } - - FORCEINLINE void AddWeighted( LightingValue_t const &src, float flWeight ) - { - m_vecLighting += flWeight * src.m_vecLighting; - m_flDirectSunAmount += flWeight * src.m_flDirectSunAmount; - } - - FORCEINLINE void AddWeighted( Vector const &src, float flWeight ) - { - m_vecLighting += flWeight * src; - } - - FORCEINLINE float Intensity( void ) const - { - return m_vecLighting.x + m_vecLighting.y + m_vecLighting.z; - } - - FORCEINLINE void AddLight( float flAmount, Vector const &vecColor, float flSunAmount = 0.0 ) - { - VectorMA( m_vecLighting, flAmount, vecColor, m_vecLighting ); - m_flDirectSunAmount += flSunAmount; - Assert( this->IsValid() ); - } - - - FORCEINLINE void AddLight( LightingValue_t const &src ) - { - m_vecLighting += src.m_vecLighting; - m_flDirectSunAmount += src.m_flDirectSunAmount; - Assert( this->IsValid() ); - } - - FORCEINLINE void Init( float x, float y, float z ) - { - m_vecLighting.Init( x, y, z ); - m_flDirectSunAmount = 0.0; - } - - -}; - - -#define MAX_PATCHES (4*65536) - -struct CPatch -{ - winding_t *winding; - Vector mins, maxs, face_mins, face_maxs; - - Vector origin; // adjusted off face by face normal - - dplane_t *plane; // plane (corrected for facing) - - unsigned short m_IterationKey; // Used to prevent touching the same patch multiple times in the same query. - // See IncrementPatchIterationKey(). - - // these are packed into one dword - unsigned int normalMajorAxis : 2; // the major axis of base face normal - unsigned int sky : 1; - unsigned int needsBumpmap : 1; - unsigned int pad : 28; - - Vector normal; // adjusted for phong shading - - float planeDist; // Fixes up patch planes for brush models with an origin brush - - float chop; // smallest acceptable width of patch face - float luxscale; // average luxels per world coord - float scale[2]; // Scaling of texture in s & t - - bumplights_t totallight; // accumulated by radiosity - // does NOT include light - // accounted for by direct lighting - Vector baselight; // emissivity only - float basearea; // surface per area per baselight instance - - Vector directlight; // direct light value - float area; - - Vector reflectivity; // Average RGB of texture, modified by material type. - - Vector samplelight; - float samplearea; // for averaging direct light - int faceNumber; - int clusterNumber; - - int parent; // patch index of parent - int child1; // patch index for children - int child2; - - int ndxNext; // next patch index in face - int ndxNextParent; // next parent patch index in face - int ndxNextClusterChild; // next terminal child index in cluster -// struct patch_s *next; // next in face -// struct patch_s *nextparent; // next in face -// struct patch_s *nextclusterchild; // next terminal child in cluster - - int numtransfers; - transfer_t *transfers; - - short indices[3]; // displacement use these for subdivision -}; - - -extern CUtlVector g_Patches; -extern CUtlVector g_FacePatches; // constains all patches, children first -extern CUtlVector faceParents; // contains only root patches, use next parent to iterate -extern CUtlVector clusterChildren; - - -struct sky_camera_t -{ - Vector origin; - float world_to_sky; - float sky_to_world; - int area; -}; - -extern int num_sky_cameras; -extern sky_camera_t sky_cameras[MAX_MAP_AREAS]; -extern int area_sky_cameras[MAX_MAP_AREAS]; -void ProcessSkyCameras(); - -extern entity_t *face_entity[MAX_MAP_FACES]; -extern Vector face_offset[MAX_MAP_FACES]; // for rotating bmodels -extern Vector face_centroids[MAX_MAP_EDGES]; -extern int leafparents[MAX_MAP_LEAFS]; -extern int nodeparents[MAX_MAP_NODES]; -extern float lightscale; -extern float dlight_threshold; -extern float coring; -extern qboolean g_bDumpPatches; -extern bool bRed2Black; -extern bool g_bNoSkyRecurse; -extern bool bDumpNormals; -extern bool g_bFastAmbient; -extern float maxchop; -extern FileHandle_t pFileSamples[4][4]; -extern qboolean g_bLowPriority; -extern qboolean do_fast; -extern bool g_bInterrupt; // Was used with background lighting in WC. Tells VRAD to stop lighting. -extern IIncremental *g_pIncremental; // null if not doing incremental lighting - -extern float g_flSkySampleScale; // extra sampling factor for indirect light - -extern bool g_bLargeDispSampleRadius; -extern bool g_bStaticPropPolys; -extern bool g_bTextureShadows; -extern bool g_bShowStaticPropNormals; -extern bool g_bDisablePropSelfShadowing; - -extern CUtlVector g_NonShadowCastingMaterialStrings; -extern void ForceTextureShadowsOnModel( const char *pModelName ); -extern bool IsModelTextureShadowsForced( const char *pModelName ); - -// Raytracing - -#define TRACE_ID_SKY 0x01000000 // sky face ray blocker -#define TRACE_ID_OPAQUE 0x02000000 // everyday light blocking face -#define TRACE_ID_STATICPROP 0x04000000 // static prop - lower bits are prop ID -extern RayTracingEnvironment g_RtEnv; - -#include "mpivrad.h" - -void MakeShadowSplits (void); - -//============================================== - -void BuildVisMatrix (void); -void BuildClusterTable( void ); -void AddDispsToClusterTable( void ); -void FreeVisMatrix (void); -// qboolean CheckVisBit (unsigned int p1, unsigned int p2); -void TouchVMFFile (void); - -//============================================== - -extern qboolean do_extra; -extern qboolean do_fast; -extern qboolean do_centersamples; -extern int extrapasses; -extern Vector ambient; -extern float maxlight; -extern unsigned numbounce; -extern qboolean g_bLogHashData; -extern bool debug_extra; -extern directlight_t *activelights; -extern directlight_t *freelights; - -// because of hdr having two face lumps (light styles can cause them to be different, among other -// things), we need to always access (r/w) face data though this pointer -extern dface_t *g_pFaces; - - -extern bool g_bMPIProps; - -extern byte nodehit[MAX_MAP_NODES]; -extern float gamma_value; -extern float indirect_sun; -extern float smoothing_threshold; -extern int dlight_map; - -extern float g_flMaxDispSampleSize; -extern float g_SunAngularExtent; - -extern char source[MAX_PATH]; - -// Used by incremental lighting to trivial-reject faces. -// There is a bit in here for each face telling whether or not any of the -// active lights can see the face. -extern CUtlVector g_FacesVisibleToLights; - -void MakeTnodes (dmodel_t *bm); -void PairEdges (void); - -void SaveVertexNormals( void ); - -qboolean IsIncremental(char *filename); -int SaveIncremental(char *filename); -int PartialHead (void); -void BuildFacelights (int facenum, int threadnum); -void PrecompLightmapOffsets(); -void FinalLightFace (int threadnum, int facenum); -void PvsForOrigin (Vector& org, byte *pvs); -void ConvertRGBExp32ToRGBA8888( const ColorRGBExp32 *pSrc, unsigned char *pDst ); - -inline byte PVSCheck( const byte *pvs, int iCluster ) -{ - if ( iCluster >= 0 ) - { - return pvs[iCluster >> 3] & ( 1 << ( iCluster & 7 ) ); - } - else - { - // PointInLeaf still returns -1 for valid points sometimes and rather than - // have black samples, we assume the sample is in the PVS. - return 1; - } -} - -// outputs 1 in fractionVisible if no occlusion, 0 if full occlusion, and in-between values -void TestLine( FourVectors const& start, FourVectors const& stop, fltx4 *pFractionVisible, int static_prop_index_to_ignore=-1); - -// returns 1 if the ray sees the sky, 0 if it doesn't, and in-between values for partial coverage -void TestLine_DoesHitSky( FourVectors const& start, FourVectors const& stop, - fltx4 *pFractionVisible, bool canRecurse = true, int static_prop_to_skip=-1, bool bDoDebug = false ); - -// converts any marked brush entities to triangles for shadow casting -void ExtractBrushEntityShadowCasters ( void ); -void AddBrushesForRayTrace ( void ); - -void BaseLightForFace( dface_t *f, Vector& light, float *parea, Vector& reflectivity ); -void CreateDirectLights (void); -void GetPhongNormal( int facenum, Vector const& spot, Vector& phongnormal ); -int LightForString( char *pLight, Vector& intensity ); -void MakeTransfer( int ndxPatch1, int ndxPatch2, transfer_t *all_transfers ); -void MakeScales( int ndxPatch, transfer_t *all_transfers ); - -// Run startup code like initialize mathlib. -void VRAD_Init(); - -// Load the BSP file and prepare to do the lighting. -// This is called after any command-line parameters have been set. -void VRAD_LoadBSP( char const *pFilename ); - -int VRAD_Main(int argc, char **argv); - -// This performs an actual lighting pass. -// Returns true if the process was interrupted (with g_bInterrupt). -bool RadWorld_Go(); - -dleaf_t *PointInLeaf (Vector const& point); -int ClusterFromPoint( Vector const& point ); -winding_t *WindingFromFace (dface_t *f, Vector& origin ); - -void WriteWinding (FileHandle_t out, winding_t *w, Vector& color ); -void WriteNormal( FileHandle_t out, Vector const &nPos, Vector const &nDir, - float length, Vector const &color ); -void WriteLine( FileHandle_t out, const Vector &vecPos1, const Vector &vecPos2, const Vector &color ); -void WriteTrace( const char *pFileName, const FourRays &rays, const RayTracingResult& result ); - -#ifdef STATIC_FOG -qboolean IsFog( dface_t * f ); -#endif - -#define CONTENTS_EMPTY 0 -#define TEX_SPECIAL (SURF_SKY|SURF_NOLIGHT) - -//============================================================================= - -// trace.cpp - -bool AddDispCollTreesToWorld( void ); -int PointLeafnum( Vector const &point ); -float TraceLeafBrushes( int leafIndex, const Vector &start, const Vector &end, CBaseTrace &traceOut ); - -//============================================================================= - -// dispinfo.cpp - -struct SSE_sampleLightOutput_t -{ - fltx4 m_flDot[NUM_BUMP_VECTS+1]; - fltx4 m_flFalloff; - fltx4 m_flSunAmount; -}; - -#define GATHERLFLAGS_FORCE_FAST 1 -#define GATHERLFLAGS_IGNORE_NORMALS 2 - -// SSE Gather light stuff -void GatherSampleLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, - FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, - int nLFlags = 0, // GATHERLFLAGS_xxx - int static_prop_to_skip=-1, - float flEpsilon = 0.0 ); -//void GatherSampleSkyLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, -// FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, -// int nLFlags = 0, -// int static_prop_to_skip=-1, -// float flEpsilon = 0.0 ); -//void GatherSampleAmbientSkySSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, -// FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, -// int nLFlags = 0, // GATHERLFLAGS_xxx -// int static_prop_to_skip=-1, -// float flEpsilon = 0.0 ); -//void GatherSampleStandardLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, -// FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, -// int nLFlags = 0, // GATHERLFLAGS_xxx -// int static_prop_to_skip=-1, -// float flEpsilon = 0.0 ); - -//----------------------------------------------------------------------------- -// VRad Displacements -//----------------------------------------------------------------------------- - -struct facelight_t; -typedef struct radial_s radial_t; -struct lightinfo_t; - -// NOTE: should probably come up with a bsptreetested_t struct or something, -// see below (PropTested_t) -struct DispTested_t -{ - int m_Enum; - int *m_pTested; -}; - -class IVRadDispMgr -{ -public: - // creation/destruction - virtual void Init( void ) = 0; - virtual void Shutdown( void ) = 0; - - // "CalcPoints" - virtual bool BuildDispSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) = 0; - virtual bool BuildDispLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) = 0; - virtual bool BuildDispSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) = 0; - - // patching functions - virtual void MakePatches( void ) = 0; - virtual void SubdividePatch( int iPatch ) = 0; - - // pre "FinalLightFace" - virtual void InsertSamplesDataIntoHashTable( void ) = 0; - virtual void InsertPatchSampleDataIntoHashTable( void ) = 0; - - // "FinalLightFace" - virtual radial_t *BuildLuxelRadial( int ndxFace, int ndxStyle, bool bBump ) = 0; - virtual bool SampleRadial( int ndxFace, radial_t *pRadial, Vector const &vPos, int ndxLxl, LightingValue_t *pLightSample, int sampleCount, bool bPatch ) = 0; - virtual radial_t *BuildPatchRadial( int ndxFace, bool bBump ) = 0; - - // utility - virtual void GetDispSurfNormal( int ndxFace, Vector &pt, Vector &ptNormal, bool bInside ) = 0; - virtual void GetDispSurf( int ndxFace, CVRADDispColl **ppDispTree ) = 0; - - // bsp tree functions - virtual bool ClipRayToDisp( DispTested_t &dispTested, Ray_t const &ray ) = 0; - virtual bool ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, int ndxLeaf ) = 0; - virtual void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, - int ndxLeaf, float& dist, dface_t*& pFace, Vector2D& luxelCoord ) = 0; - virtual void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, - int ndxLeaf, float& dist, Vector *pNormal ) = 0; - virtual void StartRayTest( DispTested_t &dispTested ) = 0; - virtual void AddPolysForRayTrace() = 0; - - // general timing -- should be moved!! - virtual void StartTimer( const char *name ) = 0; - virtual void EndTimer( void ) = 0; -}; - -IVRadDispMgr *StaticDispMgr( void ); - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline bool ValidDispFace( dface_t *pFace ) -{ - if( !pFace ) { return false; } - if( pFace->dispinfo == -1 ) { return false; } - if( pFace->numedges != 4 ) { return false; } - - return true; -} - -#define SAMPLEHASH_VOXEL_SIZE 64.0f -typedef unsigned int SampleHandle_t; // the upper 16 bits = facelight index (works because max face are 65536) - // the lower 16 bits = sample index inside of facelight -struct sample_t; -struct SampleData_t -{ - unsigned short x, y, z; - CUtlVector m_Samples; -}; - -struct PatchSampleData_t -{ - unsigned short x, y, z; - CUtlVector m_ndxPatches; -}; - -UtlHashHandle_t SampleData_AddSample( sample_t *pSample, SampleHandle_t sampleHandle ); -void PatchSampleData_AddSample( CPatch *pPatch, int ndxPatch ); -unsigned short IncrementPatchIterationKey(); -void SampleData_Log( void ); - -extern CUtlHash g_SampleHashTable; -extern CUtlHash g_PatchSampleHashTable; - -extern int samplesAdded; -extern int patchSamplesAdded; - -//----------------------------------------------------------------------------- -// Computes lighting for the detail props -//----------------------------------------------------------------------------- - -void ComputeDetailPropLighting( int iThread ); -void ComputeIndirectLightingAtPoint( Vector &position, Vector &normal, Vector &outColor, - int iThread, bool force_fast = false, bool bIgnoreNormals = false ); - -//----------------------------------------------------------------------------- -// VRad static props -//----------------------------------------------------------------------------- -class IPhysicsCollision; -struct PropTested_t -{ - int m_Enum; - int* m_pTested; - IPhysicsCollision *pThreadedCollision; -}; - -class IVradStaticPropMgr -{ -public: - // methods of IStaticPropMgr - virtual void Init() = 0; - virtual void Shutdown() = 0; - virtual void ComputeLighting( int iThread ) = 0; - virtual void AddPolysForRayTrace() = 0; -}; - -//extern PropTested_t s_PropTested[MAX_TOOL_THREADS+1]; -extern DispTested_t s_DispTested[MAX_TOOL_THREADS+1]; - -IVradStaticPropMgr* StaticPropMgr(); - -extern float ComputeCoverageFromTexture( float b0, float b1, float b2, int32 hitID ); - -#endif // VRAD_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VRAD_H +#define VRAD_H +#pragma once + + +#include "commonmacros.h" +#include "worldsize.h" +#include "cmdlib.h" +#include "mathlib/mathlib.h" +#include "bsplib.h" +#include "polylib.h" +#include "threads.h" +#include "builddisp.h" +#include "VRAD_DispColl.h" +#include "UtlMemory.h" +#include "UtlHash.h" +#include "utlvector.h" +#include "iincremental.h" +#include "raytrace.h" + + +#ifdef _WIN32 +#include +#endif + +#include +#include + +#pragma warning(disable: 4142 4028) +#include +#pragma warning(default: 4142 4028) + +#include +#include +#include + + +// Can remove these options if they don't generate problems. +//#define SAMPLEHASH_USE_AREA_PATCHES // Add patches to sample hash based on their AABB instead of as a single point. +#define SAMPLEHASH_QUERY_ONCE // Big optimization - causes way less sample hash queries. + +extern float dispchop; // "-dispchop" tightest number of luxel widths for a patch, used on edges +extern float g_MaxDispPatchRadius; + +//----------------------------------------------------------------------------- +// forward declarations +//----------------------------------------------------------------------------- + +struct Ray_t; + +#define TRANSFER_EPSILON 0.0000001 + +struct directlight_t +{ + int index; + + directlight_t *next; + dworldlight_t light; + + byte *pvs; // accumulated domain of the light + int facenum; // domain of attached lights + int texdata; // texture source of traced lights + + Vector snormal; + Vector tnormal; + float sscale; + float tscale; + float soffset; + float toffset; + + int dorecalc; // position, vector, spot angle, etc. + IncrementalLightID m_IncrementalID; + + // hard-falloff lights (lights that fade to an actual zero). between m_flStartFadeDistance and + // m_flEndFadeDistance, a smoothstep to zero will be done, so that the light goes to zero at + // the end. + float m_flStartFadeDistance; + float m_flEndFadeDistance; + float m_flCapDist; // max distance to feed in + + directlight_t(void) + { + m_flEndFadeDistance = -1.0; // end= 0 && + m_vecLighting.y >= 0 && + m_vecLighting.z >= 0 && + m_vecLighting.x < 1e10 && + m_vecLighting.y < 1e10 && + m_vecLighting.z < 1e10 ); + } + + FORCEINLINE void Zero( void ) + { + m_vecLighting.Init( 0, 0, 0 ); + m_flDirectSunAmount = 0.0; + } + + FORCEINLINE void Scale( float m_flScale ) + { + m_vecLighting *= m_flScale; + m_flDirectSunAmount *= m_flScale; + } + + FORCEINLINE void AddWeighted( LightingValue_t const &src, float flWeight ) + { + m_vecLighting += flWeight * src.m_vecLighting; + m_flDirectSunAmount += flWeight * src.m_flDirectSunAmount; + } + + FORCEINLINE void AddWeighted( Vector const &src, float flWeight ) + { + m_vecLighting += flWeight * src; + } + + FORCEINLINE float Intensity( void ) const + { + return m_vecLighting.x + m_vecLighting.y + m_vecLighting.z; + } + + FORCEINLINE void AddLight( float flAmount, Vector const &vecColor, float flSunAmount = 0.0 ) + { + VectorMA( m_vecLighting, flAmount, vecColor, m_vecLighting ); + m_flDirectSunAmount += flSunAmount; + Assert( this->IsValid() ); + } + + + FORCEINLINE void AddLight( LightingValue_t const &src ) + { + m_vecLighting += src.m_vecLighting; + m_flDirectSunAmount += src.m_flDirectSunAmount; + Assert( this->IsValid() ); + } + + FORCEINLINE void Init( float x, float y, float z ) + { + m_vecLighting.Init( x, y, z ); + m_flDirectSunAmount = 0.0; + } + + +}; + + +#define MAX_PATCHES (4*65536) + +struct CPatch +{ + winding_t *winding; + Vector mins, maxs, face_mins, face_maxs; + + Vector origin; // adjusted off face by face normal + + dplane_t *plane; // plane (corrected for facing) + + unsigned short m_IterationKey; // Used to prevent touching the same patch multiple times in the same query. + // See IncrementPatchIterationKey(). + + // these are packed into one dword + unsigned int normalMajorAxis : 2; // the major axis of base face normal + unsigned int sky : 1; + unsigned int needsBumpmap : 1; + unsigned int pad : 28; + + Vector normal; // adjusted for phong shading + + float planeDist; // Fixes up patch planes for brush models with an origin brush + + float chop; // smallest acceptable width of patch face + float luxscale; // average luxels per world coord + float scale[2]; // Scaling of texture in s & t + + bumplights_t totallight; // accumulated by radiosity + // does NOT include light + // accounted for by direct lighting + Vector baselight; // emissivity only + float basearea; // surface per area per baselight instance + + Vector directlight; // direct light value + float area; + + Vector reflectivity; // Average RGB of texture, modified by material type. + + Vector samplelight; + float samplearea; // for averaging direct light + int faceNumber; + int clusterNumber; + + int parent; // patch index of parent + int child1; // patch index for children + int child2; + + int ndxNext; // next patch index in face + int ndxNextParent; // next parent patch index in face + int ndxNextClusterChild; // next terminal child index in cluster +// struct patch_s *next; // next in face +// struct patch_s *nextparent; // next in face +// struct patch_s *nextclusterchild; // next terminal child in cluster + + int numtransfers; + transfer_t *transfers; + + short indices[3]; // displacement use these for subdivision +}; + + +extern CUtlVector g_Patches; +extern CUtlVector g_FacePatches; // constains all patches, children first +extern CUtlVector faceParents; // contains only root patches, use next parent to iterate +extern CUtlVector clusterChildren; + + +struct sky_camera_t +{ + Vector origin; + float world_to_sky; + float sky_to_world; + int area; +}; + +extern int num_sky_cameras; +extern sky_camera_t sky_cameras[MAX_MAP_AREAS]; +extern int area_sky_cameras[MAX_MAP_AREAS]; +void ProcessSkyCameras(); + +extern entity_t *face_entity[MAX_MAP_FACES]; +extern Vector face_offset[MAX_MAP_FACES]; // for rotating bmodels +extern Vector face_centroids[MAX_MAP_EDGES]; +extern int leafparents[MAX_MAP_LEAFS]; +extern int nodeparents[MAX_MAP_NODES]; +extern float lightscale; +extern float dlight_threshold; +extern float coring; +extern qboolean g_bDumpPatches; +extern bool bRed2Black; +extern bool g_bNoSkyRecurse; +extern bool bDumpNormals; +extern bool g_bFastAmbient; +extern float maxchop; +extern FileHandle_t pFileSamples[4][4]; +extern qboolean g_bLowPriority; +extern qboolean do_fast; +extern bool g_bInterrupt; // Was used with background lighting in WC. Tells VRAD to stop lighting. +extern IIncremental *g_pIncremental; // null if not doing incremental lighting + +extern float g_flSkySampleScale; // extra sampling factor for indirect light + +extern bool g_bLargeDispSampleRadius; +extern bool g_bStaticPropPolys; +extern bool g_bTextureShadows; +extern bool g_bShowStaticPropNormals; +extern bool g_bDisablePropSelfShadowing; + +extern CUtlVector g_NonShadowCastingMaterialStrings; +extern void ForceTextureShadowsOnModel( const char *pModelName ); +extern bool IsModelTextureShadowsForced( const char *pModelName ); + +// Raytracing + +#define TRACE_ID_SKY 0x01000000 // sky face ray blocker +#define TRACE_ID_OPAQUE 0x02000000 // everyday light blocking face +#define TRACE_ID_STATICPROP 0x04000000 // static prop - lower bits are prop ID +extern RayTracingEnvironment g_RtEnv; + +#include "mpivrad.h" + +void MakeShadowSplits (void); + +//============================================== + +void BuildVisMatrix (void); +void BuildClusterTable( void ); +void AddDispsToClusterTable( void ); +void FreeVisMatrix (void); +// qboolean CheckVisBit (unsigned int p1, unsigned int p2); +void TouchVMFFile (void); + +//============================================== + +extern qboolean do_extra; +extern qboolean do_fast; +extern qboolean do_centersamples; +extern int extrapasses; +extern Vector ambient; +extern float maxlight; +extern unsigned numbounce; +extern qboolean g_bLogHashData; +extern bool debug_extra; +extern directlight_t *activelights; +extern directlight_t *freelights; + +// because of hdr having two face lumps (light styles can cause them to be different, among other +// things), we need to always access (r/w) face data though this pointer +extern dface_t *g_pFaces; + + +extern bool g_bMPIProps; + +extern byte nodehit[MAX_MAP_NODES]; +extern float gamma_value; +extern float indirect_sun; +extern float smoothing_threshold; +extern int dlight_map; + +extern float g_flMaxDispSampleSize; +extern float g_SunAngularExtent; + +extern char source[MAX_PATH]; + +// Used by incremental lighting to trivial-reject faces. +// There is a bit in here for each face telling whether or not any of the +// active lights can see the face. +extern CUtlVector g_FacesVisibleToLights; + +void MakeTnodes (dmodel_t *bm); +void PairEdges (void); + +void SaveVertexNormals( void ); + +qboolean IsIncremental(char *filename); +int SaveIncremental(char *filename); +int PartialHead (void); +void BuildFacelights (int facenum, int threadnum); +void PrecompLightmapOffsets(); +void FinalLightFace (int threadnum, int facenum); +void PvsForOrigin (Vector& org, byte *pvs); +void ConvertRGBExp32ToRGBA8888( const ColorRGBExp32 *pSrc, unsigned char *pDst ); + +inline byte PVSCheck( const byte *pvs, int iCluster ) +{ + if ( iCluster >= 0 ) + { + return pvs[iCluster >> 3] & ( 1 << ( iCluster & 7 ) ); + } + else + { + // PointInLeaf still returns -1 for valid points sometimes and rather than + // have black samples, we assume the sample is in the PVS. + return 1; + } +} + +// outputs 1 in fractionVisible if no occlusion, 0 if full occlusion, and in-between values +void TestLine( FourVectors const& start, FourVectors const& stop, fltx4 *pFractionVisible, int static_prop_index_to_ignore=-1); + +// returns 1 if the ray sees the sky, 0 if it doesn't, and in-between values for partial coverage +void TestLine_DoesHitSky( FourVectors const& start, FourVectors const& stop, + fltx4 *pFractionVisible, bool canRecurse = true, int static_prop_to_skip=-1, bool bDoDebug = false ); + +// converts any marked brush entities to triangles for shadow casting +void ExtractBrushEntityShadowCasters ( void ); +void AddBrushesForRayTrace ( void ); + +void BaseLightForFace( dface_t *f, Vector& light, float *parea, Vector& reflectivity ); +void CreateDirectLights (void); +void GetPhongNormal( int facenum, Vector const& spot, Vector& phongnormal ); +int LightForString( char *pLight, Vector& intensity ); +void MakeTransfer( int ndxPatch1, int ndxPatch2, transfer_t *all_transfers ); +void MakeScales( int ndxPatch, transfer_t *all_transfers ); + +// Run startup code like initialize mathlib. +void VRAD_Init(); + +// Load the BSP file and prepare to do the lighting. +// This is called after any command-line parameters have been set. +void VRAD_LoadBSP( char const *pFilename ); + +int VRAD_Main(int argc, char **argv); + +// This performs an actual lighting pass. +// Returns true if the process was interrupted (with g_bInterrupt). +bool RadWorld_Go(); + +dleaf_t *PointInLeaf (Vector const& point); +int ClusterFromPoint( Vector const& point ); +winding_t *WindingFromFace (dface_t *f, Vector& origin ); + +void WriteWinding (FileHandle_t out, winding_t *w, Vector& color ); +void WriteNormal( FileHandle_t out, Vector const &nPos, Vector const &nDir, + float length, Vector const &color ); +void WriteLine( FileHandle_t out, const Vector &vecPos1, const Vector &vecPos2, const Vector &color ); +void WriteTrace( const char *pFileName, const FourRays &rays, const RayTracingResult& result ); + +#ifdef STATIC_FOG +qboolean IsFog( dface_t * f ); +#endif + +#define CONTENTS_EMPTY 0 +#define TEX_SPECIAL (SURF_SKY|SURF_NOLIGHT) + +//============================================================================= + +// trace.cpp + +bool AddDispCollTreesToWorld( void ); +int PointLeafnum( Vector const &point ); +float TraceLeafBrushes( int leafIndex, const Vector &start, const Vector &end, CBaseTrace &traceOut ); + +//============================================================================= + +// dispinfo.cpp + +struct SSE_sampleLightOutput_t +{ + fltx4 m_flDot[NUM_BUMP_VECTS+1]; + fltx4 m_flFalloff; + fltx4 m_flSunAmount; +}; + +#define GATHERLFLAGS_FORCE_FAST 1 +#define GATHERLFLAGS_IGNORE_NORMALS 2 + +// SSE Gather light stuff +void GatherSampleLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags = 0, // GATHERLFLAGS_xxx + int static_prop_to_skip=-1, + float flEpsilon = 0.0 ); +//void GatherSampleSkyLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, +// FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, +// int nLFlags = 0, +// int static_prop_to_skip=-1, +// float flEpsilon = 0.0 ); +//void GatherSampleAmbientSkySSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, +// FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, +// int nLFlags = 0, // GATHERLFLAGS_xxx +// int static_prop_to_skip=-1, +// float flEpsilon = 0.0 ); +//void GatherSampleStandardLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, +// FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, +// int nLFlags = 0, // GATHERLFLAGS_xxx +// int static_prop_to_skip=-1, +// float flEpsilon = 0.0 ); + +//----------------------------------------------------------------------------- +// VRad Displacements +//----------------------------------------------------------------------------- + +struct facelight_t; +typedef struct radial_s radial_t; +struct lightinfo_t; + +// NOTE: should probably come up with a bsptreetested_t struct or something, +// see below (PropTested_t) +struct DispTested_t +{ + int m_Enum; + int *m_pTested; +}; + +class IVRadDispMgr +{ +public: + // creation/destruction + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + + // "CalcPoints" + virtual bool BuildDispSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) = 0; + virtual bool BuildDispLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) = 0; + virtual bool BuildDispSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) = 0; + + // patching functions + virtual void MakePatches( void ) = 0; + virtual void SubdividePatch( int iPatch ) = 0; + + // pre "FinalLightFace" + virtual void InsertSamplesDataIntoHashTable( void ) = 0; + virtual void InsertPatchSampleDataIntoHashTable( void ) = 0; + + // "FinalLightFace" + virtual radial_t *BuildLuxelRadial( int ndxFace, int ndxStyle, bool bBump ) = 0; + virtual bool SampleRadial( int ndxFace, radial_t *pRadial, Vector const &vPos, int ndxLxl, LightingValue_t *pLightSample, int sampleCount, bool bPatch ) = 0; + virtual radial_t *BuildPatchRadial( int ndxFace, bool bBump ) = 0; + + // utility + virtual void GetDispSurfNormal( int ndxFace, Vector &pt, Vector &ptNormal, bool bInside ) = 0; + virtual void GetDispSurf( int ndxFace, CVRADDispColl **ppDispTree ) = 0; + + // bsp tree functions + virtual bool ClipRayToDisp( DispTested_t &dispTested, Ray_t const &ray ) = 0; + virtual bool ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, int ndxLeaf ) = 0; + virtual void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf, float& dist, dface_t*& pFace, Vector2D& luxelCoord ) = 0; + virtual void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf, float& dist, Vector *pNormal ) = 0; + virtual void StartRayTest( DispTested_t &dispTested ) = 0; + virtual void AddPolysForRayTrace() = 0; + + // general timing -- should be moved!! + virtual void StartTimer( const char *name ) = 0; + virtual void EndTimer( void ) = 0; +}; + +IVRadDispMgr *StaticDispMgr( void ); + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline bool ValidDispFace( dface_t *pFace ) +{ + if( !pFace ) { return false; } + if( pFace->dispinfo == -1 ) { return false; } + if( pFace->numedges != 4 ) { return false; } + + return true; +} + +#define SAMPLEHASH_VOXEL_SIZE 64.0f +typedef unsigned int SampleHandle_t; // the upper 16 bits = facelight index (works because max face are 65536) + // the lower 16 bits = sample index inside of facelight +struct sample_t; +struct SampleData_t +{ + unsigned short x, y, z; + CUtlVector m_Samples; +}; + +struct PatchSampleData_t +{ + unsigned short x, y, z; + CUtlVector m_ndxPatches; +}; + +UtlHashHandle_t SampleData_AddSample( sample_t *pSample, SampleHandle_t sampleHandle ); +void PatchSampleData_AddSample( CPatch *pPatch, int ndxPatch ); +unsigned short IncrementPatchIterationKey(); +void SampleData_Log( void ); + +extern CUtlHash g_SampleHashTable; +extern CUtlHash g_PatchSampleHashTable; + +extern int samplesAdded; +extern int patchSamplesAdded; + +//----------------------------------------------------------------------------- +// Computes lighting for the detail props +//----------------------------------------------------------------------------- + +void ComputeDetailPropLighting( int iThread ); +void ComputeIndirectLightingAtPoint( Vector &position, Vector &normal, Vector &outColor, + int iThread, bool force_fast = false, bool bIgnoreNormals = false ); + +//----------------------------------------------------------------------------- +// VRad static props +//----------------------------------------------------------------------------- +class IPhysicsCollision; +struct PropTested_t +{ + int m_Enum; + int* m_pTested; + IPhysicsCollision *pThreadedCollision; +}; + +class IVradStaticPropMgr +{ +public: + // methods of IStaticPropMgr + virtual void Init() = 0; + virtual void Shutdown() = 0; + virtual void ComputeLighting( int iThread ) = 0; + virtual void AddPolysForRayTrace() = 0; +}; + +//extern PropTested_t s_PropTested[MAX_TOOL_THREADS+1]; +extern DispTested_t s_DispTested[MAX_TOOL_THREADS+1]; + +IVradStaticPropMgr* StaticPropMgr(); + +extern float ComputeCoverageFromTexture( float b0, float b1, float b2, int32 hitID ); + +#endif // VRAD_H diff --git a/mp/src/utils/vrad/vrad_dispcoll.cpp b/mp/src/utils/vrad/vrad_dispcoll.cpp index 7e788d07..2edd1ca9 100644 --- a/mp/src/utils/vrad/vrad_dispcoll.cpp +++ b/mp/src/utils/vrad/vrad_dispcoll.cpp @@ -1,1080 +1,1080 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "vrad.h" -#include "VRAD_DispColl.h" -#include "DispColl_Common.h" -#include "radial.h" -#include "CollisionUtils.h" -#include "tier0\dbg.h" - -#define SAMPLE_BBOX_SLOP 5.0f -#define TRIEDGE_EPSILON 0.001f - -float g_flMaxDispSampleSize = 512.0f; - -static FileHandle_t pDispFile = FILESYSTEM_INVALID_HANDLE; - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -CVRADDispColl::CVRADDispColl() -{ - m_iParent = -1; - - m_flSampleRadius2 = 0.0f; - m_flPatchSampleRadius2 = 0.0f; - - m_flSampleWidth = 0.0f; - m_flSampleHeight = 0.0f; - - m_aLuxelCoords.Purge(); - m_aVertNormals.Purge(); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -CVRADDispColl::~CVRADDispColl() -{ - m_aLuxelCoords.Purge(); - m_aVertNormals.Purge(); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CVRADDispColl::Create( CCoreDispInfo *pDisp ) -{ - // Base class create. - if( !CDispCollTree::Create( pDisp ) ) - return false; - - // Allocate VRad specific memory. - m_aLuxelCoords.SetSize( GetSize() ); - m_aVertNormals.SetSize( GetSize() ); - - // VRad specific base surface data. - CCoreDispSurface *pSurf = pDisp->GetSurface(); - m_iParent = pSurf->GetHandle(); - - // VRad specific displacement surface data. - for ( int iVert = 0; iVert < m_aVerts.Count(); ++iVert ) - { - pDisp->GetNormal( iVert, m_aVertNormals[iVert] ); - pDisp->GetLuxelCoord( 0, iVert, m_aLuxelCoords[iVert] ); - } - - // Re-calculate the lightmap size (in uv) so that the luxels give - // a better world-space uniform approx. due to the non-linear nature - // of the displacement surface in uv-space - dface_t *pFace = &g_pFaces[m_iParent]; - if( pFace ) - { - CalcSampleRadius2AndBox( pFace ); - } - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CVRADDispColl::CalcSampleRadius2AndBox( dface_t *pFace ) -{ - // Get the luxel sample size. - texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; - Assert ( pTexInfo ); - if ( !pTexInfo ) - return; - - // Todo: Width = Height now, should change all the code to look at one value. - Vector vecTmp( pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][0], - pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][1], - pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); - float flWidth = 1.0f / VectorLength( vecTmp ); - float flHeight = flWidth; - - // Save off the sample width and height. - m_flSampleWidth = flWidth; - m_flSampleHeight = flHeight; - - // Calculate the sample radius squared. - float flSampleRadius = sqrt( ( ( flWidth * flWidth ) + ( flHeight * flHeight ) ) ) * 2.2f;//RADIALDIST2; - if ( flSampleRadius > g_flMaxDispSampleSize ) - { - flSampleRadius = g_flMaxDispSampleSize; - } - m_flSampleRadius2 = flSampleRadius * flSampleRadius; - - // Calculate the patch radius - the max sample edge length * the number of luxels per edge "chop." - float flSampleSize = max( m_flSampleWidth, m_flSampleHeight ); - float flPatchSampleRadius = flSampleSize * dispchop * 2.2f; - if ( flPatchSampleRadius > g_MaxDispPatchRadius ) - { - flPatchSampleRadius = g_MaxDispPatchRadius; - Warning( "Patch Sample Radius Clamped!\n" ); - } - m_flPatchSampleRadius2 = flPatchSampleRadius * flPatchSampleRadius; -} - -//----------------------------------------------------------------------------- -// Purpose: Get the min/max of the displacement surface. -//----------------------------------------------------------------------------- -void CVRADDispColl::GetSurfaceMinMax( Vector &boxMin, Vector &boxMax ) -{ - // Initialize the minimum and maximum box - boxMin = m_aVerts[0]; - boxMax = m_aVerts[0]; - - for( int i = 1; i < m_aVerts.Count(); i++ ) - { - if( m_aVerts[i].x < boxMin.x ) { boxMin.x = m_aVerts[i].x; } - if( m_aVerts[i].y < boxMin.y ) { boxMin.y = m_aVerts[i].y; } - if( m_aVerts[i].z < boxMin.z ) { boxMin.z = m_aVerts[i].z; } - - if( m_aVerts[i].x > boxMax.x ) { boxMax.x = m_aVerts[i].x; } - if( m_aVerts[i].y > boxMax.y ) { boxMax.y = m_aVerts[i].y; } - if( m_aVerts[i].z > boxMax.z ) { boxMax.z = m_aVerts[i].z; } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Find the minor projection axes based on the given normal. -//----------------------------------------------------------------------------- -void CVRADDispColl::GetMinorAxes( Vector const &vecNormal, int &nAxis0, int &nAxis1 ) -{ - nAxis0 = 0; - nAxis1 = 1; - - if( FloatMakePositive( vecNormal.x ) > FloatMakePositive( vecNormal.y ) ) - { - if( FloatMakePositive( vecNormal.x ) > FloatMakePositive( vecNormal.z ) ) - { - nAxis0 = 1; - nAxis1 = 2; - } - } - else - { - if( FloatMakePositive( vecNormal.y ) > FloatMakePositive( vecNormal.z ) ) - { - nAxis0 = 0; - nAxis1 = 2; - } - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRADDispColl::BaseFacePlaneToDispUV( Vector const &vecPlanePt, Vector2D &dispUV ) -{ - PointInQuadToBarycentric( m_vecSurfPoints[0], m_vecSurfPoints[3], m_vecSurfPoints[2], m_vecSurfPoints[1], vecPlanePt, dispUV ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRADDispColl::DispUVToSurfPoint( Vector2D const &dispUV, Vector &vecPoint, float flPushEps ) -{ - // Check to see that the point is on the surface. - if ( dispUV.x < 0.0f || dispUV.x > 1.0f || dispUV.y < 0.0f || dispUV.y > 1.0f ) - return; - - // Get the displacement power. - int nWidth = ( ( 1 << m_nPower ) + 1 ); - int nHeight = nWidth; - - // Scale the U, V coordinates to the displacement grid size. - float flU = dispUV.x * static_cast( nWidth - 1.000001f ); - float flV = dispUV.y * static_cast( nHeight - 1.000001f ); - - // Find the base U, V. - int nSnapU = static_cast( flU ); - int nSnapV = static_cast( flV ); - - // Use this to get the triangle orientation. - bool bOdd = ( ( ( nSnapV * nWidth ) + nSnapU ) % 2 == 1 ); - - // Top Left to Bottom Right - if( bOdd ) - { - DispUVToSurf_TriTLToBR( vecPoint, flPushEps, flU, flV, nSnapU, nSnapV, nWidth, nHeight ); - } - // Bottom Left to Top Right - else - { - DispUVToSurf_TriBLToTR( vecPoint, flPushEps, flU, flV, nSnapU, nSnapV, nWidth, nHeight ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CVRADDispColl::DispUVToSurf_TriTLToBR( Vector &vecPoint, float flPushEps, - float flU, float flV, int nSnapU, int nSnapV, - int nWidth, int nHeight ) -{ - int nNextU = nSnapU + 1; - int nNextV = nSnapV + 1; - if ( nNextU == nWidth) { --nNextU; } - if ( nNextV == nHeight ) { --nNextV; } - - float flFracU = flU - static_cast( nSnapU ); - float flFracV = flV - static_cast( nSnapV ); - - if( ( flFracU + flFracV ) >= ( 1.0f + TRIEDGE_EPSILON ) ) - { - int nIndices[3]; - nIndices[0] = nNextV * nWidth + nSnapU; - nIndices[1] = nNextV * nWidth + nNextU; - nIndices[2] = nSnapV * nWidth + nNextU; - - Vector edgeU = m_aVerts[nIndices[0]] - m_aVerts[nIndices[1]]; - Vector edgeV = m_aVerts[nIndices[2]] - m_aVerts[nIndices[1]]; - vecPoint = m_aVerts[nIndices[1]] + edgeU * ( 1.0f - flFracU ) + edgeV * ( 1.0f - flFracV ); - - if ( flPushEps != 0.0f ) - { - Vector vecNormal; - vecNormal = CrossProduct( edgeU, edgeV ); - VectorNormalize( vecNormal ); - vecPoint += ( vecNormal * flPushEps ); - } - } - else - { - int nIndices[3]; - nIndices[0] = nSnapV * nWidth + nSnapU; - nIndices[1] = nNextV * nWidth + nSnapU; - nIndices[2] = nSnapV * nWidth + nNextU; - - Vector edgeU = m_aVerts[nIndices[2]] - m_aVerts[nIndices[0]]; - Vector edgeV = m_aVerts[nIndices[1]] - m_aVerts[nIndices[0]]; - vecPoint = m_aVerts[nIndices[0]] + edgeU * flFracU + edgeV * flFracV; - - if ( flPushEps != 0.0f ) - { - Vector vecNormal; - vecNormal = CrossProduct( edgeU, edgeV ); - VectorNormalize( vecNormal ); - vecPoint += ( vecNormal * flPushEps ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CVRADDispColl::DispUVToSurf_TriBLToTR( Vector &vecPoint, float flPushEps, - float flU, float flV, int nSnapU, int nSnapV, - int nWidth, int nHeight ) -{ - int nNextU = nSnapU + 1; - int nNextV = nSnapV + 1; - if ( nNextU == nWidth) { --nNextU; } - if ( nNextV == nHeight ) { --nNextV; } - - float flFracU = flU - static_cast( nSnapU ); - float flFracV = flV - static_cast( nSnapV ); - - if( flFracU < flFracV ) - { - int nIndices[3]; - nIndices[0] = nSnapV * nWidth + nSnapU; - nIndices[1] = nNextV * nWidth + nSnapU; - nIndices[2] = nNextV * nWidth + nNextU; - - Vector edgeU = m_aVerts[nIndices[2]] - m_aVerts[nIndices[1]]; - Vector edgeV = m_aVerts[nIndices[0]] - m_aVerts[nIndices[1]]; - vecPoint = m_aVerts[nIndices[1]] + edgeU * flFracU + edgeV * ( 1.0f - flFracV ); - - if ( flPushEps != 0.0f ) - { - Vector vecNormal; - vecNormal = CrossProduct( edgeV, edgeU ); - VectorNormalize( vecNormal ); - vecPoint += ( vecNormal * flPushEps ); - } - } - else - { - int nIndices[3]; - nIndices[0] = nSnapV * nWidth + nSnapU; - nIndices[1] = nNextV * nWidth + nNextU; - nIndices[2] = nSnapV * nWidth + nNextU; - - Vector edgeU = m_aVerts[nIndices[0]] - m_aVerts[nIndices[2]]; - Vector edgeV = m_aVerts[nIndices[1]] - m_aVerts[nIndices[2]]; - vecPoint = m_aVerts[nIndices[2]] + edgeU * ( 1.0f - flFracU ) + edgeV * flFracV; - - if ( flPushEps != 0.0f ) - { - Vector vecNormal; - vecNormal = CrossProduct( edgeV, edgeU ); - VectorNormalize( vecNormal ); - vecPoint += ( vecNormal * flPushEps ); - } - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRADDispColl::DispUVToSurfNormal( Vector2D const &dispUV, Vector &vecNormal ) -{ - // Check to see that the point is on the surface. - if ( dispUV.x < 0.0f || dispUV.x > 1.0f || dispUV.y < 0.0f || dispUV.y > 1.0f ) - return; - - // Get the displacement power. - int nWidth = ( ( 1 << m_nPower ) + 1 ); - int nHeight = nWidth; - - // Scale the U, V coordinates to the displacement grid size. - float flU = dispUV.x * static_cast( nWidth - 1.000001f ); - float flV = dispUV.y * static_cast( nHeight - 1.000001f ); - - // Find the base U, V. - int nSnapU = static_cast( flU ); - int nSnapV = static_cast( flV ); - - int nNextU = nSnapU + 1; - int nNextV = nSnapV + 1; - if ( nNextU == nWidth) { --nNextU; } - if ( nNextV == nHeight ) { --nNextV; } - - float flFracU = flU - static_cast( nSnapU ); - float flFracV = flV - static_cast( nSnapV ); - - // Get the four normals "around" the "spot" - int iQuad[VRAD_QUAD_SIZE]; - iQuad[0] = ( nSnapV * nWidth ) + nSnapU; - iQuad[1] = ( nNextV * nWidth ) + nSnapU; - iQuad[2] = ( nNextV * nWidth ) + nNextU; - iQuad[3] = ( nSnapV * nWidth ) + nNextU; - - // Find the blended normal (bi-linear). - Vector vecTmpNormals[2], vecBlendedNormals[2], vecDispNormals[4]; - - for ( int iVert = 0; iVert < VRAD_QUAD_SIZE; ++iVert ) - { - GetVertNormal( iQuad[iVert], vecDispNormals[iVert] ); - } - - vecTmpNormals[0] = vecDispNormals[0] * ( 1.0f - flFracU ); - vecTmpNormals[1] = vecDispNormals[3] * flFracU; - vecBlendedNormals[0] = vecTmpNormals[0] + vecTmpNormals[1]; - VectorNormalize( vecBlendedNormals[0] ); - - vecTmpNormals[0] = vecDispNormals[1] * ( 1.0f - flFracU ); - vecTmpNormals[1] = vecDispNormals[2] * flFracU; - vecBlendedNormals[1] = vecTmpNormals[0] + vecTmpNormals[1]; - VectorNormalize( vecBlendedNormals[1] ); - - vecTmpNormals[0] = vecBlendedNormals[0] * ( 1.0f - flFracV ); - vecTmpNormals[1] = vecBlendedNormals[1] * flFracV; - - vecNormal = vecTmpNormals[0] + vecTmpNormals[1]; - VectorNormalize( vecNormal ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : float -//----------------------------------------------------------------------------- -float CVRADDispColl::CreateParentPatches( void ) -{ - // Save the total surface area of the displacement. - float flTotalArea = 0.0f; - - // Get the number of displacement subdivisions. - int nInterval = GetWidth(); - - Vector vecPoints[4]; - vecPoints[0].Init( m_aVerts[0].x, m_aVerts[0].y, m_aVerts[0].z ); - vecPoints[1].Init( m_aVerts[(nInterval*(nInterval-1))].x, m_aVerts[(nInterval*(nInterval-1))].y, m_aVerts[(nInterval*(nInterval-1))].z ); - vecPoints[2].Init( m_aVerts[((nInterval*nInterval)-1)].x, m_aVerts[((nInterval*nInterval)-1)].y, m_aVerts[((nInterval*nInterval)-1)].z ); - vecPoints[3].Init( m_aVerts[(nInterval-1)].x, m_aVerts[(nInterval-1)].y, m_aVerts[(nInterval-1)].z ); - - // Create and initialize the patch. - int iPatch = g_Patches.AddToTail(); - if ( iPatch == g_Patches.InvalidIndex() ) - return flTotalArea; - - // Keep track of the area of the patches. - float flArea = 0.0f; - if ( !InitParentPatch( iPatch, vecPoints, flArea ) ) - { - g_Patches.Remove( iPatch ); - flArea = 0.0f; - } - - // Return the displacement area. - return flArea; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : iParentPatch - -// nLevel - -//----------------------------------------------------------------------------- -void CVRADDispColl::CreateChildPatchesFromRoot( int iParentPatch, int *pChildPatch ) -{ - // Initialize the child patch indices. - pChildPatch[0] = g_Patches.InvalidIndex(); - pChildPatch[1] = g_Patches.InvalidIndex(); - - // Get the number of displacement subdivisions. - int nInterval = GetWidth(); - - // Get the parent patch. - CPatch *pParentPatch = &g_Patches[iParentPatch]; - if ( !pParentPatch ) - return; - - // Split along the longest edge. - Vector vecEdges[4]; - vecEdges[0] = pParentPatch->winding->p[1] - pParentPatch->winding->p[0]; - vecEdges[1] = pParentPatch->winding->p[2] - pParentPatch->winding->p[1]; - vecEdges[2] = pParentPatch->winding->p[3] - pParentPatch->winding->p[2]; - vecEdges[3] = pParentPatch->winding->p[3] - pParentPatch->winding->p[0]; - - // Should the patch be subdivided - check the area. - float flMaxLength = max( m_flSampleWidth, m_flSampleHeight ); - float flMinEdgeLength = flMaxLength * dispchop; - - // Find the longest edge. - float flEdgeLength = 0.0f; - int iLongEdge = -1; - for ( int iEdge = 0; iEdge < 4; ++iEdge ) - { - float flLength = vecEdges[iEdge].Length(); - if ( flEdgeLength < flLength ) - { - flEdgeLength = vecEdges[iEdge].Length(); - iLongEdge = iEdge; - } - } - - // Small enough already, return. - if ( flEdgeLength < flMinEdgeLength ) - return; - - // Test area as well so we don't allow slivers. - float flMinArea = ( dispchop * flMaxLength ) * ( dispchop * flMaxLength ); - Vector vecNormal = vecEdges[3].Cross( vecEdges[0] ); - float flTestArea = VectorNormalize( vecNormal ); - if ( flTestArea < flMinArea ) - return; - - // Get the points for the first triangle. - int iPoints[3]; - Vector vecPoints[3]; - float flArea; - - iPoints[0] = ( nInterval * nInterval ) - 1; - iPoints[1] = 0; - iPoints[2] = nInterval * ( nInterval - 1 ); - for ( int iPoint = 0; iPoint < 3; ++iPoint ) - { - VectorCopy( m_aVerts[iPoints[iPoint]], vecPoints[iPoint] ); - } - - // Create and initialize the patch. - pChildPatch[0] = g_Patches.AddToTail(); - if ( pChildPatch[0] == g_Patches.InvalidIndex() ) - return; - - if ( !InitPatch( pChildPatch[0], iParentPatch, 0, vecPoints, iPoints, flArea ) ) - { - g_Patches.Remove( pChildPatch[0] ); - pChildPatch[0] = g_Patches.InvalidIndex(); - return; - } - - // Get the points for the second triangle. - iPoints[0] = 0; - iPoints[1] = ( nInterval * nInterval ) - 1; - iPoints[2] = nInterval - 1; - for ( int iPoint = 0; iPoint < 3; ++iPoint ) - { - VectorCopy( m_aVerts[iPoints[iPoint]], vecPoints[iPoint] ); - } - - // Create and initialize the patch. - pChildPatch[1] = g_Patches.AddToTail(); - if ( pChildPatch[1] == g_Patches.InvalidIndex() ) - { - g_Patches.Remove( pChildPatch[0] ); - pChildPatch[0] = g_Patches.InvalidIndex(); - return; - } - - if ( !InitPatch( pChildPatch[1], iParentPatch, 1, vecPoints, iPoints, flArea ) ) - { - g_Patches.Remove( pChildPatch[0] ); - pChildPatch[0] = g_Patches.InvalidIndex(); - g_Patches.Remove( pChildPatch[1] ); - pChildPatch[1] = g_Patches.InvalidIndex(); - return; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : flMinArea - -// Output : float -//----------------------------------------------------------------------------- -void CVRADDispColl::CreateChildPatches( int iParentPatch, int nLevel ) -{ - // Get the parent patch. - CPatch *pParentPatch = &g_Patches[iParentPatch]; - if ( !pParentPatch ) - return; - - // The root face is a quad - special case. - if ( pParentPatch->winding->numpoints == 4 ) - { - int iChildPatch[2]; - CreateChildPatchesFromRoot( iParentPatch, iChildPatch ); - if ( iChildPatch[0] != g_Patches.InvalidIndex() && iChildPatch[1] != g_Patches.InvalidIndex() ) - { - CreateChildPatches( iChildPatch[0], 0 ); - CreateChildPatches( iChildPatch[1], 0 ); - } - return; - } - - // Calculate the the area of the patch (triangle!). - Assert( pParentPatch->winding->numpoints == 3 ); - if ( pParentPatch->winding->numpoints != 3 ) - return; - - // Should the patch be subdivided - check the area. - float flMaxLength = max( m_flSampleWidth, m_flSampleHeight ); - float flMinEdgeLength = flMaxLength * dispchop; - - // Split along the longest edge. - Vector vecEdges[3]; - vecEdges[0] = pParentPatch->winding->p[1] - pParentPatch->winding->p[0]; - vecEdges[1] = pParentPatch->winding->p[2] - pParentPatch->winding->p[0]; - vecEdges[2] = pParentPatch->winding->p[2] - pParentPatch->winding->p[1]; - - // Find the longest edge. - float flEdgeLength = 0.0f; - int iLongEdge = -1; - for ( int iEdge = 0; iEdge < 3; ++iEdge ) - { - if ( flEdgeLength < vecEdges[iEdge].Length() ) - { - flEdgeLength = vecEdges[iEdge].Length(); - iLongEdge = iEdge; - } - } - - // Small enough already, return. - if ( flEdgeLength < flMinEdgeLength ) - return; - - // Test area as well so we don't allow slivers. - float flMinArea = ( dispchop * flMaxLength ) * ( dispchop * flMaxLength ) * 0.5f; - Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); - float flTestArea = VectorNormalize( vecNormal ); - flTestArea *= 0.5f; - if ( flTestArea < flMinArea ) - return; - - // Check to see if any more displacement verts exist - go to subdivision if not. - if ( nLevel >= ( m_nPower * 2 ) ) - { - CreateChildPatchesSub( iParentPatch ); - return; - } - - int nChildIndices[2][3]; - int nNewIndex = ( pParentPatch->indices[1] + pParentPatch->indices[0] ) / 2; - nChildIndices[0][0] = pParentPatch->indices[2]; - nChildIndices[0][1] = pParentPatch->indices[0]; - nChildIndices[0][2] = nNewIndex; - - nChildIndices[1][0] = pParentPatch->indices[1]; - nChildIndices[1][1] = pParentPatch->indices[2]; - nChildIndices[1][2] = nNewIndex; - - Vector vecChildPoints[2][3]; - for ( int iTri = 0; iTri < 2; ++iTri ) - { - for ( int iPoint = 0; iPoint < 3; ++iPoint ) - { - VectorCopy( m_aVerts[nChildIndices[iTri][iPoint]], vecChildPoints[iTri][iPoint] ); - } - } - - // Create and initialize the children patches. - int iChildPatch[2] = { -1, -1 }; - for ( int iChild = 0; iChild < 2; ++iChild ) - { - iChildPatch[iChild] = g_Patches.AddToTail(); - - float flArea = 0.0f; - if ( !InitPatch( iChildPatch[iChild], iParentPatch, iChild, vecChildPoints[iChild], nChildIndices[iChild], flArea ) ) - { - if ( iChild == 0 ) - { - pParentPatch->child1 = g_Patches.InvalidIndex(); - g_Patches.Remove( iChildPatch[iChild] ); - break; - } - else - { - pParentPatch->child1 = g_Patches.InvalidIndex(); - pParentPatch->child2 = g_Patches.InvalidIndex(); - g_Patches.Remove( iChildPatch[iChild] ); - g_Patches.Remove( iChildPatch[0] ); - } - } - } - - // Continue creating children patches. - int nNewLevel = ++nLevel; - CreateChildPatches( iChildPatch[0], nNewLevel ); - CreateChildPatches( iChildPatch[1], nNewLevel ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : flMinArea - -// Output : float -//----------------------------------------------------------------------------- -void CVRADDispColl::CreateChildPatchesSub( int iParentPatch ) -{ - // Get the parent patch. - CPatch *pParentPatch = &g_Patches[iParentPatch]; - if ( !pParentPatch ) - return; - - // Calculate the the area of the patch (triangle!). - Assert( pParentPatch->winding->numpoints == 3 ); - if ( pParentPatch->winding->numpoints != 3 ) - return; - - // Should the patch be subdivided - check the area. - float flMaxLength = max( m_flSampleWidth, m_flSampleHeight ); - float flMinEdgeLength = flMaxLength * dispchop; - - // Split along the longest edge. - Vector vecEdges[3]; - vecEdges[0] = pParentPatch->winding->p[1] - pParentPatch->winding->p[0]; - vecEdges[1] = pParentPatch->winding->p[2] - pParentPatch->winding->p[1]; - vecEdges[2] = pParentPatch->winding->p[0] - pParentPatch->winding->p[2]; - - // Find the longest edge. - float flEdgeLength = 0.0f; - int iLongEdge = -1; - for ( int iEdge = 0; iEdge < 3; ++iEdge ) - { - if ( flEdgeLength < vecEdges[iEdge].Length() ) - { - flEdgeLength = vecEdges[iEdge].Length(); - iLongEdge = iEdge; - } - } - - // Small enough already, return. - if ( flEdgeLength < flMinEdgeLength ) - return; - - // Test area as well so we don't allow slivers. - float flMinArea = ( dispchop * flMaxLength ) * ( dispchop * flMaxLength ) * 0.5f; - Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); - float flTestArea = VectorNormalize( vecNormal ); - flTestArea *= 0.5f; - if ( flTestArea < flMinArea ) - return; - - // Create children patchs - 2 of them. - Vector vecChildPoints[2][3]; - switch ( iLongEdge ) - { - case 0: - { - vecChildPoints[0][0] = pParentPatch->winding->p[0]; - vecChildPoints[0][1] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[1] ) * 0.5f; - vecChildPoints[0][2] = pParentPatch->winding->p[2]; - - vecChildPoints[1][0] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[1] ) * 0.5f; - vecChildPoints[1][1] = pParentPatch->winding->p[1]; - vecChildPoints[1][2] = pParentPatch->winding->p[2]; - break; - } - case 1: - { - vecChildPoints[0][0] = pParentPatch->winding->p[0]; - vecChildPoints[0][1] = pParentPatch->winding->p[1]; - vecChildPoints[0][2] = ( pParentPatch->winding->p[1] + pParentPatch->winding->p[2] ) * 0.5f; - - vecChildPoints[1][0] = ( pParentPatch->winding->p[1] + pParentPatch->winding->p[2] ) * 0.5f; - vecChildPoints[1][1] = pParentPatch->winding->p[2]; - vecChildPoints[1][2] = pParentPatch->winding->p[0]; - break; - } - case 2: - { - vecChildPoints[0][0] = pParentPatch->winding->p[0]; - vecChildPoints[0][1] = pParentPatch->winding->p[1]; - vecChildPoints[0][2] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[2] ) * 0.5f; - - vecChildPoints[1][0] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[2] ) * 0.5f; - vecChildPoints[1][1] = pParentPatch->winding->p[1]; - vecChildPoints[1][2] = pParentPatch->winding->p[2]; - break; - } - } - - - // Create and initialize the children patches. - int iChildPatch[2] = { 0, 0 }; - int nChildIndices[3] = { -1, -1, -1 }; - for ( int iChild = 0; iChild < 2; ++iChild ) - { - iChildPatch[iChild] = g_Patches.AddToTail(); - - float flArea = 0.0f; - if ( !InitPatch( iChildPatch[iChild], iParentPatch, iChild, vecChildPoints[iChild], nChildIndices, flArea ) ) - { - if ( iChild == 0 ) - { - pParentPatch->child1 = g_Patches.InvalidIndex(); - g_Patches.Remove( iChildPatch[iChild] ); - break; - } - else - { - pParentPatch->child1 = g_Patches.InvalidIndex(); - pParentPatch->child2 = g_Patches.InvalidIndex(); - g_Patches.Remove( iChildPatch[iChild] ); - g_Patches.Remove( iChildPatch[0] ); - } - } - } - - // Continue creating children patches. - CreateChildPatchesSub( iChildPatch[0] ); - CreateChildPatchesSub( iChildPatch[1] ); -} - -int PlaneTypeForNormal (Vector& normal) -{ - vec_t ax, ay, az; - - // NOTE: should these have an epsilon around 1.0? - if (normal[0] == 1.0 || normal[0] == -1.0) - return PLANE_X; - if (normal[1] == 1.0 || normal[1] == -1.0) - return PLANE_Y; - if (normal[2] == 1.0 || normal[2] == -1.0) - return PLANE_Z; - - ax = fabs(normal[0]); - ay = fabs(normal[1]); - az = fabs(normal[2]); - - if (ax >= ay && ax >= az) - return PLANE_ANYX; - if (ay >= ax && ay >= az) - return PLANE_ANYY; - return PLANE_ANYZ; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : iPatch - -// iParentPatch - -// iChild - -// *pPoints - -// *pIndices - -// &flArea - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CVRADDispColl::InitParentPatch( int iPatch, Vector *pPoints, float &flArea ) -{ - // Get the current patch. - CPatch *pPatch = &g_Patches[iPatch]; - if ( !pPatch ) - return false; - - // Clear the patch data. - memset( pPatch, 0, sizeof( CPatch ) ); - - // This is a parent. - pPatch->ndxNext = g_FacePatches.Element( GetParentIndex() ); - g_FacePatches[GetParentIndex()] = iPatch; - pPatch->faceNumber = GetParentIndex(); - - // Initialize parent and children indices. - pPatch->child1 = g_Patches.InvalidIndex(); - pPatch->child2 = g_Patches.InvalidIndex(); - pPatch->parent = g_Patches.InvalidIndex(); - pPatch->ndxNextClusterChild = g_Patches.InvalidIndex(); - pPatch->ndxNextParent = g_Patches.InvalidIndex(); - - Vector vecEdges[2]; - vecEdges[0] = pPoints[1] - pPoints[0]; - vecEdges[1] = pPoints[3] - pPoints[0]; - - // Calculate the triangle normal and area. - Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); - flArea = VectorNormalize( vecNormal ); - - // Initialize the patch scale. - pPatch->scale[0] = pPatch->scale[1] = 1.0f; - - // Set the patch chop - minchop (that is what the minimum area is based on). - pPatch->chop = dispchop; - - // Displacements are not sky! - pPatch->sky = false; - - // Copy the winding. - Vector vecCenter( 0.0f, 0.0f, 0.0f ); - pPatch->winding = AllocWinding( 4 ); - pPatch->winding->numpoints = 4; - for ( int iPoint = 0; iPoint < 4; ++iPoint ) - { - VectorCopy( pPoints[iPoint], pPatch->winding->p[iPoint] ); - VectorAdd( pPoints[iPoint], vecCenter, vecCenter ); - } - - // Set the origin and normal. - VectorScale( vecCenter, ( 1.0f / 4.0f ), vecCenter ); - VectorCopy( vecCenter, pPatch->origin ); - VectorCopy( vecNormal, pPatch->normal ); - - // Create the plane. - pPatch->plane = new dplane_t; - if ( !pPatch->plane ) - return false; - - VectorCopy( vecNormal, pPatch->plane->normal ); - pPatch->plane->dist = vecNormal.Dot( pPoints[0] ); - pPatch->plane->type = PlaneTypeForNormal( pPatch->plane->normal ); - pPatch->planeDist = pPatch->plane->dist; - - // Set the area. - pPatch->area = flArea; - - // Calculate the mins/maxs. - Vector vecMin( FLT_MAX, FLT_MAX, FLT_MAX ); - Vector vecMax( FLT_MIN, FLT_MIN, FLT_MIN ); - for ( int iPoint = 0; iPoint < 4; ++iPoint ) - { - for ( int iAxis = 0; iAxis < 3; ++iAxis ) - { - vecMin[iAxis] = min( vecMin[iAxis], pPoints[iPoint][iAxis] ); - vecMax[iAxis] = max( vecMax[iAxis], pPoints[iPoint][iAxis] ); - } - } - - VectorCopy( vecMin, pPatch->mins ); - VectorCopy( vecMax, pPatch->maxs ); - VectorCopy( vecMin, pPatch->face_mins ); - VectorCopy( vecMax, pPatch->face_maxs ); - - // Check for bumpmap. - dface_t *pFace = dfaces + pPatch->faceNumber; - texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; - pPatch->needsBumpmap = pTexInfo->flags & SURF_BUMPLIGHT ? true : false; - - // Misc... - pPatch->m_IterationKey = 0; - - // Calculate the base light, area, and reflectivity. - BaseLightForFace( &g_pFaces[pPatch->faceNumber], pPatch->baselight, &pPatch->basearea, pPatch->reflectivity ); - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pPatch - -// *pPoints - -// &vecNormal - -// flArea - -//----------------------------------------------------------------------------- -bool CVRADDispColl::InitPatch( int iPatch, int iParentPatch, int iChild, Vector *pPoints, int *pIndices, float &flArea ) -{ - // Get the current patch. - CPatch *pPatch = &g_Patches[iPatch]; - if ( !pPatch ) - return false; - - // Clear the patch data. - memset( pPatch, 0, sizeof( CPatch ) ); - - // Setup the parent if we are not the parent. - CPatch *pParentPatch = NULL; - if ( iParentPatch != g_Patches.InvalidIndex() ) - { - // Get the parent patch. - pParentPatch = &g_Patches[iParentPatch]; - if ( !pParentPatch ) - return false; - } - - // Attach the face to the correct lists. - if ( !pParentPatch ) - { - // This is a parent. - pPatch->ndxNext = g_FacePatches.Element( GetParentIndex() ); - g_FacePatches[GetParentIndex()] = iPatch; - pPatch->faceNumber = GetParentIndex(); - } - else - { - pPatch->ndxNext = g_Patches.InvalidIndex(); - pPatch->faceNumber = pParentPatch->faceNumber; - - // Attach to the parent patch. - if ( iChild == 0 ) - { - pParentPatch->child1 = iPatch; - } - else - { - pParentPatch->child2 = iPatch; - } - } - - // Initialize parent and children indices. - pPatch->child1 = g_Patches.InvalidIndex(); - pPatch->child2 = g_Patches.InvalidIndex(); - pPatch->ndxNextClusterChild = g_Patches.InvalidIndex(); - pPatch->ndxNextParent = g_Patches.InvalidIndex(); - pPatch->parent = iParentPatch; - - // Get triangle edges. - Vector vecEdges[3]; - vecEdges[0] = pPoints[1] - pPoints[0]; - vecEdges[1] = pPoints[2] - pPoints[0]; - vecEdges[2] = pPoints[2] - pPoints[1]; - - // Find the longest edge. -// float flEdgeLength = 0.0f; -// for ( int iEdge = 0; iEdge < 3; ++iEdge ) -// { -// if ( flEdgeLength < vecEdges[iEdge].Length() ) -// { -// flEdgeLength = vecEdges[iEdge].Length(); -// } -// } - - // Calculate the triangle normal and area. - Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); - flArea = VectorNormalize( vecNormal ); - flArea *= 0.5f; - - // Initialize the patch scale. - pPatch->scale[0] = pPatch->scale[1] = 1.0f; - - // Set the patch chop - minchop (that is what the minimum area is based on). - pPatch->chop = dispchop; - - // Displacements are not sky! - pPatch->sky = false; - - // Copy the winding. - Vector vecCenter( 0.0f, 0.0f, 0.0f ); - pPatch->winding = AllocWinding( 3 ); - pPatch->winding->numpoints = 3; - for ( int iPoint = 0; iPoint < 3; ++iPoint ) - { - VectorCopy( pPoints[iPoint], pPatch->winding->p[iPoint] ); - VectorAdd( pPoints[iPoint], vecCenter, vecCenter ); - - pPatch->indices[iPoint] = static_cast( pIndices[iPoint] ); - } - - // Set the origin and normal. - VectorScale( vecCenter, ( 1.0f / 3.0f ), vecCenter ); - VectorCopy( vecCenter, pPatch->origin ); - VectorCopy( vecNormal, pPatch->normal ); - - // Create the plane. - pPatch->plane = new dplane_t; - if ( !pPatch->plane ) - return false; - - VectorCopy( vecNormal, pPatch->plane->normal ); - pPatch->plane->dist = vecNormal.Dot( pPoints[0] ); - pPatch->plane->type = PlaneTypeForNormal( pPatch->plane->normal ); - pPatch->planeDist = pPatch->plane->dist; - - // Set the area. - pPatch->area = flArea; - - // Calculate the mins/maxs. - Vector vecMin( FLT_MAX, FLT_MAX, FLT_MAX ); - Vector vecMax( FLT_MIN, FLT_MIN, FLT_MIN ); - for ( int iPoint = 0; iPoint < 3; ++iPoint ) - { - for ( int iAxis = 0; iAxis < 3; ++iAxis ) - { - vecMin[iAxis] = min( vecMin[iAxis], pPoints[iPoint][iAxis] ); - vecMax[iAxis] = max( vecMax[iAxis], pPoints[iPoint][iAxis] ); - } - } - - VectorCopy( vecMin, pPatch->mins ); - VectorCopy( vecMax, pPatch->maxs ); - - if ( !pParentPatch ) - { - VectorCopy( vecMin, pPatch->face_mins ); - VectorCopy( vecMax, pPatch->face_maxs ); - } - else - { - VectorCopy( pParentPatch->face_mins, pPatch->face_mins ); - VectorCopy( pParentPatch->face_maxs, pPatch->face_maxs ); - } - - // Check for bumpmap. - dface_t *pFace = dfaces + pPatch->faceNumber; - texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; - pPatch->needsBumpmap = pTexInfo->flags & SURF_BUMPLIGHT ? true : false; - - // Misc... - pPatch->m_IterationKey = 0; - - // Get the base light for the face. - if ( !pParentPatch ) - { - BaseLightForFace( &g_pFaces[pPatch->faceNumber], pPatch->baselight, &pPatch->basearea, pPatch->reflectivity ); - } - else - { - VectorCopy( pParentPatch->baselight, pPatch->baselight ); - pPatch->basearea = pParentPatch->basearea; - pPatch->reflectivity = pParentPatch->reflectivity; - } - - return true; -} - -void CVRADDispColl::AddPolysForRayTrace( void ) -{ - if ( !( m_nContents & MASK_OPAQUE ) ) - return; - - for ( int ndxTri = 0; ndxTri < m_aTris.Size(); ndxTri++ ) - { - CDispCollTri *tri = m_aTris.Base() + ndxTri; - int v[3]; - for ( int ndxv = 0; ndxv < 3; ndxv++ ) - v[ndxv] = tri->GetVert(ndxv); - - Vector fullCoverage; - fullCoverage.x = 1.0f; - g_RtEnv.AddTriangle( TRACE_ID_OPAQUE, m_aVerts[v[0]], m_aVerts[v[1]], m_aVerts[v[2]], fullCoverage ); - } +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vrad.h" +#include "VRAD_DispColl.h" +#include "DispColl_Common.h" +#include "radial.h" +#include "CollisionUtils.h" +#include "tier0\dbg.h" + +#define SAMPLE_BBOX_SLOP 5.0f +#define TRIEDGE_EPSILON 0.001f + +float g_flMaxDispSampleSize = 512.0f; + +static FileHandle_t pDispFile = FILESYSTEM_INVALID_HANDLE; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CVRADDispColl::CVRADDispColl() +{ + m_iParent = -1; + + m_flSampleRadius2 = 0.0f; + m_flPatchSampleRadius2 = 0.0f; + + m_flSampleWidth = 0.0f; + m_flSampleHeight = 0.0f; + + m_aLuxelCoords.Purge(); + m_aVertNormals.Purge(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CVRADDispColl::~CVRADDispColl() +{ + m_aLuxelCoords.Purge(); + m_aVertNormals.Purge(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRADDispColl::Create( CCoreDispInfo *pDisp ) +{ + // Base class create. + if( !CDispCollTree::Create( pDisp ) ) + return false; + + // Allocate VRad specific memory. + m_aLuxelCoords.SetSize( GetSize() ); + m_aVertNormals.SetSize( GetSize() ); + + // VRad specific base surface data. + CCoreDispSurface *pSurf = pDisp->GetSurface(); + m_iParent = pSurf->GetHandle(); + + // VRad specific displacement surface data. + for ( int iVert = 0; iVert < m_aVerts.Count(); ++iVert ) + { + pDisp->GetNormal( iVert, m_aVertNormals[iVert] ); + pDisp->GetLuxelCoord( 0, iVert, m_aLuxelCoords[iVert] ); + } + + // Re-calculate the lightmap size (in uv) so that the luxels give + // a better world-space uniform approx. due to the non-linear nature + // of the displacement surface in uv-space + dface_t *pFace = &g_pFaces[m_iParent]; + if( pFace ) + { + CalcSampleRadius2AndBox( pFace ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVRADDispColl::CalcSampleRadius2AndBox( dface_t *pFace ) +{ + // Get the luxel sample size. + texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; + Assert ( pTexInfo ); + if ( !pTexInfo ) + return; + + // Todo: Width = Height now, should change all the code to look at one value. + Vector vecTmp( pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][0], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][1], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); + float flWidth = 1.0f / VectorLength( vecTmp ); + float flHeight = flWidth; + + // Save off the sample width and height. + m_flSampleWidth = flWidth; + m_flSampleHeight = flHeight; + + // Calculate the sample radius squared. + float flSampleRadius = sqrt( ( ( flWidth * flWidth ) + ( flHeight * flHeight ) ) ) * 2.2f;//RADIALDIST2; + if ( flSampleRadius > g_flMaxDispSampleSize ) + { + flSampleRadius = g_flMaxDispSampleSize; + } + m_flSampleRadius2 = flSampleRadius * flSampleRadius; + + // Calculate the patch radius - the max sample edge length * the number of luxels per edge "chop." + float flSampleSize = max( m_flSampleWidth, m_flSampleHeight ); + float flPatchSampleRadius = flSampleSize * dispchop * 2.2f; + if ( flPatchSampleRadius > g_MaxDispPatchRadius ) + { + flPatchSampleRadius = g_MaxDispPatchRadius; + Warning( "Patch Sample Radius Clamped!\n" ); + } + m_flPatchSampleRadius2 = flPatchSampleRadius * flPatchSampleRadius; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the min/max of the displacement surface. +//----------------------------------------------------------------------------- +void CVRADDispColl::GetSurfaceMinMax( Vector &boxMin, Vector &boxMax ) +{ + // Initialize the minimum and maximum box + boxMin = m_aVerts[0]; + boxMax = m_aVerts[0]; + + for( int i = 1; i < m_aVerts.Count(); i++ ) + { + if( m_aVerts[i].x < boxMin.x ) { boxMin.x = m_aVerts[i].x; } + if( m_aVerts[i].y < boxMin.y ) { boxMin.y = m_aVerts[i].y; } + if( m_aVerts[i].z < boxMin.z ) { boxMin.z = m_aVerts[i].z; } + + if( m_aVerts[i].x > boxMax.x ) { boxMax.x = m_aVerts[i].x; } + if( m_aVerts[i].y > boxMax.y ) { boxMax.y = m_aVerts[i].y; } + if( m_aVerts[i].z > boxMax.z ) { boxMax.z = m_aVerts[i].z; } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Find the minor projection axes based on the given normal. +//----------------------------------------------------------------------------- +void CVRADDispColl::GetMinorAxes( Vector const &vecNormal, int &nAxis0, int &nAxis1 ) +{ + nAxis0 = 0; + nAxis1 = 1; + + if( FloatMakePositive( vecNormal.x ) > FloatMakePositive( vecNormal.y ) ) + { + if( FloatMakePositive( vecNormal.x ) > FloatMakePositive( vecNormal.z ) ) + { + nAxis0 = 1; + nAxis1 = 2; + } + } + else + { + if( FloatMakePositive( vecNormal.y ) > FloatMakePositive( vecNormal.z ) ) + { + nAxis0 = 0; + nAxis1 = 2; + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRADDispColl::BaseFacePlaneToDispUV( Vector const &vecPlanePt, Vector2D &dispUV ) +{ + PointInQuadToBarycentric( m_vecSurfPoints[0], m_vecSurfPoints[3], m_vecSurfPoints[2], m_vecSurfPoints[1], vecPlanePt, dispUV ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRADDispColl::DispUVToSurfPoint( Vector2D const &dispUV, Vector &vecPoint, float flPushEps ) +{ + // Check to see that the point is on the surface. + if ( dispUV.x < 0.0f || dispUV.x > 1.0f || dispUV.y < 0.0f || dispUV.y > 1.0f ) + return; + + // Get the displacement power. + int nWidth = ( ( 1 << m_nPower ) + 1 ); + int nHeight = nWidth; + + // Scale the U, V coordinates to the displacement grid size. + float flU = dispUV.x * static_cast( nWidth - 1.000001f ); + float flV = dispUV.y * static_cast( nHeight - 1.000001f ); + + // Find the base U, V. + int nSnapU = static_cast( flU ); + int nSnapV = static_cast( flV ); + + // Use this to get the triangle orientation. + bool bOdd = ( ( ( nSnapV * nWidth ) + nSnapU ) % 2 == 1 ); + + // Top Left to Bottom Right + if( bOdd ) + { + DispUVToSurf_TriTLToBR( vecPoint, flPushEps, flU, flV, nSnapU, nSnapV, nWidth, nHeight ); + } + // Bottom Left to Top Right + else + { + DispUVToSurf_TriBLToTR( vecPoint, flPushEps, flU, flV, nSnapU, nSnapV, nWidth, nHeight ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVRADDispColl::DispUVToSurf_TriTLToBR( Vector &vecPoint, float flPushEps, + float flU, float flV, int nSnapU, int nSnapV, + int nWidth, int nHeight ) +{ + int nNextU = nSnapU + 1; + int nNextV = nSnapV + 1; + if ( nNextU == nWidth) { --nNextU; } + if ( nNextV == nHeight ) { --nNextV; } + + float flFracU = flU - static_cast( nSnapU ); + float flFracV = flV - static_cast( nSnapV ); + + if( ( flFracU + flFracV ) >= ( 1.0f + TRIEDGE_EPSILON ) ) + { + int nIndices[3]; + nIndices[0] = nNextV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nNextU; + nIndices[2] = nSnapV * nWidth + nNextU; + + Vector edgeU = m_aVerts[nIndices[0]] - m_aVerts[nIndices[1]]; + Vector edgeV = m_aVerts[nIndices[2]] - m_aVerts[nIndices[1]]; + vecPoint = m_aVerts[nIndices[1]] + edgeU * ( 1.0f - flFracU ) + edgeV * ( 1.0f - flFracV ); + + if ( flPushEps != 0.0f ) + { + Vector vecNormal; + vecNormal = CrossProduct( edgeU, edgeV ); + VectorNormalize( vecNormal ); + vecPoint += ( vecNormal * flPushEps ); + } + } + else + { + int nIndices[3]; + nIndices[0] = nSnapV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nSnapU; + nIndices[2] = nSnapV * nWidth + nNextU; + + Vector edgeU = m_aVerts[nIndices[2]] - m_aVerts[nIndices[0]]; + Vector edgeV = m_aVerts[nIndices[1]] - m_aVerts[nIndices[0]]; + vecPoint = m_aVerts[nIndices[0]] + edgeU * flFracU + edgeV * flFracV; + + if ( flPushEps != 0.0f ) + { + Vector vecNormal; + vecNormal = CrossProduct( edgeU, edgeV ); + VectorNormalize( vecNormal ); + vecPoint += ( vecNormal * flPushEps ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVRADDispColl::DispUVToSurf_TriBLToTR( Vector &vecPoint, float flPushEps, + float flU, float flV, int nSnapU, int nSnapV, + int nWidth, int nHeight ) +{ + int nNextU = nSnapU + 1; + int nNextV = nSnapV + 1; + if ( nNextU == nWidth) { --nNextU; } + if ( nNextV == nHeight ) { --nNextV; } + + float flFracU = flU - static_cast( nSnapU ); + float flFracV = flV - static_cast( nSnapV ); + + if( flFracU < flFracV ) + { + int nIndices[3]; + nIndices[0] = nSnapV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nSnapU; + nIndices[2] = nNextV * nWidth + nNextU; + + Vector edgeU = m_aVerts[nIndices[2]] - m_aVerts[nIndices[1]]; + Vector edgeV = m_aVerts[nIndices[0]] - m_aVerts[nIndices[1]]; + vecPoint = m_aVerts[nIndices[1]] + edgeU * flFracU + edgeV * ( 1.0f - flFracV ); + + if ( flPushEps != 0.0f ) + { + Vector vecNormal; + vecNormal = CrossProduct( edgeV, edgeU ); + VectorNormalize( vecNormal ); + vecPoint += ( vecNormal * flPushEps ); + } + } + else + { + int nIndices[3]; + nIndices[0] = nSnapV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nNextU; + nIndices[2] = nSnapV * nWidth + nNextU; + + Vector edgeU = m_aVerts[nIndices[0]] - m_aVerts[nIndices[2]]; + Vector edgeV = m_aVerts[nIndices[1]] - m_aVerts[nIndices[2]]; + vecPoint = m_aVerts[nIndices[2]] + edgeU * ( 1.0f - flFracU ) + edgeV * flFracV; + + if ( flPushEps != 0.0f ) + { + Vector vecNormal; + vecNormal = CrossProduct( edgeV, edgeU ); + VectorNormalize( vecNormal ); + vecPoint += ( vecNormal * flPushEps ); + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRADDispColl::DispUVToSurfNormal( Vector2D const &dispUV, Vector &vecNormal ) +{ + // Check to see that the point is on the surface. + if ( dispUV.x < 0.0f || dispUV.x > 1.0f || dispUV.y < 0.0f || dispUV.y > 1.0f ) + return; + + // Get the displacement power. + int nWidth = ( ( 1 << m_nPower ) + 1 ); + int nHeight = nWidth; + + // Scale the U, V coordinates to the displacement grid size. + float flU = dispUV.x * static_cast( nWidth - 1.000001f ); + float flV = dispUV.y * static_cast( nHeight - 1.000001f ); + + // Find the base U, V. + int nSnapU = static_cast( flU ); + int nSnapV = static_cast( flV ); + + int nNextU = nSnapU + 1; + int nNextV = nSnapV + 1; + if ( nNextU == nWidth) { --nNextU; } + if ( nNextV == nHeight ) { --nNextV; } + + float flFracU = flU - static_cast( nSnapU ); + float flFracV = flV - static_cast( nSnapV ); + + // Get the four normals "around" the "spot" + int iQuad[VRAD_QUAD_SIZE]; + iQuad[0] = ( nSnapV * nWidth ) + nSnapU; + iQuad[1] = ( nNextV * nWidth ) + nSnapU; + iQuad[2] = ( nNextV * nWidth ) + nNextU; + iQuad[3] = ( nSnapV * nWidth ) + nNextU; + + // Find the blended normal (bi-linear). + Vector vecTmpNormals[2], vecBlendedNormals[2], vecDispNormals[4]; + + for ( int iVert = 0; iVert < VRAD_QUAD_SIZE; ++iVert ) + { + GetVertNormal( iQuad[iVert], vecDispNormals[iVert] ); + } + + vecTmpNormals[0] = vecDispNormals[0] * ( 1.0f - flFracU ); + vecTmpNormals[1] = vecDispNormals[3] * flFracU; + vecBlendedNormals[0] = vecTmpNormals[0] + vecTmpNormals[1]; + VectorNormalize( vecBlendedNormals[0] ); + + vecTmpNormals[0] = vecDispNormals[1] * ( 1.0f - flFracU ); + vecTmpNormals[1] = vecDispNormals[2] * flFracU; + vecBlendedNormals[1] = vecTmpNormals[0] + vecTmpNormals[1]; + VectorNormalize( vecBlendedNormals[1] ); + + vecTmpNormals[0] = vecBlendedNormals[0] * ( 1.0f - flFracV ); + vecTmpNormals[1] = vecBlendedNormals[1] * flFracV; + + vecNormal = vecTmpNormals[0] + vecTmpNormals[1]; + VectorNormalize( vecNormal ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CVRADDispColl::CreateParentPatches( void ) +{ + // Save the total surface area of the displacement. + float flTotalArea = 0.0f; + + // Get the number of displacement subdivisions. + int nInterval = GetWidth(); + + Vector vecPoints[4]; + vecPoints[0].Init( m_aVerts[0].x, m_aVerts[0].y, m_aVerts[0].z ); + vecPoints[1].Init( m_aVerts[(nInterval*(nInterval-1))].x, m_aVerts[(nInterval*(nInterval-1))].y, m_aVerts[(nInterval*(nInterval-1))].z ); + vecPoints[2].Init( m_aVerts[((nInterval*nInterval)-1)].x, m_aVerts[((nInterval*nInterval)-1)].y, m_aVerts[((nInterval*nInterval)-1)].z ); + vecPoints[3].Init( m_aVerts[(nInterval-1)].x, m_aVerts[(nInterval-1)].y, m_aVerts[(nInterval-1)].z ); + + // Create and initialize the patch. + int iPatch = g_Patches.AddToTail(); + if ( iPatch == g_Patches.InvalidIndex() ) + return flTotalArea; + + // Keep track of the area of the patches. + float flArea = 0.0f; + if ( !InitParentPatch( iPatch, vecPoints, flArea ) ) + { + g_Patches.Remove( iPatch ); + flArea = 0.0f; + } + + // Return the displacement area. + return flArea; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iParentPatch - +// nLevel - +//----------------------------------------------------------------------------- +void CVRADDispColl::CreateChildPatchesFromRoot( int iParentPatch, int *pChildPatch ) +{ + // Initialize the child patch indices. + pChildPatch[0] = g_Patches.InvalidIndex(); + pChildPatch[1] = g_Patches.InvalidIndex(); + + // Get the number of displacement subdivisions. + int nInterval = GetWidth(); + + // Get the parent patch. + CPatch *pParentPatch = &g_Patches[iParentPatch]; + if ( !pParentPatch ) + return; + + // Split along the longest edge. + Vector vecEdges[4]; + vecEdges[0] = pParentPatch->winding->p[1] - pParentPatch->winding->p[0]; + vecEdges[1] = pParentPatch->winding->p[2] - pParentPatch->winding->p[1]; + vecEdges[2] = pParentPatch->winding->p[3] - pParentPatch->winding->p[2]; + vecEdges[3] = pParentPatch->winding->p[3] - pParentPatch->winding->p[0]; + + // Should the patch be subdivided - check the area. + float flMaxLength = max( m_flSampleWidth, m_flSampleHeight ); + float flMinEdgeLength = flMaxLength * dispchop; + + // Find the longest edge. + float flEdgeLength = 0.0f; + int iLongEdge = -1; + for ( int iEdge = 0; iEdge < 4; ++iEdge ) + { + float flLength = vecEdges[iEdge].Length(); + if ( flEdgeLength < flLength ) + { + flEdgeLength = vecEdges[iEdge].Length(); + iLongEdge = iEdge; + } + } + + // Small enough already, return. + if ( flEdgeLength < flMinEdgeLength ) + return; + + // Test area as well so we don't allow slivers. + float flMinArea = ( dispchop * flMaxLength ) * ( dispchop * flMaxLength ); + Vector vecNormal = vecEdges[3].Cross( vecEdges[0] ); + float flTestArea = VectorNormalize( vecNormal ); + if ( flTestArea < flMinArea ) + return; + + // Get the points for the first triangle. + int iPoints[3]; + Vector vecPoints[3]; + float flArea; + + iPoints[0] = ( nInterval * nInterval ) - 1; + iPoints[1] = 0; + iPoints[2] = nInterval * ( nInterval - 1 ); + for ( int iPoint = 0; iPoint < 3; ++iPoint ) + { + VectorCopy( m_aVerts[iPoints[iPoint]], vecPoints[iPoint] ); + } + + // Create and initialize the patch. + pChildPatch[0] = g_Patches.AddToTail(); + if ( pChildPatch[0] == g_Patches.InvalidIndex() ) + return; + + if ( !InitPatch( pChildPatch[0], iParentPatch, 0, vecPoints, iPoints, flArea ) ) + { + g_Patches.Remove( pChildPatch[0] ); + pChildPatch[0] = g_Patches.InvalidIndex(); + return; + } + + // Get the points for the second triangle. + iPoints[0] = 0; + iPoints[1] = ( nInterval * nInterval ) - 1; + iPoints[2] = nInterval - 1; + for ( int iPoint = 0; iPoint < 3; ++iPoint ) + { + VectorCopy( m_aVerts[iPoints[iPoint]], vecPoints[iPoint] ); + } + + // Create and initialize the patch. + pChildPatch[1] = g_Patches.AddToTail(); + if ( pChildPatch[1] == g_Patches.InvalidIndex() ) + { + g_Patches.Remove( pChildPatch[0] ); + pChildPatch[0] = g_Patches.InvalidIndex(); + return; + } + + if ( !InitPatch( pChildPatch[1], iParentPatch, 1, vecPoints, iPoints, flArea ) ) + { + g_Patches.Remove( pChildPatch[0] ); + pChildPatch[0] = g_Patches.InvalidIndex(); + g_Patches.Remove( pChildPatch[1] ); + pChildPatch[1] = g_Patches.InvalidIndex(); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flMinArea - +// Output : float +//----------------------------------------------------------------------------- +void CVRADDispColl::CreateChildPatches( int iParentPatch, int nLevel ) +{ + // Get the parent patch. + CPatch *pParentPatch = &g_Patches[iParentPatch]; + if ( !pParentPatch ) + return; + + // The root face is a quad - special case. + if ( pParentPatch->winding->numpoints == 4 ) + { + int iChildPatch[2]; + CreateChildPatchesFromRoot( iParentPatch, iChildPatch ); + if ( iChildPatch[0] != g_Patches.InvalidIndex() && iChildPatch[1] != g_Patches.InvalidIndex() ) + { + CreateChildPatches( iChildPatch[0], 0 ); + CreateChildPatches( iChildPatch[1], 0 ); + } + return; + } + + // Calculate the the area of the patch (triangle!). + Assert( pParentPatch->winding->numpoints == 3 ); + if ( pParentPatch->winding->numpoints != 3 ) + return; + + // Should the patch be subdivided - check the area. + float flMaxLength = max( m_flSampleWidth, m_flSampleHeight ); + float flMinEdgeLength = flMaxLength * dispchop; + + // Split along the longest edge. + Vector vecEdges[3]; + vecEdges[0] = pParentPatch->winding->p[1] - pParentPatch->winding->p[0]; + vecEdges[1] = pParentPatch->winding->p[2] - pParentPatch->winding->p[0]; + vecEdges[2] = pParentPatch->winding->p[2] - pParentPatch->winding->p[1]; + + // Find the longest edge. + float flEdgeLength = 0.0f; + int iLongEdge = -1; + for ( int iEdge = 0; iEdge < 3; ++iEdge ) + { + if ( flEdgeLength < vecEdges[iEdge].Length() ) + { + flEdgeLength = vecEdges[iEdge].Length(); + iLongEdge = iEdge; + } + } + + // Small enough already, return. + if ( flEdgeLength < flMinEdgeLength ) + return; + + // Test area as well so we don't allow slivers. + float flMinArea = ( dispchop * flMaxLength ) * ( dispchop * flMaxLength ) * 0.5f; + Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); + float flTestArea = VectorNormalize( vecNormal ); + flTestArea *= 0.5f; + if ( flTestArea < flMinArea ) + return; + + // Check to see if any more displacement verts exist - go to subdivision if not. + if ( nLevel >= ( m_nPower * 2 ) ) + { + CreateChildPatchesSub( iParentPatch ); + return; + } + + int nChildIndices[2][3]; + int nNewIndex = ( pParentPatch->indices[1] + pParentPatch->indices[0] ) / 2; + nChildIndices[0][0] = pParentPatch->indices[2]; + nChildIndices[0][1] = pParentPatch->indices[0]; + nChildIndices[0][2] = nNewIndex; + + nChildIndices[1][0] = pParentPatch->indices[1]; + nChildIndices[1][1] = pParentPatch->indices[2]; + nChildIndices[1][2] = nNewIndex; + + Vector vecChildPoints[2][3]; + for ( int iTri = 0; iTri < 2; ++iTri ) + { + for ( int iPoint = 0; iPoint < 3; ++iPoint ) + { + VectorCopy( m_aVerts[nChildIndices[iTri][iPoint]], vecChildPoints[iTri][iPoint] ); + } + } + + // Create and initialize the children patches. + int iChildPatch[2] = { -1, -1 }; + for ( int iChild = 0; iChild < 2; ++iChild ) + { + iChildPatch[iChild] = g_Patches.AddToTail(); + + float flArea = 0.0f; + if ( !InitPatch( iChildPatch[iChild], iParentPatch, iChild, vecChildPoints[iChild], nChildIndices[iChild], flArea ) ) + { + if ( iChild == 0 ) + { + pParentPatch->child1 = g_Patches.InvalidIndex(); + g_Patches.Remove( iChildPatch[iChild] ); + break; + } + else + { + pParentPatch->child1 = g_Patches.InvalidIndex(); + pParentPatch->child2 = g_Patches.InvalidIndex(); + g_Patches.Remove( iChildPatch[iChild] ); + g_Patches.Remove( iChildPatch[0] ); + } + } + } + + // Continue creating children patches. + int nNewLevel = ++nLevel; + CreateChildPatches( iChildPatch[0], nNewLevel ); + CreateChildPatches( iChildPatch[1], nNewLevel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flMinArea - +// Output : float +//----------------------------------------------------------------------------- +void CVRADDispColl::CreateChildPatchesSub( int iParentPatch ) +{ + // Get the parent patch. + CPatch *pParentPatch = &g_Patches[iParentPatch]; + if ( !pParentPatch ) + return; + + // Calculate the the area of the patch (triangle!). + Assert( pParentPatch->winding->numpoints == 3 ); + if ( pParentPatch->winding->numpoints != 3 ) + return; + + // Should the patch be subdivided - check the area. + float flMaxLength = max( m_flSampleWidth, m_flSampleHeight ); + float flMinEdgeLength = flMaxLength * dispchop; + + // Split along the longest edge. + Vector vecEdges[3]; + vecEdges[0] = pParentPatch->winding->p[1] - pParentPatch->winding->p[0]; + vecEdges[1] = pParentPatch->winding->p[2] - pParentPatch->winding->p[1]; + vecEdges[2] = pParentPatch->winding->p[0] - pParentPatch->winding->p[2]; + + // Find the longest edge. + float flEdgeLength = 0.0f; + int iLongEdge = -1; + for ( int iEdge = 0; iEdge < 3; ++iEdge ) + { + if ( flEdgeLength < vecEdges[iEdge].Length() ) + { + flEdgeLength = vecEdges[iEdge].Length(); + iLongEdge = iEdge; + } + } + + // Small enough already, return. + if ( flEdgeLength < flMinEdgeLength ) + return; + + // Test area as well so we don't allow slivers. + float flMinArea = ( dispchop * flMaxLength ) * ( dispchop * flMaxLength ) * 0.5f; + Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); + float flTestArea = VectorNormalize( vecNormal ); + flTestArea *= 0.5f; + if ( flTestArea < flMinArea ) + return; + + // Create children patchs - 2 of them. + Vector vecChildPoints[2][3]; + switch ( iLongEdge ) + { + case 0: + { + vecChildPoints[0][0] = pParentPatch->winding->p[0]; + vecChildPoints[0][1] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[1] ) * 0.5f; + vecChildPoints[0][2] = pParentPatch->winding->p[2]; + + vecChildPoints[1][0] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[1] ) * 0.5f; + vecChildPoints[1][1] = pParentPatch->winding->p[1]; + vecChildPoints[1][2] = pParentPatch->winding->p[2]; + break; + } + case 1: + { + vecChildPoints[0][0] = pParentPatch->winding->p[0]; + vecChildPoints[0][1] = pParentPatch->winding->p[1]; + vecChildPoints[0][2] = ( pParentPatch->winding->p[1] + pParentPatch->winding->p[2] ) * 0.5f; + + vecChildPoints[1][0] = ( pParentPatch->winding->p[1] + pParentPatch->winding->p[2] ) * 0.5f; + vecChildPoints[1][1] = pParentPatch->winding->p[2]; + vecChildPoints[1][2] = pParentPatch->winding->p[0]; + break; + } + case 2: + { + vecChildPoints[0][0] = pParentPatch->winding->p[0]; + vecChildPoints[0][1] = pParentPatch->winding->p[1]; + vecChildPoints[0][2] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[2] ) * 0.5f; + + vecChildPoints[1][0] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[2] ) * 0.5f; + vecChildPoints[1][1] = pParentPatch->winding->p[1]; + vecChildPoints[1][2] = pParentPatch->winding->p[2]; + break; + } + } + + + // Create and initialize the children patches. + int iChildPatch[2] = { 0, 0 }; + int nChildIndices[3] = { -1, -1, -1 }; + for ( int iChild = 0; iChild < 2; ++iChild ) + { + iChildPatch[iChild] = g_Patches.AddToTail(); + + float flArea = 0.0f; + if ( !InitPatch( iChildPatch[iChild], iParentPatch, iChild, vecChildPoints[iChild], nChildIndices, flArea ) ) + { + if ( iChild == 0 ) + { + pParentPatch->child1 = g_Patches.InvalidIndex(); + g_Patches.Remove( iChildPatch[iChild] ); + break; + } + else + { + pParentPatch->child1 = g_Patches.InvalidIndex(); + pParentPatch->child2 = g_Patches.InvalidIndex(); + g_Patches.Remove( iChildPatch[iChild] ); + g_Patches.Remove( iChildPatch[0] ); + } + } + } + + // Continue creating children patches. + CreateChildPatchesSub( iChildPatch[0] ); + CreateChildPatchesSub( iChildPatch[1] ); +} + +int PlaneTypeForNormal (Vector& normal) +{ + vec_t ax, ay, az; + + // NOTE: should these have an epsilon around 1.0? + if (normal[0] == 1.0 || normal[0] == -1.0) + return PLANE_X; + if (normal[1] == 1.0 || normal[1] == -1.0) + return PLANE_Y; + if (normal[2] == 1.0 || normal[2] == -1.0) + return PLANE_Z; + + ax = fabs(normal[0]); + ay = fabs(normal[1]); + az = fabs(normal[2]); + + if (ax >= ay && ax >= az) + return PLANE_ANYX; + if (ay >= ax && ay >= az) + return PLANE_ANYY; + return PLANE_ANYZ; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iPatch - +// iParentPatch - +// iChild - +// *pPoints - +// *pIndices - +// &flArea - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CVRADDispColl::InitParentPatch( int iPatch, Vector *pPoints, float &flArea ) +{ + // Get the current patch. + CPatch *pPatch = &g_Patches[iPatch]; + if ( !pPatch ) + return false; + + // Clear the patch data. + memset( pPatch, 0, sizeof( CPatch ) ); + + // This is a parent. + pPatch->ndxNext = g_FacePatches.Element( GetParentIndex() ); + g_FacePatches[GetParentIndex()] = iPatch; + pPatch->faceNumber = GetParentIndex(); + + // Initialize parent and children indices. + pPatch->child1 = g_Patches.InvalidIndex(); + pPatch->child2 = g_Patches.InvalidIndex(); + pPatch->parent = g_Patches.InvalidIndex(); + pPatch->ndxNextClusterChild = g_Patches.InvalidIndex(); + pPatch->ndxNextParent = g_Patches.InvalidIndex(); + + Vector vecEdges[2]; + vecEdges[0] = pPoints[1] - pPoints[0]; + vecEdges[1] = pPoints[3] - pPoints[0]; + + // Calculate the triangle normal and area. + Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); + flArea = VectorNormalize( vecNormal ); + + // Initialize the patch scale. + pPatch->scale[0] = pPatch->scale[1] = 1.0f; + + // Set the patch chop - minchop (that is what the minimum area is based on). + pPatch->chop = dispchop; + + // Displacements are not sky! + pPatch->sky = false; + + // Copy the winding. + Vector vecCenter( 0.0f, 0.0f, 0.0f ); + pPatch->winding = AllocWinding( 4 ); + pPatch->winding->numpoints = 4; + for ( int iPoint = 0; iPoint < 4; ++iPoint ) + { + VectorCopy( pPoints[iPoint], pPatch->winding->p[iPoint] ); + VectorAdd( pPoints[iPoint], vecCenter, vecCenter ); + } + + // Set the origin and normal. + VectorScale( vecCenter, ( 1.0f / 4.0f ), vecCenter ); + VectorCopy( vecCenter, pPatch->origin ); + VectorCopy( vecNormal, pPatch->normal ); + + // Create the plane. + pPatch->plane = new dplane_t; + if ( !pPatch->plane ) + return false; + + VectorCopy( vecNormal, pPatch->plane->normal ); + pPatch->plane->dist = vecNormal.Dot( pPoints[0] ); + pPatch->plane->type = PlaneTypeForNormal( pPatch->plane->normal ); + pPatch->planeDist = pPatch->plane->dist; + + // Set the area. + pPatch->area = flArea; + + // Calculate the mins/maxs. + Vector vecMin( FLT_MAX, FLT_MAX, FLT_MAX ); + Vector vecMax( FLT_MIN, FLT_MIN, FLT_MIN ); + for ( int iPoint = 0; iPoint < 4; ++iPoint ) + { + for ( int iAxis = 0; iAxis < 3; ++iAxis ) + { + vecMin[iAxis] = min( vecMin[iAxis], pPoints[iPoint][iAxis] ); + vecMax[iAxis] = max( vecMax[iAxis], pPoints[iPoint][iAxis] ); + } + } + + VectorCopy( vecMin, pPatch->mins ); + VectorCopy( vecMax, pPatch->maxs ); + VectorCopy( vecMin, pPatch->face_mins ); + VectorCopy( vecMax, pPatch->face_maxs ); + + // Check for bumpmap. + dface_t *pFace = dfaces + pPatch->faceNumber; + texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; + pPatch->needsBumpmap = pTexInfo->flags & SURF_BUMPLIGHT ? true : false; + + // Misc... + pPatch->m_IterationKey = 0; + + // Calculate the base light, area, and reflectivity. + BaseLightForFace( &g_pFaces[pPatch->faceNumber], pPatch->baselight, &pPatch->basearea, pPatch->reflectivity ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPatch - +// *pPoints - +// &vecNormal - +// flArea - +//----------------------------------------------------------------------------- +bool CVRADDispColl::InitPatch( int iPatch, int iParentPatch, int iChild, Vector *pPoints, int *pIndices, float &flArea ) +{ + // Get the current patch. + CPatch *pPatch = &g_Patches[iPatch]; + if ( !pPatch ) + return false; + + // Clear the patch data. + memset( pPatch, 0, sizeof( CPatch ) ); + + // Setup the parent if we are not the parent. + CPatch *pParentPatch = NULL; + if ( iParentPatch != g_Patches.InvalidIndex() ) + { + // Get the parent patch. + pParentPatch = &g_Patches[iParentPatch]; + if ( !pParentPatch ) + return false; + } + + // Attach the face to the correct lists. + if ( !pParentPatch ) + { + // This is a parent. + pPatch->ndxNext = g_FacePatches.Element( GetParentIndex() ); + g_FacePatches[GetParentIndex()] = iPatch; + pPatch->faceNumber = GetParentIndex(); + } + else + { + pPatch->ndxNext = g_Patches.InvalidIndex(); + pPatch->faceNumber = pParentPatch->faceNumber; + + // Attach to the parent patch. + if ( iChild == 0 ) + { + pParentPatch->child1 = iPatch; + } + else + { + pParentPatch->child2 = iPatch; + } + } + + // Initialize parent and children indices. + pPatch->child1 = g_Patches.InvalidIndex(); + pPatch->child2 = g_Patches.InvalidIndex(); + pPatch->ndxNextClusterChild = g_Patches.InvalidIndex(); + pPatch->ndxNextParent = g_Patches.InvalidIndex(); + pPatch->parent = iParentPatch; + + // Get triangle edges. + Vector vecEdges[3]; + vecEdges[0] = pPoints[1] - pPoints[0]; + vecEdges[1] = pPoints[2] - pPoints[0]; + vecEdges[2] = pPoints[2] - pPoints[1]; + + // Find the longest edge. +// float flEdgeLength = 0.0f; +// for ( int iEdge = 0; iEdge < 3; ++iEdge ) +// { +// if ( flEdgeLength < vecEdges[iEdge].Length() ) +// { +// flEdgeLength = vecEdges[iEdge].Length(); +// } +// } + + // Calculate the triangle normal and area. + Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); + flArea = VectorNormalize( vecNormal ); + flArea *= 0.5f; + + // Initialize the patch scale. + pPatch->scale[0] = pPatch->scale[1] = 1.0f; + + // Set the patch chop - minchop (that is what the minimum area is based on). + pPatch->chop = dispchop; + + // Displacements are not sky! + pPatch->sky = false; + + // Copy the winding. + Vector vecCenter( 0.0f, 0.0f, 0.0f ); + pPatch->winding = AllocWinding( 3 ); + pPatch->winding->numpoints = 3; + for ( int iPoint = 0; iPoint < 3; ++iPoint ) + { + VectorCopy( pPoints[iPoint], pPatch->winding->p[iPoint] ); + VectorAdd( pPoints[iPoint], vecCenter, vecCenter ); + + pPatch->indices[iPoint] = static_cast( pIndices[iPoint] ); + } + + // Set the origin and normal. + VectorScale( vecCenter, ( 1.0f / 3.0f ), vecCenter ); + VectorCopy( vecCenter, pPatch->origin ); + VectorCopy( vecNormal, pPatch->normal ); + + // Create the plane. + pPatch->plane = new dplane_t; + if ( !pPatch->plane ) + return false; + + VectorCopy( vecNormal, pPatch->plane->normal ); + pPatch->plane->dist = vecNormal.Dot( pPoints[0] ); + pPatch->plane->type = PlaneTypeForNormal( pPatch->plane->normal ); + pPatch->planeDist = pPatch->plane->dist; + + // Set the area. + pPatch->area = flArea; + + // Calculate the mins/maxs. + Vector vecMin( FLT_MAX, FLT_MAX, FLT_MAX ); + Vector vecMax( FLT_MIN, FLT_MIN, FLT_MIN ); + for ( int iPoint = 0; iPoint < 3; ++iPoint ) + { + for ( int iAxis = 0; iAxis < 3; ++iAxis ) + { + vecMin[iAxis] = min( vecMin[iAxis], pPoints[iPoint][iAxis] ); + vecMax[iAxis] = max( vecMax[iAxis], pPoints[iPoint][iAxis] ); + } + } + + VectorCopy( vecMin, pPatch->mins ); + VectorCopy( vecMax, pPatch->maxs ); + + if ( !pParentPatch ) + { + VectorCopy( vecMin, pPatch->face_mins ); + VectorCopy( vecMax, pPatch->face_maxs ); + } + else + { + VectorCopy( pParentPatch->face_mins, pPatch->face_mins ); + VectorCopy( pParentPatch->face_maxs, pPatch->face_maxs ); + } + + // Check for bumpmap. + dface_t *pFace = dfaces + pPatch->faceNumber; + texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; + pPatch->needsBumpmap = pTexInfo->flags & SURF_BUMPLIGHT ? true : false; + + // Misc... + pPatch->m_IterationKey = 0; + + // Get the base light for the face. + if ( !pParentPatch ) + { + BaseLightForFace( &g_pFaces[pPatch->faceNumber], pPatch->baselight, &pPatch->basearea, pPatch->reflectivity ); + } + else + { + VectorCopy( pParentPatch->baselight, pPatch->baselight ); + pPatch->basearea = pParentPatch->basearea; + pPatch->reflectivity = pParentPatch->reflectivity; + } + + return true; +} + +void CVRADDispColl::AddPolysForRayTrace( void ) +{ + if ( !( m_nContents & MASK_OPAQUE ) ) + return; + + for ( int ndxTri = 0; ndxTri < m_aTris.Size(); ndxTri++ ) + { + CDispCollTri *tri = m_aTris.Base() + ndxTri; + int v[3]; + for ( int ndxv = 0; ndxv < 3; ndxv++ ) + v[ndxv] = tri->GetVert(ndxv); + + Vector fullCoverage; + fullCoverage.x = 1.0f; + g_RtEnv.AddTriangle( TRACE_ID_OPAQUE, m_aVerts[v[0]], m_aVerts[v[1]], m_aVerts[v[2]], fullCoverage ); + } } \ No newline at end of file diff --git a/mp/src/utils/vrad/vrad_dispcoll.h b/mp/src/utils/vrad/vrad_dispcoll.h index 668d3118..787c3137 100644 --- a/mp/src/utils/vrad/vrad_dispcoll.h +++ b/mp/src/utils/vrad/vrad_dispcoll.h @@ -1,80 +1,80 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef VRAD_DISPCOLL_H -#define VRAD_DISPCOLL_H -#pragma once - -#include -#include "DispColl_Common.h" - -//============================================================================= -// -// VRAD specific collision -// -#define VRAD_QUAD_SIZE 4 - -struct CPatch; - -class CVRADDispColl : public CDispCollTree -{ -public: - - // Creation/Destruction Functions - CVRADDispColl(); - ~CVRADDispColl(); - bool Create( CCoreDispInfo *pDisp ); - - // Patches. - bool InitPatch( int iPatch, int iParentPatch, int iChild, Vector *pPoints, int *pIndices, float &flArea ); - bool InitParentPatch( int iPatch, Vector *pPoints, float &flArea ); - float CreateParentPatches( void ); - void CreateChildPatches( int iParentPatch, int nLevel ); - void CreateChildPatchesFromRoot( int iParentPatch, int *pChildPatch ); - void CreateChildPatchesSub( int iParentPatch ); - - // Operations Functions - void BaseFacePlaneToDispUV( Vector const &vecPlanePt, Vector2D &dispUV ); - void DispUVToSurfPoint( Vector2D const &dispUV, Vector &vecPoint, float flPushEps ); - void DispUVToSurfNormal( Vector2D const &dispUV, Vector &vecNormal ); - - // Data. - inline float GetSampleRadius2( void ) { return m_flSampleRadius2; } - inline float GetPatchSampleRadius2( void ) { return m_flPatchSampleRadius2; } - - inline int GetParentIndex( void ) { return m_iParent; } - inline void GetParentFaceNormal( Vector &vecNormal ) { vecNormal = m_vecStabDir; } - - inline void GetVert( int iVert, Vector &vecVert ) { Assert( ( iVert >= 0 ) && ( iVert < GetSize() ) ); vecVert = m_aVerts[iVert]; } - inline void GetVertNormal( int iVert, Vector &vecNormal ) { Assert( ( iVert >= 0 ) && ( iVert < GetSize() ) ); vecNormal = m_aVertNormals[iVert]; } - inline Vector2D const& GetLuxelCoord( int iLuxel ) { Assert( ( iLuxel >= 0 ) && ( iLuxel < GetSize() ) ); return m_aLuxelCoords[iLuxel]; } - - // Raytracing - void AddPolysForRayTrace( void ); - -protected: - - void CalcSampleRadius2AndBox( dface_t *pFace ); - - // Utility. - void DispUVToSurf_TriTLToBR( Vector &vecPoint, float flPushEps, float flU, float flV, int nSnapU, int nSnapV, int nWidth, int nHeight ); - void DispUVToSurf_TriBLToTR( Vector &vecPoint, float flPushEps, float flU, float flV, int nSnapU, int nSnapV, int nWidth, int nHeight ); - void GetSurfaceMinMax( Vector &boxMin, Vector &boxMax ); - void GetMinorAxes( Vector const &vecNormal, int &nAxis0, int &nAxis1 ); - -protected: - - int m_iParent; // Parent index - float m_flSampleRadius2; // Sampling radius - float m_flPatchSampleRadius2; // Patch sampling radius (max bound) - float m_flSampleWidth; - float m_flSampleHeight; - CUtlVector m_aLuxelCoords; // Lightmap coordinates. - CUtlVector m_aVertNormals; // Displacement vertex normals -}; - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VRAD_DISPCOLL_H +#define VRAD_DISPCOLL_H +#pragma once + +#include +#include "DispColl_Common.h" + +//============================================================================= +// +// VRAD specific collision +// +#define VRAD_QUAD_SIZE 4 + +struct CPatch; + +class CVRADDispColl : public CDispCollTree +{ +public: + + // Creation/Destruction Functions + CVRADDispColl(); + ~CVRADDispColl(); + bool Create( CCoreDispInfo *pDisp ); + + // Patches. + bool InitPatch( int iPatch, int iParentPatch, int iChild, Vector *pPoints, int *pIndices, float &flArea ); + bool InitParentPatch( int iPatch, Vector *pPoints, float &flArea ); + float CreateParentPatches( void ); + void CreateChildPatches( int iParentPatch, int nLevel ); + void CreateChildPatchesFromRoot( int iParentPatch, int *pChildPatch ); + void CreateChildPatchesSub( int iParentPatch ); + + // Operations Functions + void BaseFacePlaneToDispUV( Vector const &vecPlanePt, Vector2D &dispUV ); + void DispUVToSurfPoint( Vector2D const &dispUV, Vector &vecPoint, float flPushEps ); + void DispUVToSurfNormal( Vector2D const &dispUV, Vector &vecNormal ); + + // Data. + inline float GetSampleRadius2( void ) { return m_flSampleRadius2; } + inline float GetPatchSampleRadius2( void ) { return m_flPatchSampleRadius2; } + + inline int GetParentIndex( void ) { return m_iParent; } + inline void GetParentFaceNormal( Vector &vecNormal ) { vecNormal = m_vecStabDir; } + + inline void GetVert( int iVert, Vector &vecVert ) { Assert( ( iVert >= 0 ) && ( iVert < GetSize() ) ); vecVert = m_aVerts[iVert]; } + inline void GetVertNormal( int iVert, Vector &vecNormal ) { Assert( ( iVert >= 0 ) && ( iVert < GetSize() ) ); vecNormal = m_aVertNormals[iVert]; } + inline Vector2D const& GetLuxelCoord( int iLuxel ) { Assert( ( iLuxel >= 0 ) && ( iLuxel < GetSize() ) ); return m_aLuxelCoords[iLuxel]; } + + // Raytracing + void AddPolysForRayTrace( void ); + +protected: + + void CalcSampleRadius2AndBox( dface_t *pFace ); + + // Utility. + void DispUVToSurf_TriTLToBR( Vector &vecPoint, float flPushEps, float flU, float flV, int nSnapU, int nSnapV, int nWidth, int nHeight ); + void DispUVToSurf_TriBLToTR( Vector &vecPoint, float flPushEps, float flU, float flV, int nSnapU, int nSnapV, int nWidth, int nHeight ); + void GetSurfaceMinMax( Vector &boxMin, Vector &boxMax ); + void GetMinorAxes( Vector const &vecNormal, int &nAxis0, int &nAxis1 ); + +protected: + + int m_iParent; // Parent index + float m_flSampleRadius2; // Sampling radius + float m_flPatchSampleRadius2; // Patch sampling radius (max bound) + float m_flSampleWidth; + float m_flSampleHeight; + CUtlVector m_aLuxelCoords; // Lightmap coordinates. + CUtlVector m_aVertNormals; // Displacement vertex normals +}; + #endif // VRAD_DISPCOLL_H \ No newline at end of file diff --git a/mp/src/utils/vrad/vrad_dll.vpc b/mp/src/utils/vrad/vrad_dll.vpc index 02fb290a..a1ae67b4 100644 --- a/mp/src/utils/vrad/vrad_dll.vpc +++ b/mp/src/utils/vrad/vrad_dll.vpc @@ -1,225 +1,225 @@ -//----------------------------------------------------------------------------- -// VRAD_DLL.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" - -$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" - -$Configuration -{ - $Compiler - { - $AdditionalIncludeDirectories "$BASE,..\common,..\vmpi,..\vmpi\mysql\mysqlpp\include,..\vmpi\mysql\include" - $PreprocessorDefinitions "$BASE;MPI;PROTECTED_THINGS_DISABLE;VRAD" - } - - $Linker - { - $AdditionalDependencies "$BASE ws2_32.lib" - } -} - -$Project "Vrad_dll" -{ - $Folder "Source Files" - { - $File "$SRCDIR\public\BSPTreeData.cpp" - $File "$SRCDIR\public\disp_common.cpp" - $File "$SRCDIR\public\disp_powerinfo.cpp" - $File "disp_vrad.cpp" - $File "imagepacker.cpp" - $File "incremental.cpp" - $File "leaf_ambient_lighting.cpp" - $File "lightmap.cpp" - $File "$SRCDIR\public\loadcmdline.cpp" - $File "$SRCDIR\public\lumpfiles.cpp" - $File "macro_texture.cpp" - $File "..\common\mpi_stats.cpp" - $File "mpivrad.cpp" - $File "..\common\MySqlDatabase.cpp" - $File "..\common\pacifier.cpp" - $File "..\common\physdll.cpp" - $File "radial.cpp" - $File "SampleHash.cpp" - $File "trace.cpp" - $File "..\common\utilmatlib.cpp" - $File "vismat.cpp" - $File "..\common\vmpi_tools_shared.cpp" - $File "..\common\vmpi_tools_shared.h" - $File "vrad.cpp" - $File "VRAD_DispColl.cpp" - $File "VradDetailProps.cpp" - $File "VRadDisps.cpp" - $File "vraddll.cpp" - $File "VRadStaticProps.cpp" - $File "$SRCDIR\public\zip_utils.cpp" - - $Folder "Common Files" - { - $File "..\common\bsplib.cpp" - $File "$SRCDIR\public\builddisp.cpp" - $File "$SRCDIR\public\ChunkFile.cpp" - $File "..\common\cmdlib.cpp" - $File "$SRCDIR\public\DispColl_Common.cpp" - $File "..\common\map_shared.cpp" - $File "..\common\polylib.cpp" - $File "..\common\scriplib.cpp" - $File "..\common\threads.cpp" - $File "..\common\tools_minidump.cpp" - $File "..\common\tools_minidump.h" - } - - $Folder "Public Files" - { - $File "$SRCDIR\public\CollisionUtils.cpp" - $File "$SRCDIR\public\filesystem_helpers.cpp" - $File "$SRCDIR\public\ScratchPad3D.cpp" - $File "$SRCDIR\public\ScratchPadUtils.cpp" - } - } - - $Folder "Header Files" - { - $File "disp_vrad.h" - $File "iincremental.h" - $File "imagepacker.h" - $File "incremental.h" - $File "leaf_ambient_lighting.h" - $File "lightmap.h" - $File "macro_texture.h" - $File "$SRCDIR\public\map_utils.h" - $File "mpivrad.h" - $File "radial.h" - $File "$SRCDIR\public\bitmap\tgawriter.h" - $File "vismat.h" - $File "vrad.h" - $File "VRAD_DispColl.h" - $File "vraddetailprops.h" - $File "vraddll.h" - - $Folder "Common Header Files" - { - $File "..\common\bsplib.h" - $File "..\common\cmdlib.h" - $File "..\common\consolewnd.h" - $File "..\vmpi\ichannel.h" - $File "..\vmpi\imysqlwrapper.h" - $File "..\vmpi\iphelpers.h" - $File "..\common\ISQLDBReplyTarget.h" - $File "..\common\map_shared.h" - $File "..\vmpi\messbuf.h" - $File "..\common\mpi_stats.h" - $File "..\common\MySqlDatabase.h" - $File "..\common\pacifier.h" - $File "..\common\polylib.h" - $File "..\common\scriplib.h" - $File "..\vmpi\threadhelpers.h" - $File "..\common\threads.h" - $File "..\common\utilmatlib.h" - $File "..\vmpi\vmpi_defs.h" - $File "..\vmpi\vmpi_dispatch.h" - $File "..\vmpi\vmpi_distribute_work.h" - $File "..\vmpi\vmpi_filesystem.h" - } - - $Folder "Public Header Files" - { - $File "$SRCDIR\public\mathlib\amd3dx.h" - $File "$SRCDIR\public\mathlib\ANORMS.H" - $File "$SRCDIR\public\basehandle.h" - $File "$SRCDIR\public\tier0\basetypes.h" - $File "$SRCDIR\public\tier1\bitbuf.h" - $File "$SRCDIR\public\bitvec.h" - $File "$SRCDIR\public\BSPFILE.H" - $File "$SRCDIR\public\bspflags.h" - $File "$SRCDIR\public\BSPTreeData.h" - $File "$SRCDIR\public\builddisp.h" - $File "$SRCDIR\public\mathlib\bumpvects.h" - $File "$SRCDIR\public\tier1\byteswap.h" - $File "$SRCDIR\public\tier1\characterset.h" - $File "$SRCDIR\public\tier1\checksum_crc.h" - $File "$SRCDIR\public\tier1\checksum_md5.h" - $File "$SRCDIR\public\ChunkFile.h" - $File "$SRCDIR\public\cmodel.h" - $File "$SRCDIR\public\CollisionUtils.h" - $File "$SRCDIR\public\tier0\commonmacros.h" - $File "$SRCDIR\public\mathlib\compressed_vector.h" - $File "$SRCDIR\public\const.h" - $File "$SRCDIR\public\coordsize.h" - $File "$SRCDIR\public\tier0\dbg.h" - $File "$SRCDIR\public\disp_common.h" - $File "$SRCDIR\public\disp_powerinfo.h" - $File "$SRCDIR\public\disp_vertindex.h" - $File "$SRCDIR\public\DispColl_Common.h" - $File "$SRCDIR\public\tier0\fasttimer.h" - $File "$SRCDIR\public\filesystem.h" - $File "$SRCDIR\public\filesystem_helpers.h" - $File "$SRCDIR\public\GameBSPFile.h" - $File "$SRCDIR\public\gametrace.h" - $File "$SRCDIR\public\mathlib\halton.h" - $File "$SRCDIR\public\materialsystem\hardwareverts.h" - $File "$SRCDIR\public\appframework\IAppSystem.h" - $File "$SRCDIR\public\tier0\icommandline.h" - $File "$SRCDIR\public\ihandleentity.h" - $File "$SRCDIR\public\materialsystem\imaterial.h" - $File "$SRCDIR\public\materialsystem\imaterialsystem.h" - $File "$SRCDIR\public\materialsystem\imaterialvar.h" - $File "$SRCDIR\public\tier1\interface.h" - $File "$SRCDIR\public\iscratchpad3d.h" - $File "$SRCDIR\public\ivraddll.h" - $File "$SRCDIR\public\materialsystem\materialsystem_config.h" - $File "$SRCDIR\public\mathlib\mathlib.h" - $File "$SRCDIR\public\tier0\memdbgon.h" - $File "$SRCDIR\public\optimize.h" - $File "$SRCDIR\public\phyfile.h" - $File "..\common\physdll.h" - $File "$SRCDIR\public\tier0\platform.h" - $File "$SRCDIR\public\tier0\protected_things.h" - $File "$SRCDIR\public\vstdlib\random.h" - $File "$SRCDIR\public\ScratchPad3D.h" - $File "$SRCDIR\public\ScratchPadUtils.h" - $File "$SRCDIR\public\string_t.h" - $File "$SRCDIR\public\tier1\strtools.h" - $File "$SRCDIR\public\studio.h" - $File "$SRCDIR\public\tier1\tokenreader.h" - $File "$SRCDIR\public\trace.h" - $File "$SRCDIR\public\tier1\utlbuffer.h" - $File "$SRCDIR\public\tier1\utldict.h" - $File "$SRCDIR\public\tier1\utlhash.h" - $File "$SRCDIR\public\tier1\utllinkedlist.h" - $File "$SRCDIR\public\tier1\utlmemory.h" - $File "$SRCDIR\public\tier1\utlrbtree.h" - $File "$SRCDIR\public\tier1\utlsymbol.h" - $File "$SRCDIR\public\tier1\utlvector.h" - $File "$SRCDIR\public\vcollide.h" - $File "$SRCDIR\public\mathlib\vector.h" - $File "$SRCDIR\public\mathlib\vector2d.h" - $File "$SRCDIR\public\mathlib\vector4d.h" - $File "$SRCDIR\public\mathlib\vmatrix.h" - $File "..\vmpi\vmpi.h" - $File "$SRCDIR\public\vphysics_interface.h" - $File "$SRCDIR\public\mathlib\vplane.h" - $File "$SRCDIR\public\tier0\vprof.h" - $File "$SRCDIR\public\vstdlib\vstdlib.h" - $File "$SRCDIR\public\vtf\vtf.h" - $File "$SRCDIR\public\wadtypes.h" - $File "$SRCDIR\public\worldsize.h" - } - } - - $Folder "Link Libraries" - { - $Lib bitmap - $Lib mathlib - $Lib raytrace - $Lib tier2 - $Lib vmpi - $Lib vtf - } - - $File "notes.txt" -} +//----------------------------------------------------------------------------- +// VRAD_DLL.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\common,..\vmpi,..\vmpi\mysql\mysqlpp\include,..\vmpi\mysql\include" + $PreprocessorDefinitions "$BASE;MPI;PROTECTED_THINGS_DISABLE;VRAD" + } + + $Linker + { + $AdditionalDependencies "$BASE ws2_32.lib" + } +} + +$Project "Vrad_dll" +{ + $Folder "Source Files" + { + $File "$SRCDIR\public\BSPTreeData.cpp" + $File "$SRCDIR\public\disp_common.cpp" + $File "$SRCDIR\public\disp_powerinfo.cpp" + $File "disp_vrad.cpp" + $File "imagepacker.cpp" + $File "incremental.cpp" + $File "leaf_ambient_lighting.cpp" + $File "lightmap.cpp" + $File "$SRCDIR\public\loadcmdline.cpp" + $File "$SRCDIR\public\lumpfiles.cpp" + $File "macro_texture.cpp" + $File "..\common\mpi_stats.cpp" + $File "mpivrad.cpp" + $File "..\common\MySqlDatabase.cpp" + $File "..\common\pacifier.cpp" + $File "..\common\physdll.cpp" + $File "radial.cpp" + $File "SampleHash.cpp" + $File "trace.cpp" + $File "..\common\utilmatlib.cpp" + $File "vismat.cpp" + $File "..\common\vmpi_tools_shared.cpp" + $File "..\common\vmpi_tools_shared.h" + $File "vrad.cpp" + $File "VRAD_DispColl.cpp" + $File "VradDetailProps.cpp" + $File "VRadDisps.cpp" + $File "vraddll.cpp" + $File "VRadStaticProps.cpp" + $File "$SRCDIR\public\zip_utils.cpp" + + $Folder "Common Files" + { + $File "..\common\bsplib.cpp" + $File "$SRCDIR\public\builddisp.cpp" + $File "$SRCDIR\public\ChunkFile.cpp" + $File "..\common\cmdlib.cpp" + $File "$SRCDIR\public\DispColl_Common.cpp" + $File "..\common\map_shared.cpp" + $File "..\common\polylib.cpp" + $File "..\common\scriplib.cpp" + $File "..\common\threads.cpp" + $File "..\common\tools_minidump.cpp" + $File "..\common\tools_minidump.h" + } + + $Folder "Public Files" + { + $File "$SRCDIR\public\CollisionUtils.cpp" + $File "$SRCDIR\public\filesystem_helpers.cpp" + $File "$SRCDIR\public\ScratchPad3D.cpp" + $File "$SRCDIR\public\ScratchPadUtils.cpp" + } + } + + $Folder "Header Files" + { + $File "disp_vrad.h" + $File "iincremental.h" + $File "imagepacker.h" + $File "incremental.h" + $File "leaf_ambient_lighting.h" + $File "lightmap.h" + $File "macro_texture.h" + $File "$SRCDIR\public\map_utils.h" + $File "mpivrad.h" + $File "radial.h" + $File "$SRCDIR\public\bitmap\tgawriter.h" + $File "vismat.h" + $File "vrad.h" + $File "VRAD_DispColl.h" + $File "vraddetailprops.h" + $File "vraddll.h" + + $Folder "Common Header Files" + { + $File "..\common\bsplib.h" + $File "..\common\cmdlib.h" + $File "..\common\consolewnd.h" + $File "..\vmpi\ichannel.h" + $File "..\vmpi\imysqlwrapper.h" + $File "..\vmpi\iphelpers.h" + $File "..\common\ISQLDBReplyTarget.h" + $File "..\common\map_shared.h" + $File "..\vmpi\messbuf.h" + $File "..\common\mpi_stats.h" + $File "..\common\MySqlDatabase.h" + $File "..\common\pacifier.h" + $File "..\common\polylib.h" + $File "..\common\scriplib.h" + $File "..\vmpi\threadhelpers.h" + $File "..\common\threads.h" + $File "..\common\utilmatlib.h" + $File "..\vmpi\vmpi_defs.h" + $File "..\vmpi\vmpi_dispatch.h" + $File "..\vmpi\vmpi_distribute_work.h" + $File "..\vmpi\vmpi_filesystem.h" + } + + $Folder "Public Header Files" + { + $File "$SRCDIR\public\mathlib\amd3dx.h" + $File "$SRCDIR\public\mathlib\ANORMS.H" + $File "$SRCDIR\public\basehandle.h" + $File "$SRCDIR\public\tier0\basetypes.h" + $File "$SRCDIR\public\tier1\bitbuf.h" + $File "$SRCDIR\public\bitvec.h" + $File "$SRCDIR\public\BSPFILE.H" + $File "$SRCDIR\public\bspflags.h" + $File "$SRCDIR\public\BSPTreeData.h" + $File "$SRCDIR\public\builddisp.h" + $File "$SRCDIR\public\mathlib\bumpvects.h" + $File "$SRCDIR\public\tier1\byteswap.h" + $File "$SRCDIR\public\tier1\characterset.h" + $File "$SRCDIR\public\tier1\checksum_crc.h" + $File "$SRCDIR\public\tier1\checksum_md5.h" + $File "$SRCDIR\public\ChunkFile.h" + $File "$SRCDIR\public\cmodel.h" + $File "$SRCDIR\public\CollisionUtils.h" + $File "$SRCDIR\public\tier0\commonmacros.h" + $File "$SRCDIR\public\mathlib\compressed_vector.h" + $File "$SRCDIR\public\const.h" + $File "$SRCDIR\public\coordsize.h" + $File "$SRCDIR\public\tier0\dbg.h" + $File "$SRCDIR\public\disp_common.h" + $File "$SRCDIR\public\disp_powerinfo.h" + $File "$SRCDIR\public\disp_vertindex.h" + $File "$SRCDIR\public\DispColl_Common.h" + $File "$SRCDIR\public\tier0\fasttimer.h" + $File "$SRCDIR\public\filesystem.h" + $File "$SRCDIR\public\filesystem_helpers.h" + $File "$SRCDIR\public\GameBSPFile.h" + $File "$SRCDIR\public\gametrace.h" + $File "$SRCDIR\public\mathlib\halton.h" + $File "$SRCDIR\public\materialsystem\hardwareverts.h" + $File "$SRCDIR\public\appframework\IAppSystem.h" + $File "$SRCDIR\public\tier0\icommandline.h" + $File "$SRCDIR\public\ihandleentity.h" + $File "$SRCDIR\public\materialsystem\imaterial.h" + $File "$SRCDIR\public\materialsystem\imaterialsystem.h" + $File "$SRCDIR\public\materialsystem\imaterialvar.h" + $File "$SRCDIR\public\tier1\interface.h" + $File "$SRCDIR\public\iscratchpad3d.h" + $File "$SRCDIR\public\ivraddll.h" + $File "$SRCDIR\public\materialsystem\materialsystem_config.h" + $File "$SRCDIR\public\mathlib\mathlib.h" + $File "$SRCDIR\public\tier0\memdbgon.h" + $File "$SRCDIR\public\optimize.h" + $File "$SRCDIR\public\phyfile.h" + $File "..\common\physdll.h" + $File "$SRCDIR\public\tier0\platform.h" + $File "$SRCDIR\public\tier0\protected_things.h" + $File "$SRCDIR\public\vstdlib\random.h" + $File "$SRCDIR\public\ScratchPad3D.h" + $File "$SRCDIR\public\ScratchPadUtils.h" + $File "$SRCDIR\public\string_t.h" + $File "$SRCDIR\public\tier1\strtools.h" + $File "$SRCDIR\public\studio.h" + $File "$SRCDIR\public\tier1\tokenreader.h" + $File "$SRCDIR\public\trace.h" + $File "$SRCDIR\public\tier1\utlbuffer.h" + $File "$SRCDIR\public\tier1\utldict.h" + $File "$SRCDIR\public\tier1\utlhash.h" + $File "$SRCDIR\public\tier1\utllinkedlist.h" + $File "$SRCDIR\public\tier1\utlmemory.h" + $File "$SRCDIR\public\tier1\utlrbtree.h" + $File "$SRCDIR\public\tier1\utlsymbol.h" + $File "$SRCDIR\public\tier1\utlvector.h" + $File "$SRCDIR\public\vcollide.h" + $File "$SRCDIR\public\mathlib\vector.h" + $File "$SRCDIR\public\mathlib\vector2d.h" + $File "$SRCDIR\public\mathlib\vector4d.h" + $File "$SRCDIR\public\mathlib\vmatrix.h" + $File "..\vmpi\vmpi.h" + $File "$SRCDIR\public\vphysics_interface.h" + $File "$SRCDIR\public\mathlib\vplane.h" + $File "$SRCDIR\public\tier0\vprof.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + $File "$SRCDIR\public\vtf\vtf.h" + $File "$SRCDIR\public\wadtypes.h" + $File "$SRCDIR\public\worldsize.h" + } + } + + $Folder "Link Libraries" + { + $Lib bitmap + $Lib mathlib + $Lib raytrace + $Lib tier2 + $Lib vmpi + $Lib vtf + } + + $File "notes.txt" +} diff --git a/mp/src/utils/vrad/vraddetailprops.cpp b/mp/src/utils/vrad/vraddetailprops.cpp index 822bc78c..93232595 100644 --- a/mp/src/utils/vrad/vraddetailprops.cpp +++ b/mp/src/utils/vrad/vraddetailprops.cpp @@ -1,1035 +1,1035 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Revision: $ -// $NoKeywords: $ -// -// This file contains code to allow us to associate client data with bsp leaves. -// -//=============================================================================// - -#include "vrad.h" -#include "Bsplib.h" -#include "GameBSPFile.h" -#include "UtlBuffer.h" -#include "utlvector.h" -#include "CModel.h" -#include "studio.h" -#include "pacifier.h" -#include "vraddetailprops.h" -#include "mathlib/halton.h" -#include "messbuf.h" -#include "byteswap.h" - -bool LoadStudioModel( char const* pModelName, CUtlBuffer& buf ); - - -//----------------------------------------------------------------------------- -// Purpose: Writes a glview text file containing the collision surface in question -// Input : *pCollide - -// *pFilename - -//----------------------------------------------------------------------------- -void DumpRayToGlView( Ray_t const& ray, float dist, Vector* pColor, const char *pFilename ) -{ - Vector dir = ray.m_Delta; - float len = VectorNormalize(dir); - if (len < 1e-3) - return; - - Vector up( 0, 0, 1 ); - Vector crossDir; - if (fabs(DotProduct(up, dir)) - 1.0f < -1e-3 ) - { - CrossProduct( dir, up, crossDir ); - VectorNormalize(crossDir); - } - else - { - up.Init( 0, 1, 0 ); - CrossProduct( dir, up, crossDir ); - VectorNormalize(crossDir); - } - - Vector end; - Vector start1, start2; - VectorMA( ray.m_Start, dist, ray.m_Delta, end ); - VectorMA( ray.m_Start, -2, crossDir, start1 ); - VectorMA( ray.m_Start, 2, crossDir, start2 ); - - FileHandle_t fp = g_pFileSystem->Open( pFilename, "a" ); - int vert = 0; - CmdLib_FPrintf( fp, "3\n" ); - CmdLib_FPrintf( fp, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", start1.x, start1.y, start1.z, - pColor->x, pColor->y, pColor->z ); - vert++; - CmdLib_FPrintf( fp, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", start2.x, start2.y, start2.z, - pColor->x, pColor->y, pColor->z ); - vert++; - CmdLib_FPrintf( fp, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", end.x, end.y, end.z, - pColor->x, pColor->y, pColor->z ); - vert++; - g_pFileSystem->Close( fp ); -} - - -//----------------------------------------------------------------------------- -// This puppy is used to construct the game lumps -//----------------------------------------------------------------------------- -static CUtlVector s_DetailPropLightStyleLumpLDR; -static CUtlVector s_DetailPropLightStyleLumpHDR; -static CUtlVector *s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpLDR; - -//----------------------------------------------------------------------------- -// An amount to add to each model to get to the model center -//----------------------------------------------------------------------------- -CUtlVector g_ModelCenterOffset; -CUtlVector g_SpriteCenterOffset; - -void VRadDetailProps_SetHDRMode( bool bHDR ) -{ - if( bHDR ) - { - s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpHDR; - } - else - { - s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpLDR; - } -} - -//----------------------------------------------------------------------------- -// Finds ambient sky lights -//----------------------------------------------------------------------------- -static directlight_t* FindAmbientSkyLight() -{ - static directlight_t *s_pCachedSkylight = NULL; - - // Don't keep searching for the same light. - if ( !s_pCachedSkylight ) - { - // find any ambient lights - directlight_t* dl; - for (dl = activelights; dl != 0; dl = dl->next) - { - if (dl->light.type == emit_skyambient) - { - s_pCachedSkylight = dl; - break; - } - } - } - - return s_pCachedSkylight; -} - - -//----------------------------------------------------------------------------- -// Compute world center of a prop -//----------------------------------------------------------------------------- -static void ComputeWorldCenter( DetailObjectLump_t& prop, Vector& center, Vector& normal ) -{ - // Transform the offset into world space - Vector forward, right; - AngleVectors( prop.m_Angles, &forward, &right, &normal ); - VectorCopy( prop.m_Origin, center ); - - // FIXME: Take orientation into account? - switch (prop.m_Type ) - { - case DETAIL_PROP_TYPE_MODEL: - VectorMA( center, g_ModelCenterOffset[prop.m_DetailModel].x, forward, center ); - VectorMA( center, -g_ModelCenterOffset[prop.m_DetailModel].y, right, center ); - VectorMA( center, g_ModelCenterOffset[prop.m_DetailModel].z, normal, center ); - break; - - case DETAIL_PROP_TYPE_SPRITE: - Vector vecOffset; - VectorMultiply( g_SpriteCenterOffset[prop.m_DetailModel], prop.m_flScale, vecOffset ); - VectorMA( center, vecOffset.x, forward, center ); - VectorMA( center, -vecOffset.y, right, center ); - VectorMA( center, vecOffset.z, normal, center ); - break; - } -} - - -//----------------------------------------------------------------------------- -// Computes max direct lighting for a single detal prop -//----------------------------------------------------------------------------- -static void ComputeMaxDirectLighting( DetailObjectLump_t& prop, Vector* maxcolor, int iThread ) -{ - // The max direct lighting must be along the direction to one - // of the static lights.... - - Vector origin, normal; - ComputeWorldCenter( prop, origin, normal ); - - if ( !origin.IsValid() || !normal.IsValid() ) - { - static bool s_Warned = false; - if ( !s_Warned ) - { - Warning("WARNING: Bogus detail props encountered!\n" ); - s_Warned = true; - } - - // fill with debug color - for ( int i = 0; i < MAX_LIGHTSTYLES; ++i) - { - maxcolor[i].Init(1,0,0); - } - return; - } - - int cluster = ClusterFromPoint(origin); - - Vector delta; - CUtlVector< directlight_t* > lights; - CUtlVector< Vector > directions; - - directlight_t* dl; - for (dl = activelights; dl != 0; dl = dl->next) - { - // skyambient doesn't affect dlights.. - if (dl->light.type == emit_skyambient) - continue; - - // is this lights cluster visible? - if ( PVSCheck( dl->pvs, cluster ) ) - { - lights.AddToTail(dl); - VectorSubtract( dl->light.origin, origin, delta ); - VectorNormalize( delta ); - directions.AddToTail( delta ); - } - } - - // Find the max illumination - int i; - for ( i = 0; i < MAX_LIGHTSTYLES; ++i) - { - maxcolor[i].Init(0,0,0); - } - - // NOTE: See version 10 for a method where we choose a normal based on whichever - // one produces the maximum possible illumination. This appeared to work better on - // e3_town, so I'm trying it now; hopefully it'll be good for all cases. - int j; - for ( j = 0; j < lights.Count(); ++j) - { - dl = lights[j]; - - SSE_sampleLightOutput_t out; - FourVectors origin4; - FourVectors normal4; - origin4.DuplicateVector( origin ); - normal4.DuplicateVector( normal ); - - GatherSampleLightSSE ( out, dl, -1, origin4, &normal4, 1, iThread ); - VectorMA( maxcolor[dl->light.style], out.m_flFalloff.m128_f32[0] * out.m_flDot[0].m128_f32[0], dl->light.intensity, maxcolor[dl->light.style] ); - } -} - - -//----------------------------------------------------------------------------- -// Computes the ambient term from a particular surface -//----------------------------------------------------------------------------- - -static void ComputeAmbientFromSurface( dface_t* pFace, directlight_t* pSkylight, - Vector& radcolor ) -{ - texinfo_t* pTex = &texinfo[pFace->texinfo]; - if (pTex) - { - // If we hit the sky, use the sky ambient - if (pTex->flags & SURF_SKY) - { - if (pSkylight) - { - // add in sky ambient - VectorDivide( pSkylight->light.intensity, 255.0f, radcolor ); - } - } - else - { - VectorMultiply( radcolor, dtexdata[pTex->texdata].reflectivity, radcolor ); - } - } -} - - -//----------------------------------------------------------------------------- -// Computes the lightmap color at a particular point -//----------------------------------------------------------------------------- - -static void ComputeLightmapColorFromAverage( dface_t* pFace, directlight_t* pSkylight, float scale, Vector pColor[MAX_LIGHTSTYLES] ) -{ - texinfo_t* pTex = &texinfo[pFace->texinfo]; - if (pTex->flags & SURF_SKY) - { - if (pSkylight) - { - // add in sky ambient - Vector amb = pSkylight->light.intensity / 255.0f; - pColor[0] += amb * scale; - } - return; - } - - for (int maps = 0 ; maps < MAXLIGHTMAPS && pFace->styles[maps] != 255 ; ++maps) - { - ColorRGBExp32* pAvgColor = dface_AvgLightColor( pFace, maps ); - - // this code expects values from [0..1] not [0..255] - Vector color; - color[0] = TexLightToLinear( pAvgColor->r, pAvgColor->exponent ); - color[1] = TexLightToLinear( pAvgColor->g, pAvgColor->exponent ); - color[2] = TexLightToLinear( pAvgColor->b, pAvgColor->exponent ); - - ComputeAmbientFromSurface( pFace, pSkylight, color ); - - int style = pFace->styles[maps]; - pColor[style] += color * scale; - } -} - - -//----------------------------------------------------------------------------- -// Returns true if the surface has bumped lightmaps -//----------------------------------------------------------------------------- - -static bool SurfHasBumpedLightmaps( dface_t *pSurf ) -{ - bool hasBumpmap = false; - if( ( texinfo[pSurf->texinfo].flags & SURF_BUMPLIGHT ) && - ( !( texinfo[pSurf->texinfo].flags & SURF_NOLIGHT ) ) ) - { - hasBumpmap = true; - } - return hasBumpmap; -} - -//----------------------------------------------------------------------------- -// Computes the lightmap color at a particular point -//----------------------------------------------------------------------------- - -static void ComputeLightmapColorPointSample( dface_t* pFace, directlight_t* pSkylight, Vector2D const& luv, float scale, Vector pColor[MAX_LIGHTSTYLES] ) -{ - // face unaffected by light - if (pFace->lightofs == -1 ) - return; - - int smax = ( pFace->m_LightmapTextureSizeInLuxels[0] ) + 1; - int tmax = ( pFace->m_LightmapTextureSizeInLuxels[1] ) + 1; - - // luv is in the space of the accumulated lightmap page; we need to convert - // it to be in the space of the surface - int ds = clamp( (int)luv.x, 0, smax-1 ); - int dt = clamp( (int)luv.y, 0, tmax-1 ); - - int offset = smax * tmax; - if ( SurfHasBumpedLightmaps( pFace ) ) - offset *= ( NUM_BUMP_VECTS + 1 ); - - ColorRGBExp32* pLightmap = (ColorRGBExp32*)&pdlightdata->Base()[pFace->lightofs]; - pLightmap += dt * smax + ds; - for (int maps = 0 ; maps < MAXLIGHTMAPS && pFace->styles[maps] != 255 ; ++maps) - { - int style = pFace->styles[maps]; - - Vector color; - color[0] = TexLightToLinear( pLightmap->r, pLightmap->exponent ); - color[1] = TexLightToLinear( pLightmap->g, pLightmap->exponent ); - color[2] = TexLightToLinear( pLightmap->b, pLightmap->exponent ); - - ComputeAmbientFromSurface( pFace, pSkylight, color ); - pColor[style] += color * scale; - - pLightmap += offset; - } -} - - -//----------------------------------------------------------------------------- -// Tests a particular node -//----------------------------------------------------------------------------- - -class CLightSurface : public IBSPNodeEnumerator -{ -public: - CLightSurface(int iThread) : m_pSurface(0), m_HitFrac(1.0f), m_bHasLuxel(false), m_iThread(iThread) {} - - // call back with a node and a context - bool EnumerateNode( int node, Ray_t const& ray, float f, int context ) - { - dface_t* pSkySurface = 0; - - // Compute the actual point - Vector pt; - VectorMA( ray.m_Start, f, ray.m_Delta, pt ); - - dnode_t* pNode = &dnodes[node]; - dface_t* pFace = &g_pFaces[pNode->firstface]; - for (int i=0 ; i < pNode->numfaces ; ++i, ++pFace) - { - // Don't take into account faces that are int a leaf - if ( !pFace->onNode ) - continue; - - // Don't test displacement faces - if ( pFace->dispinfo != -1 ) - continue; - - texinfo_t* pTex = &texinfo[pFace->texinfo]; - - // Don't immediately return when we hit sky; - // we may actually hit another surface - if (pTex->flags & SURF_SKY) - { - if (TestPointAgainstSkySurface( pt, pFace )) - { - pSkySurface = pFace; - } - - continue; - } - - if (TestPointAgainstSurface( pt, pFace, pTex )) - { - m_HitFrac = f; - m_pSurface = pFace; - m_bHasLuxel = true; - return false; - } - } - - // if we hit a sky surface, return it - m_pSurface = pSkySurface; - return (m_pSurface == 0); - } - - // call back with a leaf and a context - virtual bool EnumerateLeaf( int leaf, Ray_t const& ray, float start, float end, int context ) - { - bool hit = false; - dleaf_t* pLeaf = &dleafs[leaf]; - for (int i=0 ; i < pLeaf->numleaffaces ; ++i) - { - Assert( pLeaf->firstleafface + i < numleaffaces ); - Assert( dleaffaces[pLeaf->firstleafface + i] < numfaces ); - dface_t* pFace = &g_pFaces[dleaffaces[pLeaf->firstleafface + i]]; - - // Don't test displacement faces; we need to check another list - if ( pFace->dispinfo != -1 ) - continue; - - // Don't take into account faces that are on a node - if ( pFace->onNode ) - continue; - - // Find intersection point against detail brushes - texinfo_t* pTex = &texinfo[pFace->texinfo]; - - dplane_t* pPlane = &dplanes[pFace->planenum]; - - // Backface cull... - if (DotProduct( pPlane->normal, ray.m_Delta ) > 0) - continue; - - float startDotN = DotProduct( ray.m_Start, pPlane->normal ); - float deltaDotN = DotProduct( ray.m_Delta, pPlane->normal ); - - float front = startDotN + start * deltaDotN - pPlane->dist; - float back = startDotN + end * deltaDotN - pPlane->dist; - - int side = front < 0; - - // Blow it off if it doesn't split the plane... - if ( (back < 0) == side ) - continue; - - // Don't test a surface that is farther away from the closest found intersection - float f = front / (front-back); - float mid = start * (1.0f - f) + end * f; - if (mid >= m_HitFrac) - continue; - - Vector pt; - VectorMA( ray.m_Start, mid, ray.m_Delta, pt ); - - if (TestPointAgainstSurface( pt, pFace, pTex )) - { - m_HitFrac = mid; - m_pSurface = pFace; - hit = true; - m_bHasLuxel = true; - } - } - - // Now try to clip against all displacements in the leaf - float dist; - Vector2D luxelCoord; - dface_t *pDispFace; - StaticDispMgr()->ClipRayToDispInLeaf( s_DispTested[m_iThread], ray, leaf, dist, pDispFace, luxelCoord ); - if (dist < m_HitFrac) - { - m_HitFrac = dist; - m_pSurface = pDispFace; - Vector2DCopy( luxelCoord, m_LuxelCoord ); - hit = true; - m_bHasLuxel = true; - } - return !hit; - } - - bool FindIntersection( Ray_t const& ray ) - { - StaticDispMgr()->StartRayTest( s_DispTested[m_iThread] ); - return !EnumerateNodesAlongRay( ray, this, 0 ); - } - -private: - bool TestPointAgainstSurface( Vector const& pt, dface_t* pFace, texinfo_t* pTex ) - { - // no lightmaps on this surface? punt... - // FIXME: should be water surface? - if (pTex->flags & SURF_NOLIGHT) - return false; - - // See where in lightmap space our intersection point is - float s, t; - s = DotProduct (pt.Base(), pTex->lightmapVecsLuxelsPerWorldUnits[0]) + - pTex->lightmapVecsLuxelsPerWorldUnits[0][3]; - t = DotProduct (pt.Base(), pTex->lightmapVecsLuxelsPerWorldUnits[1]) + - pTex->lightmapVecsLuxelsPerWorldUnits[1][3]; - - // Not in the bounds of our lightmap? punt... - if( s < pFace->m_LightmapTextureMinsInLuxels[0] || t < pFace->m_LightmapTextureMinsInLuxels[1] ) - return false; - - // assuming a square lightmap (FIXME: which ain't always the case), - // lets see if it lies in that rectangle. If not, punt... - float ds = s - pFace->m_LightmapTextureMinsInLuxels[0]; - float dt = t - pFace->m_LightmapTextureMinsInLuxels[1]; - if( ds > pFace->m_LightmapTextureSizeInLuxels[0] || dt > pFace->m_LightmapTextureSizeInLuxels[1] ) - return false; - - m_LuxelCoord.x = ds; - m_LuxelCoord.y = dt; - - return true; - } - - bool TestPointAgainstSkySurface( Vector const &pt, dface_t *pFace ) - { - // Create sky face winding. - Vector v( 0.0f, 0.0f, 0.0f ); - winding_t *pWinding = WindingFromFace( pFace, v ); - - // Test point in winding. (Since it is at the node, it is in the plane.) - bool bRet = PointInWinding( pt, pWinding ); - - FreeWinding( pWinding ); - - return bRet; - } - - -public: - int m_iThread; - dface_t* m_pSurface; - float m_HitFrac; - Vector2D m_LuxelCoord; - bool m_bHasLuxel; -}; - -bool CastRayInLeaf( int iThread, const Vector &start, const Vector &end, int leafIndex, float *pFraction, Vector *pNormal ) -{ - pFraction[0] = 1.0f; - - Ray_t ray; - ray.Init( start, end, vec3_origin, vec3_origin ); - CBaseTrace trace; - if ( TraceLeafBrushes( leafIndex, start, end, trace ) != 1.0f ) - { - pFraction[0] = trace.fraction; - *pNormal = trace.plane.normal; - } - else - { - Assert(!trace.startsolid && !trace.allsolid); - } - StaticDispMgr()->StartRayTest( s_DispTested[iThread] ); - // Now try to clip against all displacements in the leaf - float dist; - Vector normal; - StaticDispMgr()->ClipRayToDispInLeaf( s_DispTested[iThread], ray, leafIndex, dist, &normal ); - if ( dist < pFraction[0] ) - { - pFraction[0] = dist; - *pNormal = normal; - } - return pFraction[0] != 1.0f ? true : false; -} - -//----------------------------------------------------------------------------- -// Computes ambient lighting along a specified ray. -// Ray represents a cone, tanTheta is the tan of the inner cone angle -//----------------------------------------------------------------------------- -void CalcRayAmbientLighting( int iThread, const Vector &vStart, const Vector &vEnd, float tanTheta, Vector color[MAX_LIGHTSTYLES] ) -{ - Ray_t ray; - ray.Init( vStart, vEnd, vec3_origin, vec3_origin ); - - directlight_t *pSkyLight = FindAmbientSkyLight(); - - CLightSurface surfEnum(iThread); - if (!surfEnum.FindIntersection( ray )) - return; - - // compute the approximate radius of a circle centered around the intersection point - float dist = ray.m_Delta.Length() * tanTheta * surfEnum.m_HitFrac; - - // until 20" we use the point sample, then blend in the average until we're covering 40" - // This is attempting to model the ray as a cone - in the ideal case we'd simply sample all - // luxels in the intersection of the cone with the surface. Since we don't have surface - // neighbor information computed we'll just approximate that sampling with a blend between - // a point sample and the face average. - // This yields results that are similar in that aliasing is reduced at distance while - // point samples provide accuracy for intersections with near geometry - float scaleAvg = RemapValClamped( dist, 20, 40, 0.0f, 1.0f ); - - if ( !surfEnum.m_bHasLuxel ) - { - // don't have luxel UV, so just use average sample - scaleAvg = 1.0; - } - float scaleSample = 1.0f - scaleAvg; - - if (scaleAvg != 0) - { - ComputeLightmapColorFromAverage( surfEnum.m_pSurface, pSkyLight, scaleAvg, color ); - } - if (scaleSample != 0) - { - ComputeLightmapColorPointSample( surfEnum.m_pSurface, pSkyLight, surfEnum.m_LuxelCoord, scaleSample, color ); - } -} - -//----------------------------------------------------------------------------- -// Compute ambient lighting component at specified position. -//----------------------------------------------------------------------------- -static void ComputeAmbientLightingAtPoint( int iThread, const Vector &origin, Vector radcolor[NUMVERTEXNORMALS], Vector color[MAX_LIGHTSTYLES] ) -{ - // NOTE: I'm not dealing with shadow-casting static props here - // This is for speed, although we can add it if it turns out to - // be important - - // sample world by casting N rays distributed across a sphere - Vector upend; - - int j; - for ( j = 0; j < MAX_LIGHTSTYLES; ++j) - { - color[j].Init( 0,0,0 ); - } - - float tanTheta = tan(VERTEXNORMAL_CONE_INNER_ANGLE); - for (int i = 0; i < NUMVERTEXNORMALS; i++) - { - VectorMA( origin, COORD_EXTENT * 1.74, g_anorms[i], upend ); - - // Now that we've got a ray, see what surface we've hit - CalcRayAmbientLighting( iThread, origin, upend, tanTheta, color ); - -// DumpRayToGlView( ray, surfEnum.m_HitFrac, &color[0], "test.out" ); - } - - for ( j = 0; j < MAX_LIGHTSTYLES; ++j) - { - VectorMultiply( color[j], 255.0f / (float)NUMVERTEXNORMALS, color[j] ); - } -} - -//----------------------------------------------------------------------------- -// Trace hemispherical rays from a vertex, accumulating indirect -// sources at each ray termination. -//----------------------------------------------------------------------------- -void ComputeIndirectLightingAtPoint( Vector &position, Vector &normal, Vector &outColor, - int iThread, bool force_fast, bool bIgnoreNormals ) -{ - Ray_t ray; - CLightSurface surfEnum(iThread); - - outColor.Init(); - - - int nSamples = NUMVERTEXNORMALS; - if ( do_fast || force_fast ) - nSamples /= 4; - else - nSamples *= g_flSkySampleScale; - - float totalDot = 0; - DirectionalSampler_t sampler; - for (int j = 0; j < nSamples; j++) - { - Vector samplingNormal = sampler.NextValue(); - float dot; - - if ( bIgnoreNormals ) - dot = (0.7071/2); - else - dot = DotProduct( normal, samplingNormal ); - - if ( dot <= EQUAL_EPSILON ) - { - // reject angles behind our plane - continue; - } - - totalDot += dot; - - // trace to determine surface - Vector vEnd; - VectorScale( samplingNormal, MAX_TRACE_LENGTH, vEnd ); - VectorAdd( position, vEnd, vEnd ); - - ray.Init( position, vEnd, vec3_origin, vec3_origin ); - if ( !surfEnum.FindIntersection( ray ) ) - continue; - - // get color from surface lightmap - texinfo_t* pTex = &texinfo[surfEnum.m_pSurface->texinfo]; - if ( !pTex || pTex->flags & SURF_SKY ) - { - // ignore contribution from sky - // sky ambient already accounted for during direct pass - continue; - } - - if ( surfEnum.m_pSurface->styles[0] == 255 || surfEnum.m_pSurface->lightofs < 0 ) - { - // no light affects this face - continue; - } - - - Vector lightmapColor; - if ( !surfEnum.m_bHasLuxel ) - { - ColorRGBExp32* pAvgLightmapColor = dface_AvgLightColor( surfEnum.m_pSurface, 0 ); - ColorRGBExp32ToVector( *pAvgLightmapColor, lightmapColor ); - } - else - { - // get color from displacement - int smax = ( surfEnum.m_pSurface->m_LightmapTextureSizeInLuxels[0] ) + 1; - int tmax = ( surfEnum.m_pSurface->m_LightmapTextureSizeInLuxels[1] ) + 1; - - // luxelcoord is in the space of the accumulated lightmap page; we need to convert - // it to be in the space of the surface - int ds = clamp( (int)surfEnum.m_LuxelCoord.x, 0, smax-1 ); - int dt = clamp( (int)surfEnum.m_LuxelCoord.y, 0, tmax-1 ); - - ColorRGBExp32* pLightmap = (ColorRGBExp32*)&(*pdlightdata)[surfEnum.m_pSurface->lightofs]; - pLightmap += dt * smax + ds; - ColorRGBExp32ToVector( *pLightmap, lightmapColor ); - } - - VectorMultiply( lightmapColor, dtexdata[pTex->texdata].reflectivity, lightmapColor ); - VectorAdd( outColor, lightmapColor, outColor ); - } - - if ( totalDot ) - { - VectorScale( outColor, 1.0f/totalDot, outColor ); - } -} - -static void ComputeAmbientLighting( int iThread, DetailObjectLump_t& prop, Vector color[MAX_LIGHTSTYLES] ) -{ - Vector origin, normal; - ComputeWorldCenter( prop, origin, normal ); - - if ( !origin.IsValid() || !normal.IsValid() ) - { - static bool s_Warned = false; - if ( !s_Warned ) - { - Warning("WARNING: Bogus detail props encountered!\n" ); - s_Warned = true; - } - - // fill with debug color - for ( int i = 0; i < MAX_LIGHTSTYLES; ++i) - { - color[i].Init(1,0,0); - } - return; - } - - Vector radcolor[NUMVERTEXNORMALS]; - ComputeAmbientLightingAtPoint( iThread, origin, radcolor, color ); -} - - -//----------------------------------------------------------------------------- -// Computes lighting for a single detal prop -//----------------------------------------------------------------------------- - -static void ComputeLighting( DetailObjectLump_t& prop, int iThread ) -{ - // We're going to take the maximum of the ambient lighting and - // the strongest directional light. This works because we're assuming - // the props will have built-in faked lighting. - - Vector directColor[MAX_LIGHTSTYLES]; - Vector ambColor[MAX_LIGHTSTYLES]; - - // Get the max influence of all direct lights - ComputeMaxDirectLighting( prop, directColor, iThread ); - - // Get the ambient lighting + lightstyles - ComputeAmbientLighting( iThread, prop, ambColor ); - - // Base lighting - Vector totalColor; - VectorAdd( directColor[0], ambColor[0], totalColor ); - VectorToColorRGBExp32( totalColor, prop.m_Lighting ); - - bool hasLightstyles = false; - prop.m_LightStyleCount = 0; - - // lightstyles - for (int i = 1; i < MAX_LIGHTSTYLES; ++i ) - { - VectorAdd( directColor[i], ambColor[i], totalColor ); - totalColor *= 0.5f; - - if ((totalColor[0] != 0.0f) || (totalColor[1] != 0.0f) || - (totalColor[2] != 0.0f) ) - { - if (!hasLightstyles) - { - prop.m_LightStyles = s_pDetailPropLightStyleLump->Size(); - hasLightstyles = true; - } - - int j = s_pDetailPropLightStyleLump->AddToTail(); - VectorToColorRGBExp32( totalColor, (*s_pDetailPropLightStyleLump)[j].m_Lighting ); - (*s_pDetailPropLightStyleLump)[j].m_Style = i; - ++prop.m_LightStyleCount; - } - } -} - - -//----------------------------------------------------------------------------- -// Unserialization -//----------------------------------------------------------------------------- -static void UnserializeModelDict( CUtlBuffer& buf ) -{ - // Get origin offset for each model... - int count = buf.GetInt(); - while ( --count >= 0 ) - { - DetailObjectDictLump_t lump; - buf.Get( &lump, sizeof(DetailObjectDictLump_t) ); - - int i = g_ModelCenterOffset.AddToTail(); - - CUtlBuffer mdlbuf; - if (LoadStudioModel( lump.m_Name, mdlbuf )) - { - studiohdr_t* pHdr = (studiohdr_t*)mdlbuf.Base(); - VectorAdd( pHdr->hull_min, pHdr->hull_max, g_ModelCenterOffset[i] ); - g_ModelCenterOffset[i] *= 0.5f; - } - else - { - g_ModelCenterOffset[i].Init(0,0,0); - } - } -} - -static void UnserializeSpriteDict( CUtlBuffer& buf ) -{ - // Get origin offset for each model... - int count = buf.GetInt(); - while ( --count >= 0 ) - { - DetailSpriteDictLump_t lump; - buf.Get( &lump, sizeof(DetailSpriteDictLump_t) ); - - // For these sprites, x goes out the front, y right, z up - int i = g_SpriteCenterOffset.AddToTail(); - g_SpriteCenterOffset[i].x = 0.0f; - g_SpriteCenterOffset[i].y = lump.m_LR.x + lump.m_UL.x; - g_SpriteCenterOffset[i].z = lump.m_LR.y + lump.m_UL.y; - g_SpriteCenterOffset[i] *= 0.5f; - } -} - - -//----------------------------------------------------------------------------- -// Unserializes the detail props -//----------------------------------------------------------------------------- -static int UnserializeDetailProps( DetailObjectLump_t*& pProps ) -{ - GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROPS ); - - if (g_GameLumps.GetGameLumpVersion(handle) != GAMELUMP_DETAIL_PROPS_VERSION) - return 0; - - // Unserialize - CUtlBuffer buf( g_GameLumps.GetGameLump(handle), g_GameLumps.GameLumpSize( handle ), CUtlBuffer::READ_ONLY ); - - UnserializeModelDict( buf ); - UnserializeSpriteDict( buf ); - - // Now we're pointing to the detail prop data - // This actually works because the scope of the game lump data - // is global and the buf was just pointing to it. - int count = buf.GetInt(); - if (count) - { - pProps = (DetailObjectLump_t*)buf.PeekGet(); - } - else - { - pProps = 0; - } - return count; -} - - -//----------------------------------------------------------------------------- -// Writes the detail lighting lump -//----------------------------------------------------------------------------- -static void WriteDetailLightingLump( int lumpID, int lumpVersion, CUtlVector &lumpData ) -{ - GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(lumpID); - if (handle != g_GameLumps.InvalidGameLump()) - g_GameLumps.DestroyGameLump(handle); - int lightsize = lumpData.Size() * sizeof(DetailPropLightstylesLump_t); - int lumpsize = lightsize + sizeof(int); - - handle = g_GameLumps.CreateGameLump( lumpID, lumpsize, 0, lumpVersion ); - - // Serialize the data - CUtlBuffer buf( g_GameLumps.GetGameLump(handle), lumpsize ); - buf.PutInt( lumpData.Size() ); - if (lightsize) - buf.Put( lumpData.Base(), lightsize ); -} - -static void WriteDetailLightingLumps( void ) -{ - WriteDetailLightingLump( GAMELUMP_DETAIL_PROP_LIGHTING, GAMELUMP_DETAIL_PROP_LIGHTING_VERSION, s_DetailPropLightStyleLumpLDR ); - WriteDetailLightingLump( GAMELUMP_DETAIL_PROP_LIGHTING_HDR, GAMELUMP_DETAIL_PROP_LIGHTING_HDR_VERSION, s_DetailPropLightStyleLumpHDR ); -} - -// need to do this so that if we are building HDR data, the LDR data is intact, and vice versa.s -void UnserializeDetailPropLighting( int lumpID, int lumpVersion, CUtlVector &lumpData ) -{ - GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( lumpID ); - - if( handle == g_GameLumps.InvalidGameLump() ) - { - return; - } - - if (g_GameLumps.GetGameLumpVersion(handle) != lumpVersion) - return; - - // Unserialize - CUtlBuffer buf( g_GameLumps.GetGameLump(handle), g_GameLumps.GameLumpSize( handle ), CUtlBuffer::READ_ONLY ); - - int count = buf.GetInt(); - if( !count ) - { - return; - } - lumpData.SetCount( count ); - int lightsize = lumpData.Size() * sizeof(DetailPropLightstylesLump_t); - buf.Get( lumpData.Base(), lightsize ); -} - -DetailObjectLump_t *g_pMPIDetailProps = NULL; - -void VMPI_ProcessDetailPropWU( int iThread, int iWorkUnit, MessageBuffer *pBuf ) -{ - CUtlVector *pDetailPropLump = s_pDetailPropLightStyleLump; - - DetailObjectLump_t& prop = g_pMPIDetailProps[iWorkUnit]; - ComputeLighting( prop, iThread ); - - // Send the results back... - pBuf->write( &prop.m_Lighting, sizeof( prop.m_Lighting ) ); - pBuf->write( &prop.m_LightStyleCount, sizeof( prop.m_LightStyleCount ) ); - pBuf->write( &prop.m_LightStyles, sizeof( prop.m_LightStyles ) ); - - for ( int i=0; i < prop.m_LightStyleCount; i++ ) - { - DetailPropLightstylesLump_t *l = &pDetailPropLump->Element( i + prop.m_LightStyles ); - pBuf->write( &l->m_Lighting, sizeof( l->m_Lighting ) ); - pBuf->write( &l->m_Style, sizeof( l->m_Style ) ); - } -} - - -void VMPI_ReceiveDetailPropWU( int iWorkUnit, MessageBuffer *pBuf, int iWorker ) -{ - CUtlVector *pDetailPropLump = s_pDetailPropLightStyleLump; - - DetailObjectLump_t& prop = g_pMPIDetailProps[iWorkUnit]; - - pBuf->read( &prop.m_Lighting, sizeof( prop.m_Lighting ) ); - pBuf->read( &prop.m_LightStyleCount, sizeof( prop.m_LightStyleCount ) ); - pBuf->read( &prop.m_LightStyles, sizeof( prop.m_LightStyles ) ); - - pDetailPropLump->EnsureCount( prop.m_LightStyles + prop.m_LightStyleCount ); - - for ( int i=0; i < prop.m_LightStyleCount; i++ ) - { - DetailPropLightstylesLump_t *l = &pDetailPropLump->Element( i + prop.m_LightStyles ); - pBuf->read( &l->m_Lighting, sizeof( l->m_Lighting ) ); - pBuf->read( &l->m_Style, sizeof( l->m_Style ) ); - } -} - -//----------------------------------------------------------------------------- -// Computes lighting for the detail props -//----------------------------------------------------------------------------- -void ComputeDetailPropLighting( int iThread ) -{ - // illuminate them all - DetailObjectLump_t* pProps; - int count = UnserializeDetailProps( pProps ); - if (!count) - return; - - // unserialize the lump that we aren't computing. - if( g_bHDR ) - { - UnserializeDetailPropLighting( GAMELUMP_DETAIL_PROP_LIGHTING, GAMELUMP_DETAIL_PROP_LIGHTING_VERSION, s_DetailPropLightStyleLumpLDR ); - } - else - { - UnserializeDetailPropLighting( GAMELUMP_DETAIL_PROP_LIGHTING_HDR, GAMELUMP_DETAIL_PROP_LIGHTING_HDR_VERSION, s_DetailPropLightStyleLumpHDR ); - } - - StartPacifier("Computing detail prop lighting : "); - - for (int i = 0; i < count; ++i) - { - UpdatePacifier( (float)i / (float)count ); - ComputeLighting( pProps[i], iThread ); - } - - // Write detail prop lightstyle lump... - WriteDetailLightingLumps(); - EndPacifier( true ); -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Revision: $ +// $NoKeywords: $ +// +// This file contains code to allow us to associate client data with bsp leaves. +// +//=============================================================================// + +#include "vrad.h" +#include "Bsplib.h" +#include "GameBSPFile.h" +#include "UtlBuffer.h" +#include "utlvector.h" +#include "CModel.h" +#include "studio.h" +#include "pacifier.h" +#include "vraddetailprops.h" +#include "mathlib/halton.h" +#include "messbuf.h" +#include "byteswap.h" + +bool LoadStudioModel( char const* pModelName, CUtlBuffer& buf ); + + +//----------------------------------------------------------------------------- +// Purpose: Writes a glview text file containing the collision surface in question +// Input : *pCollide - +// *pFilename - +//----------------------------------------------------------------------------- +void DumpRayToGlView( Ray_t const& ray, float dist, Vector* pColor, const char *pFilename ) +{ + Vector dir = ray.m_Delta; + float len = VectorNormalize(dir); + if (len < 1e-3) + return; + + Vector up( 0, 0, 1 ); + Vector crossDir; + if (fabs(DotProduct(up, dir)) - 1.0f < -1e-3 ) + { + CrossProduct( dir, up, crossDir ); + VectorNormalize(crossDir); + } + else + { + up.Init( 0, 1, 0 ); + CrossProduct( dir, up, crossDir ); + VectorNormalize(crossDir); + } + + Vector end; + Vector start1, start2; + VectorMA( ray.m_Start, dist, ray.m_Delta, end ); + VectorMA( ray.m_Start, -2, crossDir, start1 ); + VectorMA( ray.m_Start, 2, crossDir, start2 ); + + FileHandle_t fp = g_pFileSystem->Open( pFilename, "a" ); + int vert = 0; + CmdLib_FPrintf( fp, "3\n" ); + CmdLib_FPrintf( fp, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", start1.x, start1.y, start1.z, + pColor->x, pColor->y, pColor->z ); + vert++; + CmdLib_FPrintf( fp, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", start2.x, start2.y, start2.z, + pColor->x, pColor->y, pColor->z ); + vert++; + CmdLib_FPrintf( fp, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", end.x, end.y, end.z, + pColor->x, pColor->y, pColor->z ); + vert++; + g_pFileSystem->Close( fp ); +} + + +//----------------------------------------------------------------------------- +// This puppy is used to construct the game lumps +//----------------------------------------------------------------------------- +static CUtlVector s_DetailPropLightStyleLumpLDR; +static CUtlVector s_DetailPropLightStyleLumpHDR; +static CUtlVector *s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpLDR; + +//----------------------------------------------------------------------------- +// An amount to add to each model to get to the model center +//----------------------------------------------------------------------------- +CUtlVector g_ModelCenterOffset; +CUtlVector g_SpriteCenterOffset; + +void VRadDetailProps_SetHDRMode( bool bHDR ) +{ + if( bHDR ) + { + s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpHDR; + } + else + { + s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpLDR; + } +} + +//----------------------------------------------------------------------------- +// Finds ambient sky lights +//----------------------------------------------------------------------------- +static directlight_t* FindAmbientSkyLight() +{ + static directlight_t *s_pCachedSkylight = NULL; + + // Don't keep searching for the same light. + if ( !s_pCachedSkylight ) + { + // find any ambient lights + directlight_t* dl; + for (dl = activelights; dl != 0; dl = dl->next) + { + if (dl->light.type == emit_skyambient) + { + s_pCachedSkylight = dl; + break; + } + } + } + + return s_pCachedSkylight; +} + + +//----------------------------------------------------------------------------- +// Compute world center of a prop +//----------------------------------------------------------------------------- +static void ComputeWorldCenter( DetailObjectLump_t& prop, Vector& center, Vector& normal ) +{ + // Transform the offset into world space + Vector forward, right; + AngleVectors( prop.m_Angles, &forward, &right, &normal ); + VectorCopy( prop.m_Origin, center ); + + // FIXME: Take orientation into account? + switch (prop.m_Type ) + { + case DETAIL_PROP_TYPE_MODEL: + VectorMA( center, g_ModelCenterOffset[prop.m_DetailModel].x, forward, center ); + VectorMA( center, -g_ModelCenterOffset[prop.m_DetailModel].y, right, center ); + VectorMA( center, g_ModelCenterOffset[prop.m_DetailModel].z, normal, center ); + break; + + case DETAIL_PROP_TYPE_SPRITE: + Vector vecOffset; + VectorMultiply( g_SpriteCenterOffset[prop.m_DetailModel], prop.m_flScale, vecOffset ); + VectorMA( center, vecOffset.x, forward, center ); + VectorMA( center, -vecOffset.y, right, center ); + VectorMA( center, vecOffset.z, normal, center ); + break; + } +} + + +//----------------------------------------------------------------------------- +// Computes max direct lighting for a single detal prop +//----------------------------------------------------------------------------- +static void ComputeMaxDirectLighting( DetailObjectLump_t& prop, Vector* maxcolor, int iThread ) +{ + // The max direct lighting must be along the direction to one + // of the static lights.... + + Vector origin, normal; + ComputeWorldCenter( prop, origin, normal ); + + if ( !origin.IsValid() || !normal.IsValid() ) + { + static bool s_Warned = false; + if ( !s_Warned ) + { + Warning("WARNING: Bogus detail props encountered!\n" ); + s_Warned = true; + } + + // fill with debug color + for ( int i = 0; i < MAX_LIGHTSTYLES; ++i) + { + maxcolor[i].Init(1,0,0); + } + return; + } + + int cluster = ClusterFromPoint(origin); + + Vector delta; + CUtlVector< directlight_t* > lights; + CUtlVector< Vector > directions; + + directlight_t* dl; + for (dl = activelights; dl != 0; dl = dl->next) + { + // skyambient doesn't affect dlights.. + if (dl->light.type == emit_skyambient) + continue; + + // is this lights cluster visible? + if ( PVSCheck( dl->pvs, cluster ) ) + { + lights.AddToTail(dl); + VectorSubtract( dl->light.origin, origin, delta ); + VectorNormalize( delta ); + directions.AddToTail( delta ); + } + } + + // Find the max illumination + int i; + for ( i = 0; i < MAX_LIGHTSTYLES; ++i) + { + maxcolor[i].Init(0,0,0); + } + + // NOTE: See version 10 for a method where we choose a normal based on whichever + // one produces the maximum possible illumination. This appeared to work better on + // e3_town, so I'm trying it now; hopefully it'll be good for all cases. + int j; + for ( j = 0; j < lights.Count(); ++j) + { + dl = lights[j]; + + SSE_sampleLightOutput_t out; + FourVectors origin4; + FourVectors normal4; + origin4.DuplicateVector( origin ); + normal4.DuplicateVector( normal ); + + GatherSampleLightSSE ( out, dl, -1, origin4, &normal4, 1, iThread ); + VectorMA( maxcolor[dl->light.style], out.m_flFalloff.m128_f32[0] * out.m_flDot[0].m128_f32[0], dl->light.intensity, maxcolor[dl->light.style] ); + } +} + + +//----------------------------------------------------------------------------- +// Computes the ambient term from a particular surface +//----------------------------------------------------------------------------- + +static void ComputeAmbientFromSurface( dface_t* pFace, directlight_t* pSkylight, + Vector& radcolor ) +{ + texinfo_t* pTex = &texinfo[pFace->texinfo]; + if (pTex) + { + // If we hit the sky, use the sky ambient + if (pTex->flags & SURF_SKY) + { + if (pSkylight) + { + // add in sky ambient + VectorDivide( pSkylight->light.intensity, 255.0f, radcolor ); + } + } + else + { + VectorMultiply( radcolor, dtexdata[pTex->texdata].reflectivity, radcolor ); + } + } +} + + +//----------------------------------------------------------------------------- +// Computes the lightmap color at a particular point +//----------------------------------------------------------------------------- + +static void ComputeLightmapColorFromAverage( dface_t* pFace, directlight_t* pSkylight, float scale, Vector pColor[MAX_LIGHTSTYLES] ) +{ + texinfo_t* pTex = &texinfo[pFace->texinfo]; + if (pTex->flags & SURF_SKY) + { + if (pSkylight) + { + // add in sky ambient + Vector amb = pSkylight->light.intensity / 255.0f; + pColor[0] += amb * scale; + } + return; + } + + for (int maps = 0 ; maps < MAXLIGHTMAPS && pFace->styles[maps] != 255 ; ++maps) + { + ColorRGBExp32* pAvgColor = dface_AvgLightColor( pFace, maps ); + + // this code expects values from [0..1] not [0..255] + Vector color; + color[0] = TexLightToLinear( pAvgColor->r, pAvgColor->exponent ); + color[1] = TexLightToLinear( pAvgColor->g, pAvgColor->exponent ); + color[2] = TexLightToLinear( pAvgColor->b, pAvgColor->exponent ); + + ComputeAmbientFromSurface( pFace, pSkylight, color ); + + int style = pFace->styles[maps]; + pColor[style] += color * scale; + } +} + + +//----------------------------------------------------------------------------- +// Returns true if the surface has bumped lightmaps +//----------------------------------------------------------------------------- + +static bool SurfHasBumpedLightmaps( dface_t *pSurf ) +{ + bool hasBumpmap = false; + if( ( texinfo[pSurf->texinfo].flags & SURF_BUMPLIGHT ) && + ( !( texinfo[pSurf->texinfo].flags & SURF_NOLIGHT ) ) ) + { + hasBumpmap = true; + } + return hasBumpmap; +} + +//----------------------------------------------------------------------------- +// Computes the lightmap color at a particular point +//----------------------------------------------------------------------------- + +static void ComputeLightmapColorPointSample( dface_t* pFace, directlight_t* pSkylight, Vector2D const& luv, float scale, Vector pColor[MAX_LIGHTSTYLES] ) +{ + // face unaffected by light + if (pFace->lightofs == -1 ) + return; + + int smax = ( pFace->m_LightmapTextureSizeInLuxels[0] ) + 1; + int tmax = ( pFace->m_LightmapTextureSizeInLuxels[1] ) + 1; + + // luv is in the space of the accumulated lightmap page; we need to convert + // it to be in the space of the surface + int ds = clamp( (int)luv.x, 0, smax-1 ); + int dt = clamp( (int)luv.y, 0, tmax-1 ); + + int offset = smax * tmax; + if ( SurfHasBumpedLightmaps( pFace ) ) + offset *= ( NUM_BUMP_VECTS + 1 ); + + ColorRGBExp32* pLightmap = (ColorRGBExp32*)&pdlightdata->Base()[pFace->lightofs]; + pLightmap += dt * smax + ds; + for (int maps = 0 ; maps < MAXLIGHTMAPS && pFace->styles[maps] != 255 ; ++maps) + { + int style = pFace->styles[maps]; + + Vector color; + color[0] = TexLightToLinear( pLightmap->r, pLightmap->exponent ); + color[1] = TexLightToLinear( pLightmap->g, pLightmap->exponent ); + color[2] = TexLightToLinear( pLightmap->b, pLightmap->exponent ); + + ComputeAmbientFromSurface( pFace, pSkylight, color ); + pColor[style] += color * scale; + + pLightmap += offset; + } +} + + +//----------------------------------------------------------------------------- +// Tests a particular node +//----------------------------------------------------------------------------- + +class CLightSurface : public IBSPNodeEnumerator +{ +public: + CLightSurface(int iThread) : m_pSurface(0), m_HitFrac(1.0f), m_bHasLuxel(false), m_iThread(iThread) {} + + // call back with a node and a context + bool EnumerateNode( int node, Ray_t const& ray, float f, int context ) + { + dface_t* pSkySurface = 0; + + // Compute the actual point + Vector pt; + VectorMA( ray.m_Start, f, ray.m_Delta, pt ); + + dnode_t* pNode = &dnodes[node]; + dface_t* pFace = &g_pFaces[pNode->firstface]; + for (int i=0 ; i < pNode->numfaces ; ++i, ++pFace) + { + // Don't take into account faces that are int a leaf + if ( !pFace->onNode ) + continue; + + // Don't test displacement faces + if ( pFace->dispinfo != -1 ) + continue; + + texinfo_t* pTex = &texinfo[pFace->texinfo]; + + // Don't immediately return when we hit sky; + // we may actually hit another surface + if (pTex->flags & SURF_SKY) + { + if (TestPointAgainstSkySurface( pt, pFace )) + { + pSkySurface = pFace; + } + + continue; + } + + if (TestPointAgainstSurface( pt, pFace, pTex )) + { + m_HitFrac = f; + m_pSurface = pFace; + m_bHasLuxel = true; + return false; + } + } + + // if we hit a sky surface, return it + m_pSurface = pSkySurface; + return (m_pSurface == 0); + } + + // call back with a leaf and a context + virtual bool EnumerateLeaf( int leaf, Ray_t const& ray, float start, float end, int context ) + { + bool hit = false; + dleaf_t* pLeaf = &dleafs[leaf]; + for (int i=0 ; i < pLeaf->numleaffaces ; ++i) + { + Assert( pLeaf->firstleafface + i < numleaffaces ); + Assert( dleaffaces[pLeaf->firstleafface + i] < numfaces ); + dface_t* pFace = &g_pFaces[dleaffaces[pLeaf->firstleafface + i]]; + + // Don't test displacement faces; we need to check another list + if ( pFace->dispinfo != -1 ) + continue; + + // Don't take into account faces that are on a node + if ( pFace->onNode ) + continue; + + // Find intersection point against detail brushes + texinfo_t* pTex = &texinfo[pFace->texinfo]; + + dplane_t* pPlane = &dplanes[pFace->planenum]; + + // Backface cull... + if (DotProduct( pPlane->normal, ray.m_Delta ) > 0) + continue; + + float startDotN = DotProduct( ray.m_Start, pPlane->normal ); + float deltaDotN = DotProduct( ray.m_Delta, pPlane->normal ); + + float front = startDotN + start * deltaDotN - pPlane->dist; + float back = startDotN + end * deltaDotN - pPlane->dist; + + int side = front < 0; + + // Blow it off if it doesn't split the plane... + if ( (back < 0) == side ) + continue; + + // Don't test a surface that is farther away from the closest found intersection + float f = front / (front-back); + float mid = start * (1.0f - f) + end * f; + if (mid >= m_HitFrac) + continue; + + Vector pt; + VectorMA( ray.m_Start, mid, ray.m_Delta, pt ); + + if (TestPointAgainstSurface( pt, pFace, pTex )) + { + m_HitFrac = mid; + m_pSurface = pFace; + hit = true; + m_bHasLuxel = true; + } + } + + // Now try to clip against all displacements in the leaf + float dist; + Vector2D luxelCoord; + dface_t *pDispFace; + StaticDispMgr()->ClipRayToDispInLeaf( s_DispTested[m_iThread], ray, leaf, dist, pDispFace, luxelCoord ); + if (dist < m_HitFrac) + { + m_HitFrac = dist; + m_pSurface = pDispFace; + Vector2DCopy( luxelCoord, m_LuxelCoord ); + hit = true; + m_bHasLuxel = true; + } + return !hit; + } + + bool FindIntersection( Ray_t const& ray ) + { + StaticDispMgr()->StartRayTest( s_DispTested[m_iThread] ); + return !EnumerateNodesAlongRay( ray, this, 0 ); + } + +private: + bool TestPointAgainstSurface( Vector const& pt, dface_t* pFace, texinfo_t* pTex ) + { + // no lightmaps on this surface? punt... + // FIXME: should be water surface? + if (pTex->flags & SURF_NOLIGHT) + return false; + + // See where in lightmap space our intersection point is + float s, t; + s = DotProduct (pt.Base(), pTex->lightmapVecsLuxelsPerWorldUnits[0]) + + pTex->lightmapVecsLuxelsPerWorldUnits[0][3]; + t = DotProduct (pt.Base(), pTex->lightmapVecsLuxelsPerWorldUnits[1]) + + pTex->lightmapVecsLuxelsPerWorldUnits[1][3]; + + // Not in the bounds of our lightmap? punt... + if( s < pFace->m_LightmapTextureMinsInLuxels[0] || t < pFace->m_LightmapTextureMinsInLuxels[1] ) + return false; + + // assuming a square lightmap (FIXME: which ain't always the case), + // lets see if it lies in that rectangle. If not, punt... + float ds = s - pFace->m_LightmapTextureMinsInLuxels[0]; + float dt = t - pFace->m_LightmapTextureMinsInLuxels[1]; + if( ds > pFace->m_LightmapTextureSizeInLuxels[0] || dt > pFace->m_LightmapTextureSizeInLuxels[1] ) + return false; + + m_LuxelCoord.x = ds; + m_LuxelCoord.y = dt; + + return true; + } + + bool TestPointAgainstSkySurface( Vector const &pt, dface_t *pFace ) + { + // Create sky face winding. + Vector v( 0.0f, 0.0f, 0.0f ); + winding_t *pWinding = WindingFromFace( pFace, v ); + + // Test point in winding. (Since it is at the node, it is in the plane.) + bool bRet = PointInWinding( pt, pWinding ); + + FreeWinding( pWinding ); + + return bRet; + } + + +public: + int m_iThread; + dface_t* m_pSurface; + float m_HitFrac; + Vector2D m_LuxelCoord; + bool m_bHasLuxel; +}; + +bool CastRayInLeaf( int iThread, const Vector &start, const Vector &end, int leafIndex, float *pFraction, Vector *pNormal ) +{ + pFraction[0] = 1.0f; + + Ray_t ray; + ray.Init( start, end, vec3_origin, vec3_origin ); + CBaseTrace trace; + if ( TraceLeafBrushes( leafIndex, start, end, trace ) != 1.0f ) + { + pFraction[0] = trace.fraction; + *pNormal = trace.plane.normal; + } + else + { + Assert(!trace.startsolid && !trace.allsolid); + } + StaticDispMgr()->StartRayTest( s_DispTested[iThread] ); + // Now try to clip against all displacements in the leaf + float dist; + Vector normal; + StaticDispMgr()->ClipRayToDispInLeaf( s_DispTested[iThread], ray, leafIndex, dist, &normal ); + if ( dist < pFraction[0] ) + { + pFraction[0] = dist; + *pNormal = normal; + } + return pFraction[0] != 1.0f ? true : false; +} + +//----------------------------------------------------------------------------- +// Computes ambient lighting along a specified ray. +// Ray represents a cone, tanTheta is the tan of the inner cone angle +//----------------------------------------------------------------------------- +void CalcRayAmbientLighting( int iThread, const Vector &vStart, const Vector &vEnd, float tanTheta, Vector color[MAX_LIGHTSTYLES] ) +{ + Ray_t ray; + ray.Init( vStart, vEnd, vec3_origin, vec3_origin ); + + directlight_t *pSkyLight = FindAmbientSkyLight(); + + CLightSurface surfEnum(iThread); + if (!surfEnum.FindIntersection( ray )) + return; + + // compute the approximate radius of a circle centered around the intersection point + float dist = ray.m_Delta.Length() * tanTheta * surfEnum.m_HitFrac; + + // until 20" we use the point sample, then blend in the average until we're covering 40" + // This is attempting to model the ray as a cone - in the ideal case we'd simply sample all + // luxels in the intersection of the cone with the surface. Since we don't have surface + // neighbor information computed we'll just approximate that sampling with a blend between + // a point sample and the face average. + // This yields results that are similar in that aliasing is reduced at distance while + // point samples provide accuracy for intersections with near geometry + float scaleAvg = RemapValClamped( dist, 20, 40, 0.0f, 1.0f ); + + if ( !surfEnum.m_bHasLuxel ) + { + // don't have luxel UV, so just use average sample + scaleAvg = 1.0; + } + float scaleSample = 1.0f - scaleAvg; + + if (scaleAvg != 0) + { + ComputeLightmapColorFromAverage( surfEnum.m_pSurface, pSkyLight, scaleAvg, color ); + } + if (scaleSample != 0) + { + ComputeLightmapColorPointSample( surfEnum.m_pSurface, pSkyLight, surfEnum.m_LuxelCoord, scaleSample, color ); + } +} + +//----------------------------------------------------------------------------- +// Compute ambient lighting component at specified position. +//----------------------------------------------------------------------------- +static void ComputeAmbientLightingAtPoint( int iThread, const Vector &origin, Vector radcolor[NUMVERTEXNORMALS], Vector color[MAX_LIGHTSTYLES] ) +{ + // NOTE: I'm not dealing with shadow-casting static props here + // This is for speed, although we can add it if it turns out to + // be important + + // sample world by casting N rays distributed across a sphere + Vector upend; + + int j; + for ( j = 0; j < MAX_LIGHTSTYLES; ++j) + { + color[j].Init( 0,0,0 ); + } + + float tanTheta = tan(VERTEXNORMAL_CONE_INNER_ANGLE); + for (int i = 0; i < NUMVERTEXNORMALS; i++) + { + VectorMA( origin, COORD_EXTENT * 1.74, g_anorms[i], upend ); + + // Now that we've got a ray, see what surface we've hit + CalcRayAmbientLighting( iThread, origin, upend, tanTheta, color ); + +// DumpRayToGlView( ray, surfEnum.m_HitFrac, &color[0], "test.out" ); + } + + for ( j = 0; j < MAX_LIGHTSTYLES; ++j) + { + VectorMultiply( color[j], 255.0f / (float)NUMVERTEXNORMALS, color[j] ); + } +} + +//----------------------------------------------------------------------------- +// Trace hemispherical rays from a vertex, accumulating indirect +// sources at each ray termination. +//----------------------------------------------------------------------------- +void ComputeIndirectLightingAtPoint( Vector &position, Vector &normal, Vector &outColor, + int iThread, bool force_fast, bool bIgnoreNormals ) +{ + Ray_t ray; + CLightSurface surfEnum(iThread); + + outColor.Init(); + + + int nSamples = NUMVERTEXNORMALS; + if ( do_fast || force_fast ) + nSamples /= 4; + else + nSamples *= g_flSkySampleScale; + + float totalDot = 0; + DirectionalSampler_t sampler; + for (int j = 0; j < nSamples; j++) + { + Vector samplingNormal = sampler.NextValue(); + float dot; + + if ( bIgnoreNormals ) + dot = (0.7071/2); + else + dot = DotProduct( normal, samplingNormal ); + + if ( dot <= EQUAL_EPSILON ) + { + // reject angles behind our plane + continue; + } + + totalDot += dot; + + // trace to determine surface + Vector vEnd; + VectorScale( samplingNormal, MAX_TRACE_LENGTH, vEnd ); + VectorAdd( position, vEnd, vEnd ); + + ray.Init( position, vEnd, vec3_origin, vec3_origin ); + if ( !surfEnum.FindIntersection( ray ) ) + continue; + + // get color from surface lightmap + texinfo_t* pTex = &texinfo[surfEnum.m_pSurface->texinfo]; + if ( !pTex || pTex->flags & SURF_SKY ) + { + // ignore contribution from sky + // sky ambient already accounted for during direct pass + continue; + } + + if ( surfEnum.m_pSurface->styles[0] == 255 || surfEnum.m_pSurface->lightofs < 0 ) + { + // no light affects this face + continue; + } + + + Vector lightmapColor; + if ( !surfEnum.m_bHasLuxel ) + { + ColorRGBExp32* pAvgLightmapColor = dface_AvgLightColor( surfEnum.m_pSurface, 0 ); + ColorRGBExp32ToVector( *pAvgLightmapColor, lightmapColor ); + } + else + { + // get color from displacement + int smax = ( surfEnum.m_pSurface->m_LightmapTextureSizeInLuxels[0] ) + 1; + int tmax = ( surfEnum.m_pSurface->m_LightmapTextureSizeInLuxels[1] ) + 1; + + // luxelcoord is in the space of the accumulated lightmap page; we need to convert + // it to be in the space of the surface + int ds = clamp( (int)surfEnum.m_LuxelCoord.x, 0, smax-1 ); + int dt = clamp( (int)surfEnum.m_LuxelCoord.y, 0, tmax-1 ); + + ColorRGBExp32* pLightmap = (ColorRGBExp32*)&(*pdlightdata)[surfEnum.m_pSurface->lightofs]; + pLightmap += dt * smax + ds; + ColorRGBExp32ToVector( *pLightmap, lightmapColor ); + } + + VectorMultiply( lightmapColor, dtexdata[pTex->texdata].reflectivity, lightmapColor ); + VectorAdd( outColor, lightmapColor, outColor ); + } + + if ( totalDot ) + { + VectorScale( outColor, 1.0f/totalDot, outColor ); + } +} + +static void ComputeAmbientLighting( int iThread, DetailObjectLump_t& prop, Vector color[MAX_LIGHTSTYLES] ) +{ + Vector origin, normal; + ComputeWorldCenter( prop, origin, normal ); + + if ( !origin.IsValid() || !normal.IsValid() ) + { + static bool s_Warned = false; + if ( !s_Warned ) + { + Warning("WARNING: Bogus detail props encountered!\n" ); + s_Warned = true; + } + + // fill with debug color + for ( int i = 0; i < MAX_LIGHTSTYLES; ++i) + { + color[i].Init(1,0,0); + } + return; + } + + Vector radcolor[NUMVERTEXNORMALS]; + ComputeAmbientLightingAtPoint( iThread, origin, radcolor, color ); +} + + +//----------------------------------------------------------------------------- +// Computes lighting for a single detal prop +//----------------------------------------------------------------------------- + +static void ComputeLighting( DetailObjectLump_t& prop, int iThread ) +{ + // We're going to take the maximum of the ambient lighting and + // the strongest directional light. This works because we're assuming + // the props will have built-in faked lighting. + + Vector directColor[MAX_LIGHTSTYLES]; + Vector ambColor[MAX_LIGHTSTYLES]; + + // Get the max influence of all direct lights + ComputeMaxDirectLighting( prop, directColor, iThread ); + + // Get the ambient lighting + lightstyles + ComputeAmbientLighting( iThread, prop, ambColor ); + + // Base lighting + Vector totalColor; + VectorAdd( directColor[0], ambColor[0], totalColor ); + VectorToColorRGBExp32( totalColor, prop.m_Lighting ); + + bool hasLightstyles = false; + prop.m_LightStyleCount = 0; + + // lightstyles + for (int i = 1; i < MAX_LIGHTSTYLES; ++i ) + { + VectorAdd( directColor[i], ambColor[i], totalColor ); + totalColor *= 0.5f; + + if ((totalColor[0] != 0.0f) || (totalColor[1] != 0.0f) || + (totalColor[2] != 0.0f) ) + { + if (!hasLightstyles) + { + prop.m_LightStyles = s_pDetailPropLightStyleLump->Size(); + hasLightstyles = true; + } + + int j = s_pDetailPropLightStyleLump->AddToTail(); + VectorToColorRGBExp32( totalColor, (*s_pDetailPropLightStyleLump)[j].m_Lighting ); + (*s_pDetailPropLightStyleLump)[j].m_Style = i; + ++prop.m_LightStyleCount; + } + } +} + + +//----------------------------------------------------------------------------- +// Unserialization +//----------------------------------------------------------------------------- +static void UnserializeModelDict( CUtlBuffer& buf ) +{ + // Get origin offset for each model... + int count = buf.GetInt(); + while ( --count >= 0 ) + { + DetailObjectDictLump_t lump; + buf.Get( &lump, sizeof(DetailObjectDictLump_t) ); + + int i = g_ModelCenterOffset.AddToTail(); + + CUtlBuffer mdlbuf; + if (LoadStudioModel( lump.m_Name, mdlbuf )) + { + studiohdr_t* pHdr = (studiohdr_t*)mdlbuf.Base(); + VectorAdd( pHdr->hull_min, pHdr->hull_max, g_ModelCenterOffset[i] ); + g_ModelCenterOffset[i] *= 0.5f; + } + else + { + g_ModelCenterOffset[i].Init(0,0,0); + } + } +} + +static void UnserializeSpriteDict( CUtlBuffer& buf ) +{ + // Get origin offset for each model... + int count = buf.GetInt(); + while ( --count >= 0 ) + { + DetailSpriteDictLump_t lump; + buf.Get( &lump, sizeof(DetailSpriteDictLump_t) ); + + // For these sprites, x goes out the front, y right, z up + int i = g_SpriteCenterOffset.AddToTail(); + g_SpriteCenterOffset[i].x = 0.0f; + g_SpriteCenterOffset[i].y = lump.m_LR.x + lump.m_UL.x; + g_SpriteCenterOffset[i].z = lump.m_LR.y + lump.m_UL.y; + g_SpriteCenterOffset[i] *= 0.5f; + } +} + + +//----------------------------------------------------------------------------- +// Unserializes the detail props +//----------------------------------------------------------------------------- +static int UnserializeDetailProps( DetailObjectLump_t*& pProps ) +{ + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROPS ); + + if (g_GameLumps.GetGameLumpVersion(handle) != GAMELUMP_DETAIL_PROPS_VERSION) + return 0; + + // Unserialize + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), g_GameLumps.GameLumpSize( handle ), CUtlBuffer::READ_ONLY ); + + UnserializeModelDict( buf ); + UnserializeSpriteDict( buf ); + + // Now we're pointing to the detail prop data + // This actually works because the scope of the game lump data + // is global and the buf was just pointing to it. + int count = buf.GetInt(); + if (count) + { + pProps = (DetailObjectLump_t*)buf.PeekGet(); + } + else + { + pProps = 0; + } + return count; +} + + +//----------------------------------------------------------------------------- +// Writes the detail lighting lump +//----------------------------------------------------------------------------- +static void WriteDetailLightingLump( int lumpID, int lumpVersion, CUtlVector &lumpData ) +{ + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(lumpID); + if (handle != g_GameLumps.InvalidGameLump()) + g_GameLumps.DestroyGameLump(handle); + int lightsize = lumpData.Size() * sizeof(DetailPropLightstylesLump_t); + int lumpsize = lightsize + sizeof(int); + + handle = g_GameLumps.CreateGameLump( lumpID, lumpsize, 0, lumpVersion ); + + // Serialize the data + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), lumpsize ); + buf.PutInt( lumpData.Size() ); + if (lightsize) + buf.Put( lumpData.Base(), lightsize ); +} + +static void WriteDetailLightingLumps( void ) +{ + WriteDetailLightingLump( GAMELUMP_DETAIL_PROP_LIGHTING, GAMELUMP_DETAIL_PROP_LIGHTING_VERSION, s_DetailPropLightStyleLumpLDR ); + WriteDetailLightingLump( GAMELUMP_DETAIL_PROP_LIGHTING_HDR, GAMELUMP_DETAIL_PROP_LIGHTING_HDR_VERSION, s_DetailPropLightStyleLumpHDR ); +} + +// need to do this so that if we are building HDR data, the LDR data is intact, and vice versa.s +void UnserializeDetailPropLighting( int lumpID, int lumpVersion, CUtlVector &lumpData ) +{ + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( lumpID ); + + if( handle == g_GameLumps.InvalidGameLump() ) + { + return; + } + + if (g_GameLumps.GetGameLumpVersion(handle) != lumpVersion) + return; + + // Unserialize + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), g_GameLumps.GameLumpSize( handle ), CUtlBuffer::READ_ONLY ); + + int count = buf.GetInt(); + if( !count ) + { + return; + } + lumpData.SetCount( count ); + int lightsize = lumpData.Size() * sizeof(DetailPropLightstylesLump_t); + buf.Get( lumpData.Base(), lightsize ); +} + +DetailObjectLump_t *g_pMPIDetailProps = NULL; + +void VMPI_ProcessDetailPropWU( int iThread, int iWorkUnit, MessageBuffer *pBuf ) +{ + CUtlVector *pDetailPropLump = s_pDetailPropLightStyleLump; + + DetailObjectLump_t& prop = g_pMPIDetailProps[iWorkUnit]; + ComputeLighting( prop, iThread ); + + // Send the results back... + pBuf->write( &prop.m_Lighting, sizeof( prop.m_Lighting ) ); + pBuf->write( &prop.m_LightStyleCount, sizeof( prop.m_LightStyleCount ) ); + pBuf->write( &prop.m_LightStyles, sizeof( prop.m_LightStyles ) ); + + for ( int i=0; i < prop.m_LightStyleCount; i++ ) + { + DetailPropLightstylesLump_t *l = &pDetailPropLump->Element( i + prop.m_LightStyles ); + pBuf->write( &l->m_Lighting, sizeof( l->m_Lighting ) ); + pBuf->write( &l->m_Style, sizeof( l->m_Style ) ); + } +} + + +void VMPI_ReceiveDetailPropWU( int iWorkUnit, MessageBuffer *pBuf, int iWorker ) +{ + CUtlVector *pDetailPropLump = s_pDetailPropLightStyleLump; + + DetailObjectLump_t& prop = g_pMPIDetailProps[iWorkUnit]; + + pBuf->read( &prop.m_Lighting, sizeof( prop.m_Lighting ) ); + pBuf->read( &prop.m_LightStyleCount, sizeof( prop.m_LightStyleCount ) ); + pBuf->read( &prop.m_LightStyles, sizeof( prop.m_LightStyles ) ); + + pDetailPropLump->EnsureCount( prop.m_LightStyles + prop.m_LightStyleCount ); + + for ( int i=0; i < prop.m_LightStyleCount; i++ ) + { + DetailPropLightstylesLump_t *l = &pDetailPropLump->Element( i + prop.m_LightStyles ); + pBuf->read( &l->m_Lighting, sizeof( l->m_Lighting ) ); + pBuf->read( &l->m_Style, sizeof( l->m_Style ) ); + } +} + +//----------------------------------------------------------------------------- +// Computes lighting for the detail props +//----------------------------------------------------------------------------- +void ComputeDetailPropLighting( int iThread ) +{ + // illuminate them all + DetailObjectLump_t* pProps; + int count = UnserializeDetailProps( pProps ); + if (!count) + return; + + // unserialize the lump that we aren't computing. + if( g_bHDR ) + { + UnserializeDetailPropLighting( GAMELUMP_DETAIL_PROP_LIGHTING, GAMELUMP_DETAIL_PROP_LIGHTING_VERSION, s_DetailPropLightStyleLumpLDR ); + } + else + { + UnserializeDetailPropLighting( GAMELUMP_DETAIL_PROP_LIGHTING_HDR, GAMELUMP_DETAIL_PROP_LIGHTING_HDR_VERSION, s_DetailPropLightStyleLumpHDR ); + } + + StartPacifier("Computing detail prop lighting : "); + + for (int i = 0; i < count; ++i) + { + UpdatePacifier( (float)i / (float)count ); + ComputeLighting( pProps[i], iThread ); + } + + // Write detail prop lightstyle lump... + WriteDetailLightingLumps(); + EndPacifier( true ); +} diff --git a/mp/src/utils/vrad/vraddetailprops.h b/mp/src/utils/vrad/vraddetailprops.h index f9cc8da1..0345d96a 100644 --- a/mp/src/utils/vrad/vraddetailprops.h +++ b/mp/src/utils/vrad/vraddetailprops.h @@ -1,34 +1,34 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef VRADDETAILPROPS_H -#define VRADDETAILPROPS_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "bspfile.h" -#include "mathlib/anorms.h" - - -// Calculate the lighting at whatever surface the ray hits. -// Note: this ADDS to the values already in color. So if you want absolute -// values in there, then clear the values in color[] first. -void CalcRayAmbientLighting( - int iThread, - const Vector &vStart, - const Vector &vEnd, - float tanTheta, // tangent of the inner angle of the cone - Vector color[MAX_LIGHTSTYLES] // The color contribution from each lightstyle. - ); - -bool CastRayInLeaf( int iThread, const Vector &start, const Vector &end, int leafIndex, float *pFraction, Vector *pNormal ); - -void ComputeDetailPropLighting( int iThread ); - - -#endif // VRADDETAILPROPS_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef VRADDETAILPROPS_H +#define VRADDETAILPROPS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "bspfile.h" +#include "mathlib/anorms.h" + + +// Calculate the lighting at whatever surface the ray hits. +// Note: this ADDS to the values already in color. So if you want absolute +// values in there, then clear the values in color[] first. +void CalcRayAmbientLighting( + int iThread, + const Vector &vStart, + const Vector &vEnd, + float tanTheta, // tangent of the inner angle of the cone + Vector color[MAX_LIGHTSTYLES] // The color contribution from each lightstyle. + ); + +bool CastRayInLeaf( int iThread, const Vector &start, const Vector &end, int leafIndex, float *pFraction, Vector *pNormal ); + +void ComputeDetailPropLighting( int iThread ); + + +#endif // VRADDETAILPROPS_H diff --git a/mp/src/utils/vrad/vraddisps.cpp b/mp/src/utils/vrad/vraddisps.cpp index 0bbafd40..d6bc6f7a 100644 --- a/mp/src/utils/vrad/vraddisps.cpp +++ b/mp/src/utils/vrad/vraddisps.cpp @@ -1,1759 +1,1759 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include "vrad.h" -#include "utlvector.h" -#include "cmodel.h" -#include "BSPTreeData.h" -#include "VRAD_DispColl.h" -#include "CollisionUtils.h" -#include "lightmap.h" -#include "Radial.h" -#include "CollisionUtils.h" -#include "mathlib/bumpvects.h" -#include "utlrbtree.h" -#include "tier0/fasttimer.h" -#include "disp_vrad.h" - -class CBSPDispRayDistanceEnumerator; - -//============================================================================= -// -// Displacement/Face List -// -class CBSPDispFaceListEnumerator : public ISpatialLeafEnumerator, public IBSPTreeDataEnumerator -{ -public: - - //========================================================================= - // - // Construction/Deconstruction - // - CBSPDispFaceListEnumerator() {}; - virtual ~CBSPDispFaceListEnumerator() - { - m_DispList.Purge(); - m_FaceList.Purge(); - } - - // ISpatialLeafEnumerator - bool EnumerateLeaf( int ndxLeaf, int context ); - - // IBSPTreeDataEnumerator - bool FASTCALL EnumerateElement( int userId, int context ); - -public: - - CUtlVector m_DispList; - CUtlVector m_FaceList; -}; - - -//============================================================================= -// -// RayEnumerator -// -class CBSPDispRayEnumerator : public ISpatialLeafEnumerator, public IBSPTreeDataEnumerator -{ -public: - // ISpatialLeafEnumerator - bool EnumerateLeaf( int ndxLeaf, int context ); - - // IBSPTreeDataEnumerator - bool FASTCALL EnumerateElement( int userId, int context ); -}; - -//============================================================================= -// -// VRad Displacement Manager -// -class CVRadDispMgr : public IVRadDispMgr -{ -public: - - //========================================================================= - // - // Construction/Deconstruction - // - CVRadDispMgr(); - virtual ~CVRadDispMgr(); - - // creation/destruction - void Init( void ); - void Shutdown( void ); - - // "CalcPoints" - bool BuildDispSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ); - bool BuildDispLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ); - bool BuildDispSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ); - - // patching functions - void MakePatches( void ); - void SubdividePatch( int iPatch ); - - // pre "FinalLightFace" - void InsertSamplesDataIntoHashTable( void ); - void InsertPatchSampleDataIntoHashTable( void ); - - // "FinalLightFace" - radial_t *BuildLuxelRadial( int ndxFace, int ndxStyle, bool bBump ); - bool SampleRadial( int ndxFace, radial_t *pRadial, Vector const &vPos, int ndxLxl, LightingValue_t *pLightSample, int sampleCount, bool bPatch ); - radial_t *BuildPatchRadial( int ndxFace, bool bBump ); - - // utility - void GetDispSurfNormal( int ndxFace, Vector &pt, Vector &ptNormal, bool bInside ); - void GetDispSurf( int ndxFace, CVRADDispColl **ppDispTree ); - - // bsp tree functions - bool ClipRayToDisp( DispTested_t &dispTested, Ray_t const &ray ); - bool ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, int ndxLeaf ); - void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, int ndxLeaf, - float& dist, dface_t*& pFace, Vector2D& luxelCoord ); - void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, - int ndxLeaf, float& dist, Vector *pNormal ); - - void StartRayTest( DispTested_t &dispTested ); - void AddPolysForRayTrace( void ); - - // general timing -- should be moved!! - void StartTimer( const char *name ); - void EndTimer( void ); - - //========================================================================= - // - // Enumeration Methods - // - bool DispRay_EnumerateLeaf( int ndxLeaf, int context ); - bool DispRay_EnumerateElement( int userId, int context ); - bool DispRayDistance_EnumerateElement( int userId, CBSPDispRayDistanceEnumerator* pEnum ); - - bool DispFaceList_EnumerateLeaf( int ndxLeaf, int context ); - bool DispFaceList_EnumerateElement( int userId, int context ); - -private: - - //========================================================================= - // - // BSP Tree Helpers - // - void InsertDispIntoTree( int ndxDisp ); - void RemoveDispFromTree( int ndxDisp ); - - //========================================================================= - // - // Displacement Data Loader (from .bsp) - // - void UnserializeDisps( void ); - void DispBuilderInit( CCoreDispInfo *pBuilderDisp, dface_t *pFace, int ndxFace ); - - //========================================================================= - // - // Sampling Helpers - // - void RadialLuxelBuild( CVRADDispColl *pDispTree, radial_t *pRadial, int ndxStyle, bool bBump ); - void RadialLuxelAddSamples( int ndxFace, Vector const &luxelPt, Vector const &luxelNormal, float radius, - radial_t *pRadial, int ndxRadial, bool bBump, int lightStyle ); - - void RadialPatchBuild( CVRADDispColl *pDispTree, radial_t *pRadial, bool bBump ); - void RadialLuxelAddPatch( int ndxFace, Vector const &luxelPt, - Vector const &luxelNormal, float radius, - radial_t *pRadial, int ndxRadial, bool bBump, - CUtlVector &interestingPatches ); - - bool IsNeighbor( int iDispFace, int iNeighborFace ); - - void GetInterestingPatchesForLuxels( - int ndxFace, - CUtlVector &interestingPatches, - float patchSampleRadius ); - -private: - - struct DispCollTree_t - { - CVRADDispColl *m_pDispTree; - BSPTreeDataHandle_t m_Handle; - }; - - struct EnumContext_t - { - DispTested_t *m_pDispTested; - Ray_t const *m_pRay; - }; - - CUtlVector m_DispTrees; - - IBSPTreeData *m_pBSPTreeData; - - CBSPDispRayEnumerator m_EnumDispRay; - CBSPDispFaceListEnumerator m_EnumDispFaceList; - - int sampleCount; - Vector *m_pSamplePos; - - CFastTimer m_Timer; -}; - -//----------------------------------------------------------------------------- -// Purpose: expose IVRadDispMgr to vrad -//----------------------------------------------------------------------------- - -static CVRadDispMgr s_DispMgr; - -IVRadDispMgr *StaticDispMgr( void ) -{ - return &s_DispMgr; -} - - -//============================================================================= -// -// Displacement/Face List -// -// ISpatialLeafEnumerator -bool CBSPDispFaceListEnumerator::EnumerateLeaf( int ndxLeaf, int context ) -{ - return s_DispMgr.DispFaceList_EnumerateLeaf( ndxLeaf, context ); -} - -// IBSPTreeDataEnumerator -bool FASTCALL CBSPDispFaceListEnumerator::EnumerateElement( int userId, int context ) -{ - return s_DispMgr.DispFaceList_EnumerateElement( userId, context ); -} - - -//============================================================================= -// -// RayEnumerator -// -bool CBSPDispRayEnumerator::EnumerateLeaf( int ndxLeaf, int context ) -{ - return s_DispMgr.DispRay_EnumerateLeaf( ndxLeaf, context ); -} - -bool FASTCALL CBSPDispRayEnumerator::EnumerateElement( int userId, int context ) -{ - return s_DispMgr.DispRay_EnumerateElement( userId, context ); -} - - -//----------------------------------------------------------------------------- -// Here's an enumerator that we use for testing against disps in a leaf... -//----------------------------------------------------------------------------- - -class CBSPDispRayDistanceEnumerator : public IBSPTreeDataEnumerator -{ -public: - CBSPDispRayDistanceEnumerator() : m_Distance(1.0f), m_pSurface(0) {} - - // IBSPTreeDataEnumerator - bool FASTCALL EnumerateElement( int userId, int context ) - { - return s_DispMgr.DispRayDistance_EnumerateElement( userId, this ); - } - - float m_Distance; - dface_t* m_pSurface; - DispTested_t *m_pDispTested; - Ray_t const *m_pRay; - Vector2D m_LuxelCoord; - Vector m_Normal; -}; - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -CVRadDispMgr::CVRadDispMgr() -{ - m_pBSPTreeData = CreateBSPTreeData(); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -CVRadDispMgr::~CVRadDispMgr() -{ - DestroyBSPTreeData( m_pBSPTreeData ); -} - - -//----------------------------------------------------------------------------- -// Insert a displacement into the tree for collision -//----------------------------------------------------------------------------- -void CVRadDispMgr::InsertDispIntoTree( int ndxDisp ) -{ - DispCollTree_t &dispTree = m_DispTrees[ndxDisp]; - CDispCollTree *pDispTree = dispTree.m_pDispTree; - - // get the bounding box of the tree - Vector boxMin, boxMax; - pDispTree->GetBounds( boxMin, boxMax ); - - // add the displacement to the tree so we will collide against it - dispTree.m_Handle = m_pBSPTreeData->Insert( ndxDisp, boxMin, boxMax ); -} - - -//----------------------------------------------------------------------------- -// Remove a displacement from the tree for collision -//----------------------------------------------------------------------------- -void CVRadDispMgr::RemoveDispFromTree( int ndxDisp ) -{ - // release the tree handle - if( m_DispTrees[ndxDisp].m_Handle != TREEDATA_INVALID_HANDLE ) - { - m_pBSPTreeData->Remove( m_DispTrees[ndxDisp].m_Handle ); - m_DispTrees[ndxDisp].m_Handle = TREEDATA_INVALID_HANDLE; - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::Init( void ) -{ - // initialize the bsp tree - m_pBSPTreeData->Init( ToolBSPTree() ); - - // read in displacements that have been compiled into the bsp file - UnserializeDisps(); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::Shutdown( void ) -{ - // remove all displacements from the tree - for( int ndxDisp = m_DispTrees.Size(); ndxDisp >= 0; ndxDisp-- ) - { - RemoveDispFromTree( ndxDisp ); - } - - // shutdown the bsp tree - m_pBSPTreeData->Shutdown(); - - // purge the displacement collision tree list - m_DispTrees.Purge(); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::DispBuilderInit( CCoreDispInfo *pBuilderDisp, dface_t *pFace, int ndxFace ) -{ - // get the .bsp displacement - ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; - if( !pDisp ) - return; - - // - // initlialize the displacement base surface - // - CCoreDispSurface *pSurf = pBuilderDisp->GetSurface(); - pSurf->SetPointCount( 4 ); - pSurf->SetHandle( ndxFace ); - pSurf->SetContents( pDisp->contents ); - - Vector pt[4]; - int ndxPt; - for( ndxPt = 0; ndxPt < 4; ndxPt++ ) - { - int eIndex = dsurfedges[pFace->firstedge+ndxPt]; - if( eIndex < 0 ) - { - pSurf->SetPoint( ndxPt, dvertexes[dedges[-eIndex].v[1]].point ); - } - else - { - pSurf->SetPoint( ndxPt, dvertexes[dedges[eIndex].v[0]].point ); - } - - VectorCopy( pSurf->GetPoint(ndxPt), pt[ndxPt] ); - } - - // - // calculate the displacement surface normal - // - Vector vFaceNormal; - pSurf->GetNormal( vFaceNormal ); - for( ndxPt = 0; ndxPt < 4; ndxPt++ ) - { - pSurf->SetPointNormal( ndxPt, vFaceNormal ); - } - - // set the surface initial point info - pSurf->SetPointStart( pDisp->startPosition ); - pSurf->FindSurfPointStartIndex(); - pSurf->AdjustSurfPointData(); - - Vector vecTmp( texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][0], - texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][1], - texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][2] ); - int nLuxelsPerWorldUnit = static_cast( 1.0f / VectorLength( vecTmp ) ); - Vector vecU( texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][0], - texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][1], - texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][2] ); - Vector vecV( texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[1][0], - texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[1][1], - texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[1][2] ); - pSurf->CalcLuxelCoords( nLuxelsPerWorldUnit, false, vecU, vecV ); - - pBuilderDisp->SetNeighborData( pDisp->m_EdgeNeighbors, pDisp->m_CornerNeighbors ); - - CDispVert *pVerts = &g_DispVerts[ pDisp->m_iDispVertStart ]; - CDispTri *pTris = &g_DispTris[pDisp->m_iDispTriStart]; - - // - // initialize the displacement data - // - pBuilderDisp->InitDispInfo( - pDisp->power, - pDisp->minTess, - pDisp->smoothingAngle, - pVerts, - pTris ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::UnserializeDisps( void ) -{ - // temporarily create the "builder" displacements - CUtlVector builderDisps; - for ( int iDisp = 0; iDisp < g_dispinfo.Count(); ++iDisp ) - { - CCoreDispInfo *pDisp = new CCoreDispInfo; - if ( !pDisp ) - { - builderDisps.Purge(); - return; - } - - int nIndex = builderDisps.AddToTail(); - pDisp->SetListIndex( nIndex ); - builderDisps[nIndex] = pDisp; - } - - // Set them up as CDispUtilsHelpers. - for ( int iDisp = 0; iDisp < g_dispinfo.Count(); ++iDisp ) - { - builderDisps[iDisp]->SetDispUtilsHelperInfo( builderDisps.Base(), g_dispinfo.Count() ); - } - - // - // find all faces with displacement data and initialize - // - for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) - { - dface_t *pFace = &g_pFaces[ndxFace]; - if( ValidDispFace( pFace ) ) - { - DispBuilderInit( builderDisps[pFace->dispinfo], pFace, ndxFace ); - } - } - - // generate the displacement surfaces - for( int iDisp = 0; iDisp < g_dispinfo.Count(); ++iDisp ) - { - builderDisps[iDisp]->Create(); - } - - // smooth edge normals - SmoothNeighboringDispSurfNormals( builderDisps.Base(), g_dispinfo.Count() ); - - // - // create the displacement collision tree and add it to the bsp tree - // - CVRADDispColl *pDispTrees = new CVRADDispColl[g_dispinfo.Count()]; - if( !pDispTrees ) - return; - - m_DispTrees.AddMultipleToTail( g_dispinfo.Count() ); - - for( int iDisp = 0; iDisp < g_dispinfo.Count(); iDisp++ ) - { - pDispTrees[iDisp].Create( builderDisps[iDisp] ); - - m_DispTrees[iDisp].m_pDispTree = &pDispTrees[iDisp]; - m_DispTrees[iDisp].m_Handle = TREEDATA_INVALID_HANDLE; - - InsertDispIntoTree( iDisp ); - } - - // free "builder" disps - builderDisps.Purge(); -} - -//----------------------------------------------------------------------------- -// Purpose: create a set of patches for each displacement surface to transfer -// bounced light around with -//----------------------------------------------------------------------------- -void CVRadDispMgr::MakePatches( void ) -{ - // Collect stats - keep track of the total displacement surface area. - float flTotalArea = 0.0f; - - // Create patches for all of the displacements. - int nTreeCount = m_DispTrees.Size(); - for( int iTree = 0; iTree < nTreeCount; ++iTree ) - { - // Get the current displacement collision tree. - CVRADDispColl *pDispTree = m_DispTrees[iTree].m_pDispTree; - if( !pDispTree ) - continue; - - flTotalArea += pDispTree->CreateParentPatches(); - } - - // Print stats. - qprintf( "%i Displacements\n", nTreeCount ); - qprintf( "%i Square Feet [%.2f Square Inches]\n", ( int )( flTotalArea / 144.0f ), flTotalArea ); -} -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::SubdividePatch( int iPatch ) -{ - // Get the current patch to subdivide. - CPatch *pPatch = &g_Patches[iPatch]; - if ( !pPatch ) - return; - - // Create children patches. - DispCollTree_t &dispTree = m_DispTrees[g_pFaces[pPatch->faceNumber].dispinfo]; - CVRADDispColl *pTree = dispTree.m_pDispTree; - if( pTree ) - { - pTree->CreateChildPatches( iPatch, 0 ); - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::StartRayTest( DispTested_t &dispTested ) -{ - if( m_DispTrees.Size() > 0 ) - { - if( dispTested.m_pTested == 0 ) - { - dispTested.m_pTested = new int[m_DispTrees.Size()]; - memset( dispTested.m_pTested, 0, m_DispTrees.Size() * sizeof( int ) ); - dispTested.m_Enum = 0; - } - ++dispTested.m_Enum; - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CVRadDispMgr::ClipRayToDisp( DispTested_t &dispTested, Ray_t const &ray ) -{ - StartRayTest( dispTested ); - - EnumContext_t ctx; - ctx.m_pRay = &ray; - ctx.m_pDispTested = &dispTested; - - // If it got through without a hit, it returns true - return !m_pBSPTreeData->EnumerateLeavesAlongRay( ray, &m_EnumDispRay, ( int )&ctx ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CVRadDispMgr::ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, - int ndxLeaf ) -{ - EnumContext_t ctx; - ctx.m_pRay = &ray; - ctx.m_pDispTested = &dispTested; - - return !m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &m_EnumDispRay, ( int )&ctx ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, - int ndxLeaf, float& dist, dface_t*& pFace, Vector2D& luxelCoord ) -{ - CBSPDispRayDistanceEnumerator rayTestEnum; - rayTestEnum.m_pRay = &ray; - rayTestEnum.m_pDispTested = &dispTested; - - m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &rayTestEnum, 0 ); - - dist = rayTestEnum.m_Distance; - pFace = rayTestEnum.m_pSurface; - if (pFace) - { - Vector2DCopy( rayTestEnum.m_LuxelCoord, luxelCoord ); - } -} - -void CVRadDispMgr::ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, - int ndxLeaf, float& dist, Vector *pNormal ) -{ - CBSPDispRayDistanceEnumerator rayTestEnum; - rayTestEnum.m_pRay = &ray; - rayTestEnum.m_pDispTested = &dispTested; - - m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &rayTestEnum, 0 ); - dist = rayTestEnum.m_Distance; - if ( rayTestEnum.m_pSurface ) - { - *pNormal = rayTestEnum.m_Normal; - } -} - -void CVRadDispMgr::AddPolysForRayTrace( void ) -{ - int nTreeCount = m_DispTrees.Size(); - for( int iTree = 0; iTree < nTreeCount; ++iTree ) - { - // Get the current displacement collision tree. - CVRADDispColl *pDispTree = m_DispTrees[iTree].m_pDispTree; - - // Add the triangles of the tree to the RT environment - pDispTree->AddPolysForRayTrace(); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::GetDispSurfNormal( int ndxFace, Vector &pt, Vector &ptNormal, - bool bInside ) -{ - // get the displacement surface data - DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; - CVRADDispColl *pDispTree = dispTree.m_pDispTree; - - // find the parameterized displacement indices - Vector2D uv; - pDispTree->BaseFacePlaneToDispUV( pt, uv ); - - if( bInside ) - { - if( uv[0] < 0.0f || uv[0] > 1.0f ) { Msg( "Disp UV (%f) outside bounds!\n", uv[0] ); } - if( uv[1] < 0.0f || uv[1] > 1.0f ) { Msg( "Disp UV (%f) outside bounds!\n", uv[1] ); } - } - - if( uv[0] < 0.0f ) { uv[0] = 0.0f; } - if( uv[0] > 1.0f ) { uv[0] = 1.0f; } - if( uv[1] < 0.0f ) { uv[1] = 0.0f; } - if( uv[1] > 1.0f ) { uv[1] = 1.0f; } - - // get the normal at "pt" - pDispTree->DispUVToSurfNormal( uv, ptNormal ); - - // get the new "pt" - pDispTree->DispUVToSurfPoint( uv, pt, 1.0f ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::GetDispSurf( int ndxFace, CVRADDispColl **ppDispTree ) -{ - DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; - *ppDispTree = dispTree.m_pDispTree; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CVRadDispMgr::DispRay_EnumerateLeaf( int ndxLeaf, int context ) -{ - return m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &m_EnumDispRay, context ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CVRadDispMgr::DispRay_EnumerateElement( int userId, int context ) -{ - DispCollTree_t &dispTree = m_DispTrees[userId]; - EnumContext_t *pCtx = ( EnumContext_t* )context; - - // don't test twice (check tested value) - if( pCtx->m_pDispTested->m_pTested[userId] == pCtx->m_pDispTested->m_Enum ) - return true; - - // set the tested value - pCtx->m_pDispTested->m_pTested[userId] = pCtx->m_pDispTested->m_Enum; - - // false mean stop iterating -- return false if we hit! (NOTE: opposite return - // result of the collision tree's ray test, thus the !) - CBaseTrace trace; - trace.fraction = 1.0f; - return ( !dispTree.m_pDispTree->AABBTree_Ray( *pCtx->m_pRay, pCtx->m_pRay->InvDelta(), &trace, true ) ); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -bool CVRadDispMgr::DispRayDistance_EnumerateElement( int userId, CBSPDispRayDistanceEnumerator* pCtx ) -{ - DispCollTree_t &dispTree = m_DispTrees[userId]; - - // don't test twice (check tested value) - if( pCtx->m_pDispTested->m_pTested[userId] == pCtx->m_pDispTested->m_Enum ) - return true; - - // set the tested value - pCtx->m_pDispTested->m_pTested[userId] = pCtx->m_pDispTested->m_Enum; - - // Test the ray, if it's closer than previous tests, use it! - RayDispOutput_t output; - output.ndxVerts[0] = -1; - output.ndxVerts[1] = -1; - output.ndxVerts[2] = -1; - output.ndxVerts[3] = -1; - output.u = -1.0f; - output.v = -1.0f; - output.dist = FLT_MAX; - - if (dispTree.m_pDispTree->AABBTree_Ray( *pCtx->m_pRay, output )) - { - if (output.dist < pCtx->m_Distance) - { - pCtx->m_Distance = output.dist; - pCtx->m_pSurface = &g_pFaces[dispTree.m_pDispTree->GetParentIndex()]; - - // Get the luxel coordinate - ComputePointFromBarycentric( - dispTree.m_pDispTree->GetLuxelCoord(output.ndxVerts[0]), - dispTree.m_pDispTree->GetLuxelCoord(output.ndxVerts[1]), - dispTree.m_pDispTree->GetLuxelCoord(output.ndxVerts[2]), - output.u, output.v, pCtx->m_LuxelCoord ); - - Vector v0,v1,v2; - dispTree.m_pDispTree->GetVert( output.ndxVerts[0], v0 ); - dispTree.m_pDispTree->GetVert( output.ndxVerts[1], v1 ); - dispTree.m_pDispTree->GetVert( output.ndxVerts[2], v2 ); - Vector e0 = v1-v0; - Vector e1 = v2-v0; - pCtx->m_Normal = CrossProduct( e0, e1 ); - VectorNormalize(pCtx->m_Normal); - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// Test a ray against a particular dispinfo -//----------------------------------------------------------------------------- - -/* -float CVRadDispMgr::ClipRayToDisp( Ray_t const &ray, int dispinfo ) -{ - assert( m_DispTrees.IsValidIndex(dispinfo) ); - - RayDispOutput_t output; - if (!m_DispTrees[dispinfo].m_pDispTree->AABBTree_Ray( ray, output )) - return 1.0f; - return output.dist; -} -*/ - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CVRadDispMgr::DispFaceList_EnumerateLeaf( int ndxLeaf, int context ) -{ - // - // add the faces found in this leaf to the face list - // - dleaf_t *pLeaf = &dleafs[ndxLeaf]; - for( int ndxFace = 0; ndxFace < pLeaf->numleaffaces; ndxFace++ ) - { - // get the current face index - int ndxLeafFace = pLeaf->firstleafface + ndxFace; - - // check to see if the face already lives in the list - int ndx; - int size = m_EnumDispFaceList.m_FaceList.Size(); - for( ndx = 0; ndx < size; ndx++ ) - { - if( m_EnumDispFaceList.m_FaceList[ndx] == ndxLeafFace ) - break; - } - - if( ndx == size ) - { - int ndxList = m_EnumDispFaceList.m_FaceList.AddToTail(); - m_EnumDispFaceList.m_FaceList[ndxList] = ndxLeafFace; - } - } - - return m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &m_EnumDispFaceList, context ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CVRadDispMgr::DispFaceList_EnumerateElement( int userId, int context ) -{ - DispCollTree_t &dispTree = m_DispTrees[userId]; - CVRADDispColl *pDispTree = dispTree.m_pDispTree; - if( !pDispTree ) - return false; - - // check to see if the displacement already lives in the list - int ndx; - int size = m_EnumDispFaceList.m_DispList.Size(); - for( ndx = 0; ndx < size; ndx++ ) - { - if( m_EnumDispFaceList.m_DispList[ndx] == pDispTree ) - break; - } - - if( ndx == size ) - { - int ndxList = m_EnumDispFaceList.m_DispList.AddToTail(); - m_EnumDispFaceList.m_DispList[ndxList] = pDispTree; - } - - return true; -} - - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -inline void GetSampleLight( facelight_t *pFaceLight, int ndxStyle, bool bBumped, - int ndxSample, LightingValue_t *pSampleLight ) -{ -// SampleLight[0].Init( 20.0f, 10.0f, 10.0f ); -// return; - - // get sample from bumped lighting data - if( bBumped ) - { - for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) - { - pSampleLight[ndxBump] = pFaceLight->light[ndxStyle][ndxBump][ndxSample]; - } - } - // just a generally lit surface - else - { - pSampleLight[0] = pFaceLight->light[ndxStyle][0][ndxSample]; - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void AddSampleLightToRadial( Vector const &samplePos, Vector const &sampleNormal, - LightingValue_t *pSampleLight, float sampleRadius2, - Vector const &luxelPos, Vector const &luxelNormal, - radial_t *pRadial, int ndxRadial, bool bBumped, - bool bNeighborBumped ) -{ - // check normals to see if sample contributes any light at all - float angle = sampleNormal.Dot( luxelNormal ); - if ( angle < 0.15f ) - return; - - // calculate the light vector - Vector vSegment = samplePos - luxelPos; - - // get the distance to the light - float dist = vSegment.Length(); - float dist2 = dist * dist; - - // Check to see if the light is within the influence. - float influence = 1.0f - ( dist2 / ( sampleRadius2 ) ); - if( influence <= 0.0f ) - return; - - influence *= angle; - - if( bBumped ) - { - if( bNeighborBumped ) - { - for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) - { - pRadial->light[ndxBump][ndxRadial].AddWeighted( pSampleLight[ndxBump], influence ); - } - pRadial->weight[ndxRadial] += influence; - } - else - { - influence *= 0.05f; - for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) - { - pRadial->light[ndxBump][ndxRadial].AddWeighted( pSampleLight[0], influence ); - } - pRadial->weight[ndxRadial] += influence; - } - } - else - { - pRadial->light[0][ndxRadial].AddWeighted( pSampleLight[0], influence ); - pRadial->weight[ndxRadial] += influence; - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CVRadDispMgr::IsNeighbor( int iFace, int iNeighborFace ) -{ - if ( iFace == iNeighborFace ) - return true; - - faceneighbor_t *pFaceNeighbor = &faceneighbor[iFace]; - for ( int iNeighbor = 0; iNeighbor < pFaceNeighbor->numneighbors; iNeighbor++ ) - { - if ( pFaceNeighbor->neighbor[iNeighbor] == iNeighborFace ) - return true; - } - - return false; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::RadialLuxelAddSamples( int ndxFace, Vector const &luxelPt, Vector const &luxelNormal, float radius, - radial_t *pRadial, int ndxRadial, bool bBump, int lightStyle ) -{ - // calculate one over the voxel size - float ooVoxelSize = 1.0f / SAMPLEHASH_VOXEL_SIZE; - - // - // find voxel info - // - int voxelMin[3], voxelMax[3]; - for( int axis = 0; axis < 3; axis++ ) - { - voxelMin[axis] = ( int )( ( luxelPt[axis] - radius ) * ooVoxelSize ); - voxelMax[axis] = ( int )( ( luxelPt[axis] + radius ) * ooVoxelSize ) + 1; - } - - SampleData_t sampleData; - for( int ndxZ = voxelMin[2]; ndxZ < voxelMax[2] + 1; ndxZ++ ) - { - for( int ndxY = voxelMin[1]; ndxY < voxelMax[1] + 1; ndxY++ ) - { - for( int ndxX = voxelMin[0]; ndxX < voxelMax[0] + 1; ndxX++ ) - { - sampleData.x = ndxX * 100; - sampleData.y = ndxY * 10; - sampleData.z = ndxZ; - - UtlHashHandle_t handle = g_SampleHashTable.Find( sampleData ); - if( handle != g_SampleHashTable.InvalidHandle() ) - { - SampleData_t *pSampleData = &g_SampleHashTable.Element( handle ); - int count = pSampleData->m_Samples.Count(); - for( int ndx = 0; ndx < count; ndx++ ) - { - SampleHandle_t sampleHandle = pSampleData->m_Samples.Element( ndx ); - int ndxSample = ( sampleHandle & 0x0000ffff ); - int ndxFaceLight = ( ( sampleHandle >> 16 ) & 0x0000ffff ); - - facelight_t *pFaceLight = &facelight[ndxFaceLight]; - if( pFaceLight && IsNeighbor( ndxFace, ndxFaceLight ) ) - { - // - // check for similar lightstyles - // - dface_t *pFace = &g_pFaces[ndxFaceLight]; - if( pFace ) - { - int ndxNeighborStyle = -1; - for( int ndxLightStyle = 0; ndxLightStyle < MAXLIGHTMAPS; ndxLightStyle++ ) - { - if( pFace->styles[ndxLightStyle] == lightStyle ) - { - ndxNeighborStyle = ndxLightStyle; - break; - } - } - if( ndxNeighborStyle == -1 ) - continue; - - // is this surface bumped??? - bool bNeighborBump = texinfo[pFace->texinfo].flags & SURF_BUMPLIGHT ? true : false; - - LightingValue_t sampleLight[NUM_BUMP_VECTS+1]; - GetSampleLight( pFaceLight, ndxNeighborStyle, bNeighborBump, ndxSample, sampleLight ); - AddSampleLightToRadial( pFaceLight->sample[ndxSample].pos, pFaceLight->sample[ndxSample].normal, - sampleLight, radius*radius, luxelPt, luxelNormal, pRadial, ndxRadial, - bBump, bNeighborBump ); - } - } - } - } - } - } - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::RadialLuxelBuild( CVRADDispColl *pDispTree, radial_t *pRadial, - int ndxStyle, bool bBump ) -{ - // - // get data lighting data - // - int ndxFace = pDispTree->GetParentIndex(); - - dface_t *pFace = &g_pFaces[ndxFace]; - facelight_t *pFaceLight = &facelight[ndxFace]; - - // get the influence radius - float radius2 = pDispTree->GetSampleRadius2(); - float radius = ( float )sqrt( radius2 ); - - int radialSize = pRadial->w * pRadial->h; - for( int ndxRadial = 0; ndxRadial < radialSize; ndxRadial++ ) - { - RadialLuxelAddSamples( ndxFace, pFaceLight->luxel[ndxRadial], pFaceLight->luxelNormals[ndxRadial], - radius, pRadial, ndxRadial, bBump, pFace->styles[ndxStyle] ); - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -radial_t *CVRadDispMgr::BuildLuxelRadial( int ndxFace, int ndxStyle, bool bBump ) -{ - // allocate the radial - radial_t *pRadial = AllocateRadial( ndxFace ); - if( !pRadial ) - return NULL; - - // - // step 1: get the displacement surface to be lit - // - DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; - CVRADDispColl *pDispTree = dispTree.m_pDispTree; - if( !pDispTree ) - return NULL; - - // step 2: build radial luxels - RadialLuxelBuild( pDispTree, pRadial, ndxStyle, bBump ); - - // step 3: return the built radial - return pRadial; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CVRadDispMgr::SampleRadial( int ndxFace, radial_t *pRadial, Vector const &vPos, int ndxLxl, - LightingValue_t *pLightSample, int sampleCount, bool bPatch ) -{ - bool bGoodSample = true; - for ( int count = 0; count < sampleCount; count++ ) - { - pLightSample[count].Zero(); - - if ( pRadial->weight[ndxLxl] > 0.0f ) - { - pLightSample[count].AddWeighted( pRadial->light[count][ndxLxl], ( 1.0f / pRadial->weight[ndxLxl] ) ); - } - else - { - // error, luxel has no samples (not for patches) - if ( !bPatch ) - { - // Yes, 2550 is correct! - // pLightSample[count].Init( 2550.0f, 0.0f, 2550.0f ); - if( count == 0 ) - bGoodSample = false; - } - } - } - - return bGoodSample; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void GetPatchLight( CPatch *pPatch, bool bBump, Vector *pPatchLight ) -{ - VectorCopy( pPatch->totallight.light[0], pPatchLight[0] ); - - if( bBump ) - { - for( int ndxBump = 1; ndxBump < ( NUM_BUMP_VECTS + 1 ); ndxBump++ ) - { - VectorCopy( pPatch->totallight.light[ndxBump], pPatchLight[ndxBump] ); - } - } -} - -extern void GetBumpNormals( const float* sVect, const float* tVect, const Vector& flatNormal, - const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] ); -extern void PreGetBumpNormalsForDisp( texinfo_t *pTexinfo, Vector &vecU, Vector &vecV, Vector &vecNormal ); - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void AddPatchLightToRadial( Vector const &patchOrigin, Vector const &patchNormal, - Vector *pPatchLight, float patchRadius2, - Vector const &luxelPos, Vector const &luxelNormal, - radial_t *pRadial, int ndxRadial, bool bBump, - bool bNeighborBump ) -{ - // calculate the light vector - Vector vSegment = patchOrigin - luxelPos; - - // get the distance to the light - float dist = vSegment.Length(); - float dist2 = dist * dist; - - // Check to see if the light is within the sample influence. - float influence = 1.0f - ( dist2 / ( patchRadius2 ) ); - if ( influence <= 0.0f ) - return; - - if( bBump ) - { - Vector normals[NUM_BUMP_VECTS+1]; - normals[0] = luxelNormal; - texinfo_t *pTexinfo = &texinfo[g_pFaces[pRadial->facenum].texinfo]; - Vector vecTexU, vecTexV; - PreGetBumpNormalsForDisp( pTexinfo, vecTexU, vecTexV, normals[0] ); - GetBumpNormals( vecTexU, vecTexV, normals[0], normals[0], &normals[1] ); - - if( bNeighborBump ) - { - float flScale = patchNormal.Dot( normals[0] ); - flScale = max( 0.0f, flScale ); - float flBumpInfluence = influence * flScale; - - for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) - { - pRadial->light[ndxBump][ndxRadial].AddWeighted( pPatchLight[ndxBump], flBumpInfluence ); - } - - pRadial->weight[ndxRadial] += flBumpInfluence; - } - else - { - float flScale = patchNormal.Dot( normals[0] ); - flScale = max( 0.0f, flScale ); - float flBumpInfluence = influence * flScale * 0.05f; - - for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) - { - pRadial->light[ndxBump][ndxRadial].AddWeighted( pPatchLight[0], flBumpInfluence ); - } - - pRadial->weight[ndxRadial] += flBumpInfluence; - } - } - else - { - float flScale = patchNormal.Dot( luxelNormal ); - flScale = max( 0.0f, flScale ); - influence *= flScale; - pRadial->light[0][ndxRadial].AddWeighted( pPatchLight[0], influence ); - - // add the weight value - pRadial->weight[ndxRadial] += influence; - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::RadialLuxelAddPatch( int ndxFace, Vector const &luxelPt, - Vector const &luxelNormal, float radius, - radial_t *pRadial, int ndxRadial, bool bBump, - CUtlVector &interestingPatches ) -{ -#ifdef SAMPLEHASH_QUERY_ONCE - for ( int i=0; i < interestingPatches.Count(); i++ ) - { - CPatch *pPatch = interestingPatches[i]; - bool bNeighborBump = texinfo[g_pFaces[pPatch->faceNumber].texinfo].flags & SURF_BUMPLIGHT ? true : false; - - Vector patchLight[NUM_BUMP_VECTS+1]; - GetPatchLight( pPatch, bBump, patchLight ); - AddPatchLightToRadial( pPatch->origin, pPatch->normal, patchLight, radius*radius, - luxelPt, luxelNormal, pRadial, ndxRadial, bBump, bNeighborBump ); - } -#else - // calculate one over the voxel size - float ooVoxelSize = 1.0f / SAMPLEHASH_VOXEL_SIZE; - - // - // find voxel info - // - int voxelMin[3], voxelMax[3]; - for ( int axis = 0; axis < 3; axis++ ) - { - voxelMin[axis] = ( int )( ( luxelPt[axis] - radius ) * ooVoxelSize ); - voxelMax[axis] = ( int )( ( luxelPt[axis] + radius ) * ooVoxelSize ) + 1; - } - - unsigned short curIterationKey = IncrementPatchIterationKey(); - PatchSampleData_t patchData; - for ( int ndxZ = voxelMin[2]; ndxZ < voxelMax[2] + 1; ndxZ++ ) - { - for ( int ndxY = voxelMin[1]; ndxY < voxelMax[1] + 1; ndxY++ ) - { - for ( int ndxX = voxelMin[0]; ndxX < voxelMax[0] + 1; ndxX++ ) - { - patchData.x = ndxX * 100; - patchData.y = ndxY * 10; - patchData.z = ndxZ; - - UtlHashHandle_t handle = g_PatchSampleHashTable.Find( patchData ); - if ( handle != g_PatchSampleHashTable.InvalidHandle() ) - { - PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); - int count = pPatchData->m_ndxPatches.Count(); - for ( int ndx = 0; ndx < count; ndx++ ) - { - int ndxPatch = pPatchData->m_ndxPatches.Element( ndx ); - CPatch *pPatch = &g_Patches.Element( ndxPatch ); - if ( pPatch && pPatch->m_IterationKey != curIterationKey ) - { - pPatch->m_IterationKey = curIterationKey; - - if ( IsNeighbor( ndxFace, pPatch->faceNumber ) ) - { - bool bNeighborBump = texinfo[g_pFaces[pPatch->faceNumber].texinfo].flags & SURF_BUMPLIGHT ? true : false; - - Vector patchLight[NUM_BUMP_VECTS+1]; - GetPatchLight( pPatch, bBump, patchLight ); - AddPatchLightToRadial( pPatch->origin, pPatch->normal, patchLight, radius*radius, - luxelPt, luxelNormal, pRadial, ndxRadial, bBump, bNeighborBump ); - } - } - } - } - } - } - } -#endif -} - - -void CVRadDispMgr::GetInterestingPatchesForLuxels( - int ndxFace, - CUtlVector &interestingPatches, - float patchSampleRadius ) -{ - facelight_t *pFaceLight = &facelight[ndxFace]; - - // Get the max bounds of all voxels that these luxels touch. - Vector vLuxelMin( FLT_MAX, FLT_MAX, FLT_MAX ); - Vector vLuxelMax( -FLT_MAX, -FLT_MAX, -FLT_MAX ); - for ( int i=0; i < pFaceLight->numluxels; i++ ) - { - VectorMin( pFaceLight->luxel[i], vLuxelMin, vLuxelMin ); - VectorMax( pFaceLight->luxel[i], vLuxelMax, vLuxelMax ); - } - - int allVoxelMin[3], allVoxelMax[3]; - for ( int axis = 0; axis < 3; axis++ ) - { - allVoxelMin[axis] = ( int )( ( vLuxelMin[axis] - patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ); - allVoxelMax[axis] = ( int )( ( vLuxelMax[axis] + patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ) + 1; - } - int allVoxelSize[3] = { allVoxelMax[0] - allVoxelMin[0], allVoxelMax[1] - allVoxelMin[1], allVoxelMax[2] - allVoxelMin[2] }; - - - // Now figure out exactly which voxels these luxels touch. - CUtlVector voxelBits; - voxelBits.SetSize( ((allVoxelSize[0] * allVoxelSize[1] * allVoxelSize[2]) + 7) / 8 ); - memset( voxelBits.Base(), 0, voxelBits.Count() ); - - for ( int i=0; i < pFaceLight->numluxels; i++ ) - { - int voxelMin[3], voxelMax[3]; - for ( int axis=0; axis < 3; axis++ ) - { - voxelMin[axis] = ( int )( ( pFaceLight->luxel[i][axis] - patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ); - voxelMax[axis] = ( int )( ( pFaceLight->luxel[i][axis] + patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ) + 1; - } - - for ( int x=voxelMin[0]; x < voxelMax[0]; x++ ) - { - for ( int y=voxelMin[1]; y < voxelMax[1]; y++ ) - { - for ( int z=voxelMin[2]; z < voxelMax[2]; z++ ) - { - int iBit = (z - allVoxelMin[2])*(allVoxelSize[0]*allVoxelSize[1]) + - (y-allVoxelMin[1])*allVoxelSize[0] + - (x-allVoxelMin[0]); - voxelBits[iBit>>3] |= (1 << (iBit & 7)); - } - } - } - } - - - // Now get the list of patches that touch those voxels. - unsigned short curIterationKey = IncrementPatchIterationKey(); - - for ( int x=0; x < allVoxelSize[0]; x++ ) - { - for ( int y=0; y < allVoxelSize[1]; y++ ) - { - for ( int z=0; z < allVoxelSize[2]; z++ ) - { - // Make sure this voxel has any luxels that care about it. - int iBit = z*(allVoxelSize[0]*allVoxelSize[1]) + y*allVoxelSize[0] + x; - unsigned char val = voxelBits[iBit>>3] & (1 << (iBit & 7)); - if ( !val ) - continue; - - PatchSampleData_t patchData; - patchData.x = (x + allVoxelMin[0]) * 100; - patchData.y = (y + allVoxelMin[1]) * 10; - patchData.z = (z + allVoxelMin[2]); - - UtlHashHandle_t handle = g_PatchSampleHashTable.Find( patchData ); - if ( handle != g_PatchSampleHashTable.InvalidHandle() ) - { - PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); - - // For all patches that touch this hash table element.. - for ( int ndx = 0; ndx < pPatchData->m_ndxPatches.Count(); ndx++ ) - { - int ndxPatch = pPatchData->m_ndxPatches.Element( ndx ); - CPatch *pPatch = &g_Patches.Element( ndxPatch ); - - // If we haven't touched the patch already and it's a valid neighbor, then we want to use it. - if ( pPatch && pPatch->m_IterationKey != curIterationKey ) - { - pPatch->m_IterationKey = curIterationKey; - - if ( IsNeighbor( ndxFace, pPatch->faceNumber ) ) - { - interestingPatches.AddToTail( pPatch ); - } - } - } - } - } - } - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::RadialPatchBuild( CVRADDispColl *pDispTree, radial_t *pRadial, - bool bBump ) -{ - // - // get data lighting data - // - int ndxFace = pDispTree->GetParentIndex(); - facelight_t *pFaceLight = &facelight[ndxFace]; - - // get the influence radius - float radius2 = pDispTree->GetPatchSampleRadius2(); - float radius = ( float )sqrt( radius2 ); - - CUtlVector interestingPatches; -#ifdef SAMPLEHASH_QUERY_ONCE - GetInterestingPatchesForLuxels( ndxFace, interestingPatches, radius ); -#endif - - int radialSize = pRadial->w * pRadial->h; - for( int ndxRadial = 0; ndxRadial < radialSize; ndxRadial++ ) - { - RadialLuxelAddPatch( - ndxFace, - pFaceLight->luxel[ndxRadial], - pFaceLight->luxelNormals[ndxRadial], - radius, - pRadial, - ndxRadial, - bBump, - interestingPatches ); - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -radial_t *CVRadDispMgr::BuildPatchRadial( int ndxFace, bool bBump ) -{ - // allocate the radial - radial_t *pRadial = AllocateRadial( ndxFace ); - if( !pRadial ) - return NULL; - - // - // step 1: get the displacement surface to be lit - // - DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; - CVRADDispColl *pDispTree = dispTree.m_pDispTree; - if( !pDispTree ) - return NULL; - - // step 2: build radial of patch light - RadialPatchBuild( pDispTree, pRadial, bBump ); - - // step 3: return the built radial - return pRadial; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool SampleInSolid( sample_t *pSample ) -{ - int ndxLeaf = PointLeafnum( pSample->pos ); - return ( dleafs[ndxLeaf].contents == CONTENTS_SOLID ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::InsertSamplesDataIntoHashTable( void ) -{ - int totalSamples = 0; -#if 0 - int totalSamplesInSolid = 0; -#endif - - for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) - { - dface_t *pFace = &g_pFaces[ndxFace]; - facelight_t *pFaceLight = &facelight[ndxFace]; - if( !pFace || !pFaceLight ) - continue; - - if( texinfo[pFace->texinfo].flags & TEX_SPECIAL ) - continue; - -#if 0 - bool bDisp = ( pFace->dispinfo != -1 ); -#endif - // - // for each sample - // - for( int ndxSample = 0; ndxSample < pFaceLight->numsamples; ndxSample++ ) - { - sample_t *pSample = &pFaceLight->sample[ndxSample]; - if( pSample ) - { -#if 0 - if( bDisp ) - { - // test sample to see if the displacement samples resides in solid - if( SampleInSolid( pSample ) ) - { - totalSamplesInSolid++; - continue; - } - } -#endif - - // create the sample handle - SampleHandle_t sampleHandle = ndxSample; - sampleHandle |= ( ndxFace << 16 ); - - SampleData_AddSample( pSample, sampleHandle ); - } - - } - - totalSamples += pFaceLight->numsamples; - } - -#if 0 - // not implemented yet!!! - Msg( "%d samples in solid\n", totalSamplesInSolid ); -#endif - - // log the distribution - SampleData_Log(); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::InsertPatchSampleDataIntoHashTable( void ) -{ - // don't insert patch samples if we are not bouncing light - if( numbounce <= 0 ) - return; - - int totalPatchSamples = 0; - - for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) - { - dface_t *pFace = &g_pFaces[ndxFace]; - facelight_t *pFaceLight = &facelight[ndxFace]; - if( !pFace || !pFaceLight ) - continue; - - if( texinfo[pFace->texinfo].flags & TEX_SPECIAL ) - continue; - - // - // for each patch - // - CPatch *pNextPatch = NULL; - if( g_FacePatches.Element( ndxFace ) != g_FacePatches.InvalidIndex() ) - { - for( CPatch *pPatch = &g_Patches.Element( g_FacePatches.Element( ndxFace ) ); pPatch; pPatch = pNextPatch ) - { - // next patch - pNextPatch = NULL; - if( pPatch->ndxNext != g_Patches.InvalidIndex() ) - { - pNextPatch = &g_Patches.Element( pPatch->ndxNext ); - } - - // skip patches with children - if( pPatch->child1 != g_Patches.InvalidIndex() ) - continue; - - int ndxPatch = pPatch - g_Patches.Base(); - PatchSampleData_AddSample( pPatch, ndxPatch ); - - totalPatchSamples++; - } - } - } -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::StartTimer( const char *name ) -{ - Msg( name ); - m_Timer.Start(); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CVRadDispMgr::EndTimer( void ) -{ - m_Timer.End(); - CCycleCount duration = m_Timer.GetDuration(); - double seconds = duration.GetSeconds(); - - Msg( "Done<%1.4lf sec>\n", seconds ); -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CVRadDispMgr::BuildDispSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) -{ - // get the tree assosciated with the face - DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; - CVRADDispColl *pDispTree = dispTree.m_pDispTree; - if( !pDispTree ) - return false; - - // lightmap size - int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; - int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; - - // calculate the steps in uv space - float stepU = 1.0f / ( float )width; - float stepV = 1.0f / ( float )height; - float halfStepU = stepU * 0.5f; - float halfStepV = stepV * 0.5f; - - // - // build the winding points (used to generate world space winding and - // calculate the area of the "sample") - // - int ndxU, ndxV; - - CUtlVector samples; - samples.SetCount( SINGLEMAP ); - sample_t *pSamples = samples.Base(); - - CUtlVector worldPoints; - worldPoints.SetCount( SINGLEMAP ); - Vector *pWorldPoints = worldPoints.Base(); - - for( ndxV = 0; ndxV < ( height + 1 ); ndxV++ ) - { - for( ndxU = 0; ndxU < ( width + 1 ); ndxU++ ) - { - int ndx = ( ndxV * ( width + 1 ) ) + ndxU; - - Vector2D uv( ndxU * stepU, ndxV * stepV ); - pDispTree->DispUVToSurfPoint( uv, pWorldPoints[ndx], 0.0f ); - } - } - - for( ndxV = 0; ndxV < height; ndxV++ ) - { - for( ndxU = 0; ndxU < width; ndxU++ ) - { - // build the winding - winding_t *pWinding = AllocWinding( 4 ); - if( pWinding ) - { - pWinding->numpoints = 4; - pWinding->p[0] = pWorldPoints[(ndxV*(width+1))+ndxU]; - pWinding->p[1] = pWorldPoints[((ndxV+1)*(width+1))+ndxU]; - pWinding->p[2] = pWorldPoints[((ndxV+1)*(width+1))+(ndxU+1)]; - pWinding->p[3] = pWorldPoints[(ndxV*(width+1))+(ndxU+1)]; - - // calculate the area - float area = WindingArea( pWinding ); - - int ndxSample = ( ndxV * width ) + ndxU; - pSamples[ndxSample].w = pWinding; - pSamples[ndxSample].area = area; - } - else - { - Msg( "BuildDispSamples: WARNING - failed winding allocation\n" ); - } - } - } - - // - // build the samples points (based on s, t and sampleoffset (center of samples); - // generates world space position and normal) - // - for( ndxV = 0; ndxV < height; ndxV++ ) - { - for( ndxU = 0; ndxU < width; ndxU++ ) - { - int ndxSample = ( ndxV * width ) + ndxU; - pSamples[ndxSample].s = ndxU; - pSamples[ndxSample].t = ndxV; - pSamples[ndxSample].coord[0] = ( ndxU * stepU ) + halfStepU; - pSamples[ndxSample].coord[1] = ( ndxV * stepV ) + halfStepV; - pDispTree->DispUVToSurfPoint( pSamples[ndxSample].coord, pSamples[ndxSample].pos, 1.0f ); - pDispTree->DispUVToSurfNormal( pSamples[ndxSample].coord, pSamples[ndxSample].normal ); - } - } - - // - // copy over samples - // - pFaceLight->numsamples = width * height; - pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); - if( !pFaceLight->sample ) - return false; - - memcpy( pFaceLight->sample, pSamples, pFaceLight->numsamples * sizeof( *pFaceLight->sample ) ); - - // statistics - warning?! - if( pFaceLight->numsamples == 0 ) - { - Msg( "BuildDispSamples: WARNING - no samples %d\n", pLightInfo->face - g_pFaces ); - } - - return true; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CVRadDispMgr::BuildDispLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) -{ - // get the tree assosciated with the face - DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; - CVRADDispColl *pDispTree = dispTree.m_pDispTree; - if( !pDispTree ) - return false; - - // lightmap size - int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; - int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; - - // calcuate actual luxel points - pFaceLight->numluxels = width * height; - pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); - pFaceLight->luxelNormals = ( Vector* )calloc( pFaceLight->numluxels, sizeof( Vector ) ); - if( !pFaceLight->luxel || !pFaceLight->luxelNormals ) - return false; - - float stepU = 1.0f / ( float )( width - 1 ); - float stepV = 1.0f / ( float )( height - 1 ); - - for( int ndxV = 0; ndxV < height; ndxV++ ) - { - for( int ndxU = 0; ndxU < width; ndxU++ ) - { - int ndxLuxel = ( ndxV * width ) + ndxU; - - Vector2D uv( ndxU * stepU, ndxV * stepV ); - pDispTree->DispUVToSurfPoint( uv, pFaceLight->luxel[ndxLuxel], 1.0f ); - pDispTree->DispUVToSurfNormal( uv, pFaceLight->luxelNormals[ndxLuxel] ); - } - } - - return true; -} - - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -bool CVRadDispMgr::BuildDispSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) -{ - // get the tree assosciated with the face - DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; - CVRADDispColl *pDispTree = dispTree.m_pDispTree; - if( !pDispTree ) - return false; - - // lightmap size - int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; - int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; - - // calcuate actual luxel points - pFaceLight->numsamples = width * height; - pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); - if( !pFaceLight->sample ) - return false; - - pFaceLight->numluxels = width * height; - pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); - pFaceLight->luxelNormals = ( Vector* )calloc( pFaceLight->numluxels, sizeof( Vector ) ); - if( !pFaceLight->luxel || !pFaceLight->luxelNormals ) - return false; - - float stepU = 1.0f / ( float )( width - 1 ); - float stepV = 1.0f / ( float )( height - 1 ); - float halfStepU = stepU * 0.5f; - float halfStepV = stepV * 0.5f; - - for( int ndxV = 0; ndxV < height; ndxV++ ) - { - for( int ndxU = 0; ndxU < width; ndxU++ ) - { - int ndx = ( ndxV * width ) + ndxU; - - pFaceLight->sample[ndx].s = ndxU; - pFaceLight->sample[ndx].t = ndxV; - pFaceLight->sample[ndx].coord[0] = ( ndxU * stepU ) + halfStepU; - pFaceLight->sample[ndx].coord[1] = ( ndxV * stepV ) + halfStepV; - - pDispTree->DispUVToSurfPoint( pFaceLight->sample[ndx].coord, pFaceLight->sample[ndx].pos, 1.0f ); - pDispTree->DispUVToSurfNormal( pFaceLight->sample[ndx].coord, pFaceLight->sample[ndx].normal ); - - pFaceLight->luxel[ndx] = pFaceLight->sample[ndx].pos; - pFaceLight->luxelNormals[ndx] = pFaceLight->sample[ndx].normal; - } - } - - return true; -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vrad.h" +#include "utlvector.h" +#include "cmodel.h" +#include "BSPTreeData.h" +#include "VRAD_DispColl.h" +#include "CollisionUtils.h" +#include "lightmap.h" +#include "Radial.h" +#include "CollisionUtils.h" +#include "mathlib/bumpvects.h" +#include "utlrbtree.h" +#include "tier0/fasttimer.h" +#include "disp_vrad.h" + +class CBSPDispRayDistanceEnumerator; + +//============================================================================= +// +// Displacement/Face List +// +class CBSPDispFaceListEnumerator : public ISpatialLeafEnumerator, public IBSPTreeDataEnumerator +{ +public: + + //========================================================================= + // + // Construction/Deconstruction + // + CBSPDispFaceListEnumerator() {}; + virtual ~CBSPDispFaceListEnumerator() + { + m_DispList.Purge(); + m_FaceList.Purge(); + } + + // ISpatialLeafEnumerator + bool EnumerateLeaf( int ndxLeaf, int context ); + + // IBSPTreeDataEnumerator + bool FASTCALL EnumerateElement( int userId, int context ); + +public: + + CUtlVector m_DispList; + CUtlVector m_FaceList; +}; + + +//============================================================================= +// +// RayEnumerator +// +class CBSPDispRayEnumerator : public ISpatialLeafEnumerator, public IBSPTreeDataEnumerator +{ +public: + // ISpatialLeafEnumerator + bool EnumerateLeaf( int ndxLeaf, int context ); + + // IBSPTreeDataEnumerator + bool FASTCALL EnumerateElement( int userId, int context ); +}; + +//============================================================================= +// +// VRad Displacement Manager +// +class CVRadDispMgr : public IVRadDispMgr +{ +public: + + //========================================================================= + // + // Construction/Deconstruction + // + CVRadDispMgr(); + virtual ~CVRadDispMgr(); + + // creation/destruction + void Init( void ); + void Shutdown( void ); + + // "CalcPoints" + bool BuildDispSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ); + bool BuildDispLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ); + bool BuildDispSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ); + + // patching functions + void MakePatches( void ); + void SubdividePatch( int iPatch ); + + // pre "FinalLightFace" + void InsertSamplesDataIntoHashTable( void ); + void InsertPatchSampleDataIntoHashTable( void ); + + // "FinalLightFace" + radial_t *BuildLuxelRadial( int ndxFace, int ndxStyle, bool bBump ); + bool SampleRadial( int ndxFace, radial_t *pRadial, Vector const &vPos, int ndxLxl, LightingValue_t *pLightSample, int sampleCount, bool bPatch ); + radial_t *BuildPatchRadial( int ndxFace, bool bBump ); + + // utility + void GetDispSurfNormal( int ndxFace, Vector &pt, Vector &ptNormal, bool bInside ); + void GetDispSurf( int ndxFace, CVRADDispColl **ppDispTree ); + + // bsp tree functions + bool ClipRayToDisp( DispTested_t &dispTested, Ray_t const &ray ); + bool ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, int ndxLeaf ); + void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, int ndxLeaf, + float& dist, dface_t*& pFace, Vector2D& luxelCoord ); + void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf, float& dist, Vector *pNormal ); + + void StartRayTest( DispTested_t &dispTested ); + void AddPolysForRayTrace( void ); + + // general timing -- should be moved!! + void StartTimer( const char *name ); + void EndTimer( void ); + + //========================================================================= + // + // Enumeration Methods + // + bool DispRay_EnumerateLeaf( int ndxLeaf, int context ); + bool DispRay_EnumerateElement( int userId, int context ); + bool DispRayDistance_EnumerateElement( int userId, CBSPDispRayDistanceEnumerator* pEnum ); + + bool DispFaceList_EnumerateLeaf( int ndxLeaf, int context ); + bool DispFaceList_EnumerateElement( int userId, int context ); + +private: + + //========================================================================= + // + // BSP Tree Helpers + // + void InsertDispIntoTree( int ndxDisp ); + void RemoveDispFromTree( int ndxDisp ); + + //========================================================================= + // + // Displacement Data Loader (from .bsp) + // + void UnserializeDisps( void ); + void DispBuilderInit( CCoreDispInfo *pBuilderDisp, dface_t *pFace, int ndxFace ); + + //========================================================================= + // + // Sampling Helpers + // + void RadialLuxelBuild( CVRADDispColl *pDispTree, radial_t *pRadial, int ndxStyle, bool bBump ); + void RadialLuxelAddSamples( int ndxFace, Vector const &luxelPt, Vector const &luxelNormal, float radius, + radial_t *pRadial, int ndxRadial, bool bBump, int lightStyle ); + + void RadialPatchBuild( CVRADDispColl *pDispTree, radial_t *pRadial, bool bBump ); + void RadialLuxelAddPatch( int ndxFace, Vector const &luxelPt, + Vector const &luxelNormal, float radius, + radial_t *pRadial, int ndxRadial, bool bBump, + CUtlVector &interestingPatches ); + + bool IsNeighbor( int iDispFace, int iNeighborFace ); + + void GetInterestingPatchesForLuxels( + int ndxFace, + CUtlVector &interestingPatches, + float patchSampleRadius ); + +private: + + struct DispCollTree_t + { + CVRADDispColl *m_pDispTree; + BSPTreeDataHandle_t m_Handle; + }; + + struct EnumContext_t + { + DispTested_t *m_pDispTested; + Ray_t const *m_pRay; + }; + + CUtlVector m_DispTrees; + + IBSPTreeData *m_pBSPTreeData; + + CBSPDispRayEnumerator m_EnumDispRay; + CBSPDispFaceListEnumerator m_EnumDispFaceList; + + int sampleCount; + Vector *m_pSamplePos; + + CFastTimer m_Timer; +}; + +//----------------------------------------------------------------------------- +// Purpose: expose IVRadDispMgr to vrad +//----------------------------------------------------------------------------- + +static CVRadDispMgr s_DispMgr; + +IVRadDispMgr *StaticDispMgr( void ) +{ + return &s_DispMgr; +} + + +//============================================================================= +// +// Displacement/Face List +// +// ISpatialLeafEnumerator +bool CBSPDispFaceListEnumerator::EnumerateLeaf( int ndxLeaf, int context ) +{ + return s_DispMgr.DispFaceList_EnumerateLeaf( ndxLeaf, context ); +} + +// IBSPTreeDataEnumerator +bool FASTCALL CBSPDispFaceListEnumerator::EnumerateElement( int userId, int context ) +{ + return s_DispMgr.DispFaceList_EnumerateElement( userId, context ); +} + + +//============================================================================= +// +// RayEnumerator +// +bool CBSPDispRayEnumerator::EnumerateLeaf( int ndxLeaf, int context ) +{ + return s_DispMgr.DispRay_EnumerateLeaf( ndxLeaf, context ); +} + +bool FASTCALL CBSPDispRayEnumerator::EnumerateElement( int userId, int context ) +{ + return s_DispMgr.DispRay_EnumerateElement( userId, context ); +} + + +//----------------------------------------------------------------------------- +// Here's an enumerator that we use for testing against disps in a leaf... +//----------------------------------------------------------------------------- + +class CBSPDispRayDistanceEnumerator : public IBSPTreeDataEnumerator +{ +public: + CBSPDispRayDistanceEnumerator() : m_Distance(1.0f), m_pSurface(0) {} + + // IBSPTreeDataEnumerator + bool FASTCALL EnumerateElement( int userId, int context ) + { + return s_DispMgr.DispRayDistance_EnumerateElement( userId, this ); + } + + float m_Distance; + dface_t* m_pSurface; + DispTested_t *m_pDispTested; + Ray_t const *m_pRay; + Vector2D m_LuxelCoord; + Vector m_Normal; +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CVRadDispMgr::CVRadDispMgr() +{ + m_pBSPTreeData = CreateBSPTreeData(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CVRadDispMgr::~CVRadDispMgr() +{ + DestroyBSPTreeData( m_pBSPTreeData ); +} + + +//----------------------------------------------------------------------------- +// Insert a displacement into the tree for collision +//----------------------------------------------------------------------------- +void CVRadDispMgr::InsertDispIntoTree( int ndxDisp ) +{ + DispCollTree_t &dispTree = m_DispTrees[ndxDisp]; + CDispCollTree *pDispTree = dispTree.m_pDispTree; + + // get the bounding box of the tree + Vector boxMin, boxMax; + pDispTree->GetBounds( boxMin, boxMax ); + + // add the displacement to the tree so we will collide against it + dispTree.m_Handle = m_pBSPTreeData->Insert( ndxDisp, boxMin, boxMax ); +} + + +//----------------------------------------------------------------------------- +// Remove a displacement from the tree for collision +//----------------------------------------------------------------------------- +void CVRadDispMgr::RemoveDispFromTree( int ndxDisp ) +{ + // release the tree handle + if( m_DispTrees[ndxDisp].m_Handle != TREEDATA_INVALID_HANDLE ) + { + m_pBSPTreeData->Remove( m_DispTrees[ndxDisp].m_Handle ); + m_DispTrees[ndxDisp].m_Handle = TREEDATA_INVALID_HANDLE; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::Init( void ) +{ + // initialize the bsp tree + m_pBSPTreeData->Init( ToolBSPTree() ); + + // read in displacements that have been compiled into the bsp file + UnserializeDisps(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::Shutdown( void ) +{ + // remove all displacements from the tree + for( int ndxDisp = m_DispTrees.Size(); ndxDisp >= 0; ndxDisp-- ) + { + RemoveDispFromTree( ndxDisp ); + } + + // shutdown the bsp tree + m_pBSPTreeData->Shutdown(); + + // purge the displacement collision tree list + m_DispTrees.Purge(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::DispBuilderInit( CCoreDispInfo *pBuilderDisp, dface_t *pFace, int ndxFace ) +{ + // get the .bsp displacement + ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; + if( !pDisp ) + return; + + // + // initlialize the displacement base surface + // + CCoreDispSurface *pSurf = pBuilderDisp->GetSurface(); + pSurf->SetPointCount( 4 ); + pSurf->SetHandle( ndxFace ); + pSurf->SetContents( pDisp->contents ); + + Vector pt[4]; + int ndxPt; + for( ndxPt = 0; ndxPt < 4; ndxPt++ ) + { + int eIndex = dsurfedges[pFace->firstedge+ndxPt]; + if( eIndex < 0 ) + { + pSurf->SetPoint( ndxPt, dvertexes[dedges[-eIndex].v[1]].point ); + } + else + { + pSurf->SetPoint( ndxPt, dvertexes[dedges[eIndex].v[0]].point ); + } + + VectorCopy( pSurf->GetPoint(ndxPt), pt[ndxPt] ); + } + + // + // calculate the displacement surface normal + // + Vector vFaceNormal; + pSurf->GetNormal( vFaceNormal ); + for( ndxPt = 0; ndxPt < 4; ndxPt++ ) + { + pSurf->SetPointNormal( ndxPt, vFaceNormal ); + } + + // set the surface initial point info + pSurf->SetPointStart( pDisp->startPosition ); + pSurf->FindSurfPointStartIndex(); + pSurf->AdjustSurfPointData(); + + Vector vecTmp( texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][0], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][1], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][2] ); + int nLuxelsPerWorldUnit = static_cast( 1.0f / VectorLength( vecTmp ) ); + Vector vecU( texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][0], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][1], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][2] ); + Vector vecV( texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[1][0], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[1][1], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[1][2] ); + pSurf->CalcLuxelCoords( nLuxelsPerWorldUnit, false, vecU, vecV ); + + pBuilderDisp->SetNeighborData( pDisp->m_EdgeNeighbors, pDisp->m_CornerNeighbors ); + + CDispVert *pVerts = &g_DispVerts[ pDisp->m_iDispVertStart ]; + CDispTri *pTris = &g_DispTris[pDisp->m_iDispTriStart]; + + // + // initialize the displacement data + // + pBuilderDisp->InitDispInfo( + pDisp->power, + pDisp->minTess, + pDisp->smoothingAngle, + pVerts, + pTris ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::UnserializeDisps( void ) +{ + // temporarily create the "builder" displacements + CUtlVector builderDisps; + for ( int iDisp = 0; iDisp < g_dispinfo.Count(); ++iDisp ) + { + CCoreDispInfo *pDisp = new CCoreDispInfo; + if ( !pDisp ) + { + builderDisps.Purge(); + return; + } + + int nIndex = builderDisps.AddToTail(); + pDisp->SetListIndex( nIndex ); + builderDisps[nIndex] = pDisp; + } + + // Set them up as CDispUtilsHelpers. + for ( int iDisp = 0; iDisp < g_dispinfo.Count(); ++iDisp ) + { + builderDisps[iDisp]->SetDispUtilsHelperInfo( builderDisps.Base(), g_dispinfo.Count() ); + } + + // + // find all faces with displacement data and initialize + // + for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) + { + dface_t *pFace = &g_pFaces[ndxFace]; + if( ValidDispFace( pFace ) ) + { + DispBuilderInit( builderDisps[pFace->dispinfo], pFace, ndxFace ); + } + } + + // generate the displacement surfaces + for( int iDisp = 0; iDisp < g_dispinfo.Count(); ++iDisp ) + { + builderDisps[iDisp]->Create(); + } + + // smooth edge normals + SmoothNeighboringDispSurfNormals( builderDisps.Base(), g_dispinfo.Count() ); + + // + // create the displacement collision tree and add it to the bsp tree + // + CVRADDispColl *pDispTrees = new CVRADDispColl[g_dispinfo.Count()]; + if( !pDispTrees ) + return; + + m_DispTrees.AddMultipleToTail( g_dispinfo.Count() ); + + for( int iDisp = 0; iDisp < g_dispinfo.Count(); iDisp++ ) + { + pDispTrees[iDisp].Create( builderDisps[iDisp] ); + + m_DispTrees[iDisp].m_pDispTree = &pDispTrees[iDisp]; + m_DispTrees[iDisp].m_Handle = TREEDATA_INVALID_HANDLE; + + InsertDispIntoTree( iDisp ); + } + + // free "builder" disps + builderDisps.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: create a set of patches for each displacement surface to transfer +// bounced light around with +//----------------------------------------------------------------------------- +void CVRadDispMgr::MakePatches( void ) +{ + // Collect stats - keep track of the total displacement surface area. + float flTotalArea = 0.0f; + + // Create patches for all of the displacements. + int nTreeCount = m_DispTrees.Size(); + for( int iTree = 0; iTree < nTreeCount; ++iTree ) + { + // Get the current displacement collision tree. + CVRADDispColl *pDispTree = m_DispTrees[iTree].m_pDispTree; + if( !pDispTree ) + continue; + + flTotalArea += pDispTree->CreateParentPatches(); + } + + // Print stats. + qprintf( "%i Displacements\n", nTreeCount ); + qprintf( "%i Square Feet [%.2f Square Inches]\n", ( int )( flTotalArea / 144.0f ), flTotalArea ); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::SubdividePatch( int iPatch ) +{ + // Get the current patch to subdivide. + CPatch *pPatch = &g_Patches[iPatch]; + if ( !pPatch ) + return; + + // Create children patches. + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[pPatch->faceNumber].dispinfo]; + CVRADDispColl *pTree = dispTree.m_pDispTree; + if( pTree ) + { + pTree->CreateChildPatches( iPatch, 0 ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::StartRayTest( DispTested_t &dispTested ) +{ + if( m_DispTrees.Size() > 0 ) + { + if( dispTested.m_pTested == 0 ) + { + dispTested.m_pTested = new int[m_DispTrees.Size()]; + memset( dispTested.m_pTested, 0, m_DispTrees.Size() * sizeof( int ) ); + dispTested.m_Enum = 0; + } + ++dispTested.m_Enum; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::ClipRayToDisp( DispTested_t &dispTested, Ray_t const &ray ) +{ + StartRayTest( dispTested ); + + EnumContext_t ctx; + ctx.m_pRay = &ray; + ctx.m_pDispTested = &dispTested; + + // If it got through without a hit, it returns true + return !m_pBSPTreeData->EnumerateLeavesAlongRay( ray, &m_EnumDispRay, ( int )&ctx ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf ) +{ + EnumContext_t ctx; + ctx.m_pRay = &ray; + ctx.m_pDispTested = &dispTested; + + return !m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &m_EnumDispRay, ( int )&ctx ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf, float& dist, dface_t*& pFace, Vector2D& luxelCoord ) +{ + CBSPDispRayDistanceEnumerator rayTestEnum; + rayTestEnum.m_pRay = &ray; + rayTestEnum.m_pDispTested = &dispTested; + + m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &rayTestEnum, 0 ); + + dist = rayTestEnum.m_Distance; + pFace = rayTestEnum.m_pSurface; + if (pFace) + { + Vector2DCopy( rayTestEnum.m_LuxelCoord, luxelCoord ); + } +} + +void CVRadDispMgr::ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf, float& dist, Vector *pNormal ) +{ + CBSPDispRayDistanceEnumerator rayTestEnum; + rayTestEnum.m_pRay = &ray; + rayTestEnum.m_pDispTested = &dispTested; + + m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &rayTestEnum, 0 ); + dist = rayTestEnum.m_Distance; + if ( rayTestEnum.m_pSurface ) + { + *pNormal = rayTestEnum.m_Normal; + } +} + +void CVRadDispMgr::AddPolysForRayTrace( void ) +{ + int nTreeCount = m_DispTrees.Size(); + for( int iTree = 0; iTree < nTreeCount; ++iTree ) + { + // Get the current displacement collision tree. + CVRADDispColl *pDispTree = m_DispTrees[iTree].m_pDispTree; + + // Add the triangles of the tree to the RT environment + pDispTree->AddPolysForRayTrace(); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::GetDispSurfNormal( int ndxFace, Vector &pt, Vector &ptNormal, + bool bInside ) +{ + // get the displacement surface data + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + + // find the parameterized displacement indices + Vector2D uv; + pDispTree->BaseFacePlaneToDispUV( pt, uv ); + + if( bInside ) + { + if( uv[0] < 0.0f || uv[0] > 1.0f ) { Msg( "Disp UV (%f) outside bounds!\n", uv[0] ); } + if( uv[1] < 0.0f || uv[1] > 1.0f ) { Msg( "Disp UV (%f) outside bounds!\n", uv[1] ); } + } + + if( uv[0] < 0.0f ) { uv[0] = 0.0f; } + if( uv[0] > 1.0f ) { uv[0] = 1.0f; } + if( uv[1] < 0.0f ) { uv[1] = 0.0f; } + if( uv[1] > 1.0f ) { uv[1] = 1.0f; } + + // get the normal at "pt" + pDispTree->DispUVToSurfNormal( uv, ptNormal ); + + // get the new "pt" + pDispTree->DispUVToSurfPoint( uv, pt, 1.0f ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::GetDispSurf( int ndxFace, CVRADDispColl **ppDispTree ) +{ + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + *ppDispTree = dispTree.m_pDispTree; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::DispRay_EnumerateLeaf( int ndxLeaf, int context ) +{ + return m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &m_EnumDispRay, context ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::DispRay_EnumerateElement( int userId, int context ) +{ + DispCollTree_t &dispTree = m_DispTrees[userId]; + EnumContext_t *pCtx = ( EnumContext_t* )context; + + // don't test twice (check tested value) + if( pCtx->m_pDispTested->m_pTested[userId] == pCtx->m_pDispTested->m_Enum ) + return true; + + // set the tested value + pCtx->m_pDispTested->m_pTested[userId] = pCtx->m_pDispTested->m_Enum; + + // false mean stop iterating -- return false if we hit! (NOTE: opposite return + // result of the collision tree's ray test, thus the !) + CBaseTrace trace; + trace.fraction = 1.0f; + return ( !dispTree.m_pDispTree->AABBTree_Ray( *pCtx->m_pRay, pCtx->m_pRay->InvDelta(), &trace, true ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +bool CVRadDispMgr::DispRayDistance_EnumerateElement( int userId, CBSPDispRayDistanceEnumerator* pCtx ) +{ + DispCollTree_t &dispTree = m_DispTrees[userId]; + + // don't test twice (check tested value) + if( pCtx->m_pDispTested->m_pTested[userId] == pCtx->m_pDispTested->m_Enum ) + return true; + + // set the tested value + pCtx->m_pDispTested->m_pTested[userId] = pCtx->m_pDispTested->m_Enum; + + // Test the ray, if it's closer than previous tests, use it! + RayDispOutput_t output; + output.ndxVerts[0] = -1; + output.ndxVerts[1] = -1; + output.ndxVerts[2] = -1; + output.ndxVerts[3] = -1; + output.u = -1.0f; + output.v = -1.0f; + output.dist = FLT_MAX; + + if (dispTree.m_pDispTree->AABBTree_Ray( *pCtx->m_pRay, output )) + { + if (output.dist < pCtx->m_Distance) + { + pCtx->m_Distance = output.dist; + pCtx->m_pSurface = &g_pFaces[dispTree.m_pDispTree->GetParentIndex()]; + + // Get the luxel coordinate + ComputePointFromBarycentric( + dispTree.m_pDispTree->GetLuxelCoord(output.ndxVerts[0]), + dispTree.m_pDispTree->GetLuxelCoord(output.ndxVerts[1]), + dispTree.m_pDispTree->GetLuxelCoord(output.ndxVerts[2]), + output.u, output.v, pCtx->m_LuxelCoord ); + + Vector v0,v1,v2; + dispTree.m_pDispTree->GetVert( output.ndxVerts[0], v0 ); + dispTree.m_pDispTree->GetVert( output.ndxVerts[1], v1 ); + dispTree.m_pDispTree->GetVert( output.ndxVerts[2], v2 ); + Vector e0 = v1-v0; + Vector e1 = v2-v0; + pCtx->m_Normal = CrossProduct( e0, e1 ); + VectorNormalize(pCtx->m_Normal); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Test a ray against a particular dispinfo +//----------------------------------------------------------------------------- + +/* +float CVRadDispMgr::ClipRayToDisp( Ray_t const &ray, int dispinfo ) +{ + assert( m_DispTrees.IsValidIndex(dispinfo) ); + + RayDispOutput_t output; + if (!m_DispTrees[dispinfo].m_pDispTree->AABBTree_Ray( ray, output )) + return 1.0f; + return output.dist; +} +*/ + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::DispFaceList_EnumerateLeaf( int ndxLeaf, int context ) +{ + // + // add the faces found in this leaf to the face list + // + dleaf_t *pLeaf = &dleafs[ndxLeaf]; + for( int ndxFace = 0; ndxFace < pLeaf->numleaffaces; ndxFace++ ) + { + // get the current face index + int ndxLeafFace = pLeaf->firstleafface + ndxFace; + + // check to see if the face already lives in the list + int ndx; + int size = m_EnumDispFaceList.m_FaceList.Size(); + for( ndx = 0; ndx < size; ndx++ ) + { + if( m_EnumDispFaceList.m_FaceList[ndx] == ndxLeafFace ) + break; + } + + if( ndx == size ) + { + int ndxList = m_EnumDispFaceList.m_FaceList.AddToTail(); + m_EnumDispFaceList.m_FaceList[ndxList] = ndxLeafFace; + } + } + + return m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &m_EnumDispFaceList, context ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::DispFaceList_EnumerateElement( int userId, int context ) +{ + DispCollTree_t &dispTree = m_DispTrees[userId]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return false; + + // check to see if the displacement already lives in the list + int ndx; + int size = m_EnumDispFaceList.m_DispList.Size(); + for( ndx = 0; ndx < size; ndx++ ) + { + if( m_EnumDispFaceList.m_DispList[ndx] == pDispTree ) + break; + } + + if( ndx == size ) + { + int ndxList = m_EnumDispFaceList.m_DispList.AddToTail(); + m_EnumDispFaceList.m_DispList[ndxList] = pDispTree; + } + + return true; +} + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void GetSampleLight( facelight_t *pFaceLight, int ndxStyle, bool bBumped, + int ndxSample, LightingValue_t *pSampleLight ) +{ +// SampleLight[0].Init( 20.0f, 10.0f, 10.0f ); +// return; + + // get sample from bumped lighting data + if( bBumped ) + { + for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) + { + pSampleLight[ndxBump] = pFaceLight->light[ndxStyle][ndxBump][ndxSample]; + } + } + // just a generally lit surface + else + { + pSampleLight[0] = pFaceLight->light[ndxStyle][0][ndxSample]; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void AddSampleLightToRadial( Vector const &samplePos, Vector const &sampleNormal, + LightingValue_t *pSampleLight, float sampleRadius2, + Vector const &luxelPos, Vector const &luxelNormal, + radial_t *pRadial, int ndxRadial, bool bBumped, + bool bNeighborBumped ) +{ + // check normals to see if sample contributes any light at all + float angle = sampleNormal.Dot( luxelNormal ); + if ( angle < 0.15f ) + return; + + // calculate the light vector + Vector vSegment = samplePos - luxelPos; + + // get the distance to the light + float dist = vSegment.Length(); + float dist2 = dist * dist; + + // Check to see if the light is within the influence. + float influence = 1.0f - ( dist2 / ( sampleRadius2 ) ); + if( influence <= 0.0f ) + return; + + influence *= angle; + + if( bBumped ) + { + if( bNeighborBumped ) + { + for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) + { + pRadial->light[ndxBump][ndxRadial].AddWeighted( pSampleLight[ndxBump], influence ); + } + pRadial->weight[ndxRadial] += influence; + } + else + { + influence *= 0.05f; + for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) + { + pRadial->light[ndxBump][ndxRadial].AddWeighted( pSampleLight[0], influence ); + } + pRadial->weight[ndxRadial] += influence; + } + } + else + { + pRadial->light[0][ndxRadial].AddWeighted( pSampleLight[0], influence ); + pRadial->weight[ndxRadial] += influence; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::IsNeighbor( int iFace, int iNeighborFace ) +{ + if ( iFace == iNeighborFace ) + return true; + + faceneighbor_t *pFaceNeighbor = &faceneighbor[iFace]; + for ( int iNeighbor = 0; iNeighbor < pFaceNeighbor->numneighbors; iNeighbor++ ) + { + if ( pFaceNeighbor->neighbor[iNeighbor] == iNeighborFace ) + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::RadialLuxelAddSamples( int ndxFace, Vector const &luxelPt, Vector const &luxelNormal, float radius, + radial_t *pRadial, int ndxRadial, bool bBump, int lightStyle ) +{ + // calculate one over the voxel size + float ooVoxelSize = 1.0f / SAMPLEHASH_VOXEL_SIZE; + + // + // find voxel info + // + int voxelMin[3], voxelMax[3]; + for( int axis = 0; axis < 3; axis++ ) + { + voxelMin[axis] = ( int )( ( luxelPt[axis] - radius ) * ooVoxelSize ); + voxelMax[axis] = ( int )( ( luxelPt[axis] + radius ) * ooVoxelSize ) + 1; + } + + SampleData_t sampleData; + for( int ndxZ = voxelMin[2]; ndxZ < voxelMax[2] + 1; ndxZ++ ) + { + for( int ndxY = voxelMin[1]; ndxY < voxelMax[1] + 1; ndxY++ ) + { + for( int ndxX = voxelMin[0]; ndxX < voxelMax[0] + 1; ndxX++ ) + { + sampleData.x = ndxX * 100; + sampleData.y = ndxY * 10; + sampleData.z = ndxZ; + + UtlHashHandle_t handle = g_SampleHashTable.Find( sampleData ); + if( handle != g_SampleHashTable.InvalidHandle() ) + { + SampleData_t *pSampleData = &g_SampleHashTable.Element( handle ); + int count = pSampleData->m_Samples.Count(); + for( int ndx = 0; ndx < count; ndx++ ) + { + SampleHandle_t sampleHandle = pSampleData->m_Samples.Element( ndx ); + int ndxSample = ( sampleHandle & 0x0000ffff ); + int ndxFaceLight = ( ( sampleHandle >> 16 ) & 0x0000ffff ); + + facelight_t *pFaceLight = &facelight[ndxFaceLight]; + if( pFaceLight && IsNeighbor( ndxFace, ndxFaceLight ) ) + { + // + // check for similar lightstyles + // + dface_t *pFace = &g_pFaces[ndxFaceLight]; + if( pFace ) + { + int ndxNeighborStyle = -1; + for( int ndxLightStyle = 0; ndxLightStyle < MAXLIGHTMAPS; ndxLightStyle++ ) + { + if( pFace->styles[ndxLightStyle] == lightStyle ) + { + ndxNeighborStyle = ndxLightStyle; + break; + } + } + if( ndxNeighborStyle == -1 ) + continue; + + // is this surface bumped??? + bool bNeighborBump = texinfo[pFace->texinfo].flags & SURF_BUMPLIGHT ? true : false; + + LightingValue_t sampleLight[NUM_BUMP_VECTS+1]; + GetSampleLight( pFaceLight, ndxNeighborStyle, bNeighborBump, ndxSample, sampleLight ); + AddSampleLightToRadial( pFaceLight->sample[ndxSample].pos, pFaceLight->sample[ndxSample].normal, + sampleLight, radius*radius, luxelPt, luxelNormal, pRadial, ndxRadial, + bBump, bNeighborBump ); + } + } + } + } + } + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::RadialLuxelBuild( CVRADDispColl *pDispTree, radial_t *pRadial, + int ndxStyle, bool bBump ) +{ + // + // get data lighting data + // + int ndxFace = pDispTree->GetParentIndex(); + + dface_t *pFace = &g_pFaces[ndxFace]; + facelight_t *pFaceLight = &facelight[ndxFace]; + + // get the influence radius + float radius2 = pDispTree->GetSampleRadius2(); + float radius = ( float )sqrt( radius2 ); + + int radialSize = pRadial->w * pRadial->h; + for( int ndxRadial = 0; ndxRadial < radialSize; ndxRadial++ ) + { + RadialLuxelAddSamples( ndxFace, pFaceLight->luxel[ndxRadial], pFaceLight->luxelNormals[ndxRadial], + radius, pRadial, ndxRadial, bBump, pFace->styles[ndxStyle] ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +radial_t *CVRadDispMgr::BuildLuxelRadial( int ndxFace, int ndxStyle, bool bBump ) +{ + // allocate the radial + radial_t *pRadial = AllocateRadial( ndxFace ); + if( !pRadial ) + return NULL; + + // + // step 1: get the displacement surface to be lit + // + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return NULL; + + // step 2: build radial luxels + RadialLuxelBuild( pDispTree, pRadial, ndxStyle, bBump ); + + // step 3: return the built radial + return pRadial; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::SampleRadial( int ndxFace, radial_t *pRadial, Vector const &vPos, int ndxLxl, + LightingValue_t *pLightSample, int sampleCount, bool bPatch ) +{ + bool bGoodSample = true; + for ( int count = 0; count < sampleCount; count++ ) + { + pLightSample[count].Zero(); + + if ( pRadial->weight[ndxLxl] > 0.0f ) + { + pLightSample[count].AddWeighted( pRadial->light[count][ndxLxl], ( 1.0f / pRadial->weight[ndxLxl] ) ); + } + else + { + // error, luxel has no samples (not for patches) + if ( !bPatch ) + { + // Yes, 2550 is correct! + // pLightSample[count].Init( 2550.0f, 0.0f, 2550.0f ); + if( count == 0 ) + bGoodSample = false; + } + } + } + + return bGoodSample; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void GetPatchLight( CPatch *pPatch, bool bBump, Vector *pPatchLight ) +{ + VectorCopy( pPatch->totallight.light[0], pPatchLight[0] ); + + if( bBump ) + { + for( int ndxBump = 1; ndxBump < ( NUM_BUMP_VECTS + 1 ); ndxBump++ ) + { + VectorCopy( pPatch->totallight.light[ndxBump], pPatchLight[ndxBump] ); + } + } +} + +extern void GetBumpNormals( const float* sVect, const float* tVect, const Vector& flatNormal, + const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] ); +extern void PreGetBumpNormalsForDisp( texinfo_t *pTexinfo, Vector &vecU, Vector &vecV, Vector &vecNormal ); + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void AddPatchLightToRadial( Vector const &patchOrigin, Vector const &patchNormal, + Vector *pPatchLight, float patchRadius2, + Vector const &luxelPos, Vector const &luxelNormal, + radial_t *pRadial, int ndxRadial, bool bBump, + bool bNeighborBump ) +{ + // calculate the light vector + Vector vSegment = patchOrigin - luxelPos; + + // get the distance to the light + float dist = vSegment.Length(); + float dist2 = dist * dist; + + // Check to see if the light is within the sample influence. + float influence = 1.0f - ( dist2 / ( patchRadius2 ) ); + if ( influence <= 0.0f ) + return; + + if( bBump ) + { + Vector normals[NUM_BUMP_VECTS+1]; + normals[0] = luxelNormal; + texinfo_t *pTexinfo = &texinfo[g_pFaces[pRadial->facenum].texinfo]; + Vector vecTexU, vecTexV; + PreGetBumpNormalsForDisp( pTexinfo, vecTexU, vecTexV, normals[0] ); + GetBumpNormals( vecTexU, vecTexV, normals[0], normals[0], &normals[1] ); + + if( bNeighborBump ) + { + float flScale = patchNormal.Dot( normals[0] ); + flScale = max( 0.0f, flScale ); + float flBumpInfluence = influence * flScale; + + for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) + { + pRadial->light[ndxBump][ndxRadial].AddWeighted( pPatchLight[ndxBump], flBumpInfluence ); + } + + pRadial->weight[ndxRadial] += flBumpInfluence; + } + else + { + float flScale = patchNormal.Dot( normals[0] ); + flScale = max( 0.0f, flScale ); + float flBumpInfluence = influence * flScale * 0.05f; + + for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) + { + pRadial->light[ndxBump][ndxRadial].AddWeighted( pPatchLight[0], flBumpInfluence ); + } + + pRadial->weight[ndxRadial] += flBumpInfluence; + } + } + else + { + float flScale = patchNormal.Dot( luxelNormal ); + flScale = max( 0.0f, flScale ); + influence *= flScale; + pRadial->light[0][ndxRadial].AddWeighted( pPatchLight[0], influence ); + + // add the weight value + pRadial->weight[ndxRadial] += influence; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::RadialLuxelAddPatch( int ndxFace, Vector const &luxelPt, + Vector const &luxelNormal, float radius, + radial_t *pRadial, int ndxRadial, bool bBump, + CUtlVector &interestingPatches ) +{ +#ifdef SAMPLEHASH_QUERY_ONCE + for ( int i=0; i < interestingPatches.Count(); i++ ) + { + CPatch *pPatch = interestingPatches[i]; + bool bNeighborBump = texinfo[g_pFaces[pPatch->faceNumber].texinfo].flags & SURF_BUMPLIGHT ? true : false; + + Vector patchLight[NUM_BUMP_VECTS+1]; + GetPatchLight( pPatch, bBump, patchLight ); + AddPatchLightToRadial( pPatch->origin, pPatch->normal, patchLight, radius*radius, + luxelPt, luxelNormal, pRadial, ndxRadial, bBump, bNeighborBump ); + } +#else + // calculate one over the voxel size + float ooVoxelSize = 1.0f / SAMPLEHASH_VOXEL_SIZE; + + // + // find voxel info + // + int voxelMin[3], voxelMax[3]; + for ( int axis = 0; axis < 3; axis++ ) + { + voxelMin[axis] = ( int )( ( luxelPt[axis] - radius ) * ooVoxelSize ); + voxelMax[axis] = ( int )( ( luxelPt[axis] + radius ) * ooVoxelSize ) + 1; + } + + unsigned short curIterationKey = IncrementPatchIterationKey(); + PatchSampleData_t patchData; + for ( int ndxZ = voxelMin[2]; ndxZ < voxelMax[2] + 1; ndxZ++ ) + { + for ( int ndxY = voxelMin[1]; ndxY < voxelMax[1] + 1; ndxY++ ) + { + for ( int ndxX = voxelMin[0]; ndxX < voxelMax[0] + 1; ndxX++ ) + { + patchData.x = ndxX * 100; + patchData.y = ndxY * 10; + patchData.z = ndxZ; + + UtlHashHandle_t handle = g_PatchSampleHashTable.Find( patchData ); + if ( handle != g_PatchSampleHashTable.InvalidHandle() ) + { + PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); + int count = pPatchData->m_ndxPatches.Count(); + for ( int ndx = 0; ndx < count; ndx++ ) + { + int ndxPatch = pPatchData->m_ndxPatches.Element( ndx ); + CPatch *pPatch = &g_Patches.Element( ndxPatch ); + if ( pPatch && pPatch->m_IterationKey != curIterationKey ) + { + pPatch->m_IterationKey = curIterationKey; + + if ( IsNeighbor( ndxFace, pPatch->faceNumber ) ) + { + bool bNeighborBump = texinfo[g_pFaces[pPatch->faceNumber].texinfo].flags & SURF_BUMPLIGHT ? true : false; + + Vector patchLight[NUM_BUMP_VECTS+1]; + GetPatchLight( pPatch, bBump, patchLight ); + AddPatchLightToRadial( pPatch->origin, pPatch->normal, patchLight, radius*radius, + luxelPt, luxelNormal, pRadial, ndxRadial, bBump, bNeighborBump ); + } + } + } + } + } + } + } +#endif +} + + +void CVRadDispMgr::GetInterestingPatchesForLuxels( + int ndxFace, + CUtlVector &interestingPatches, + float patchSampleRadius ) +{ + facelight_t *pFaceLight = &facelight[ndxFace]; + + // Get the max bounds of all voxels that these luxels touch. + Vector vLuxelMin( FLT_MAX, FLT_MAX, FLT_MAX ); + Vector vLuxelMax( -FLT_MAX, -FLT_MAX, -FLT_MAX ); + for ( int i=0; i < pFaceLight->numluxels; i++ ) + { + VectorMin( pFaceLight->luxel[i], vLuxelMin, vLuxelMin ); + VectorMax( pFaceLight->luxel[i], vLuxelMax, vLuxelMax ); + } + + int allVoxelMin[3], allVoxelMax[3]; + for ( int axis = 0; axis < 3; axis++ ) + { + allVoxelMin[axis] = ( int )( ( vLuxelMin[axis] - patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ); + allVoxelMax[axis] = ( int )( ( vLuxelMax[axis] + patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ) + 1; + } + int allVoxelSize[3] = { allVoxelMax[0] - allVoxelMin[0], allVoxelMax[1] - allVoxelMin[1], allVoxelMax[2] - allVoxelMin[2] }; + + + // Now figure out exactly which voxels these luxels touch. + CUtlVector voxelBits; + voxelBits.SetSize( ((allVoxelSize[0] * allVoxelSize[1] * allVoxelSize[2]) + 7) / 8 ); + memset( voxelBits.Base(), 0, voxelBits.Count() ); + + for ( int i=0; i < pFaceLight->numluxels; i++ ) + { + int voxelMin[3], voxelMax[3]; + for ( int axis=0; axis < 3; axis++ ) + { + voxelMin[axis] = ( int )( ( pFaceLight->luxel[i][axis] - patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ); + voxelMax[axis] = ( int )( ( pFaceLight->luxel[i][axis] + patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ) + 1; + } + + for ( int x=voxelMin[0]; x < voxelMax[0]; x++ ) + { + for ( int y=voxelMin[1]; y < voxelMax[1]; y++ ) + { + for ( int z=voxelMin[2]; z < voxelMax[2]; z++ ) + { + int iBit = (z - allVoxelMin[2])*(allVoxelSize[0]*allVoxelSize[1]) + + (y-allVoxelMin[1])*allVoxelSize[0] + + (x-allVoxelMin[0]); + voxelBits[iBit>>3] |= (1 << (iBit & 7)); + } + } + } + } + + + // Now get the list of patches that touch those voxels. + unsigned short curIterationKey = IncrementPatchIterationKey(); + + for ( int x=0; x < allVoxelSize[0]; x++ ) + { + for ( int y=0; y < allVoxelSize[1]; y++ ) + { + for ( int z=0; z < allVoxelSize[2]; z++ ) + { + // Make sure this voxel has any luxels that care about it. + int iBit = z*(allVoxelSize[0]*allVoxelSize[1]) + y*allVoxelSize[0] + x; + unsigned char val = voxelBits[iBit>>3] & (1 << (iBit & 7)); + if ( !val ) + continue; + + PatchSampleData_t patchData; + patchData.x = (x + allVoxelMin[0]) * 100; + patchData.y = (y + allVoxelMin[1]) * 10; + patchData.z = (z + allVoxelMin[2]); + + UtlHashHandle_t handle = g_PatchSampleHashTable.Find( patchData ); + if ( handle != g_PatchSampleHashTable.InvalidHandle() ) + { + PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); + + // For all patches that touch this hash table element.. + for ( int ndx = 0; ndx < pPatchData->m_ndxPatches.Count(); ndx++ ) + { + int ndxPatch = pPatchData->m_ndxPatches.Element( ndx ); + CPatch *pPatch = &g_Patches.Element( ndxPatch ); + + // If we haven't touched the patch already and it's a valid neighbor, then we want to use it. + if ( pPatch && pPatch->m_IterationKey != curIterationKey ) + { + pPatch->m_IterationKey = curIterationKey; + + if ( IsNeighbor( ndxFace, pPatch->faceNumber ) ) + { + interestingPatches.AddToTail( pPatch ); + } + } + } + } + } + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::RadialPatchBuild( CVRADDispColl *pDispTree, radial_t *pRadial, + bool bBump ) +{ + // + // get data lighting data + // + int ndxFace = pDispTree->GetParentIndex(); + facelight_t *pFaceLight = &facelight[ndxFace]; + + // get the influence radius + float radius2 = pDispTree->GetPatchSampleRadius2(); + float radius = ( float )sqrt( radius2 ); + + CUtlVector interestingPatches; +#ifdef SAMPLEHASH_QUERY_ONCE + GetInterestingPatchesForLuxels( ndxFace, interestingPatches, radius ); +#endif + + int radialSize = pRadial->w * pRadial->h; + for( int ndxRadial = 0; ndxRadial < radialSize; ndxRadial++ ) + { + RadialLuxelAddPatch( + ndxFace, + pFaceLight->luxel[ndxRadial], + pFaceLight->luxelNormals[ndxRadial], + radius, + pRadial, + ndxRadial, + bBump, + interestingPatches ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +radial_t *CVRadDispMgr::BuildPatchRadial( int ndxFace, bool bBump ) +{ + // allocate the radial + radial_t *pRadial = AllocateRadial( ndxFace ); + if( !pRadial ) + return NULL; + + // + // step 1: get the displacement surface to be lit + // + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return NULL; + + // step 2: build radial of patch light + RadialPatchBuild( pDispTree, pRadial, bBump ); + + // step 3: return the built radial + return pRadial; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool SampleInSolid( sample_t *pSample ) +{ + int ndxLeaf = PointLeafnum( pSample->pos ); + return ( dleafs[ndxLeaf].contents == CONTENTS_SOLID ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::InsertSamplesDataIntoHashTable( void ) +{ + int totalSamples = 0; +#if 0 + int totalSamplesInSolid = 0; +#endif + + for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) + { + dface_t *pFace = &g_pFaces[ndxFace]; + facelight_t *pFaceLight = &facelight[ndxFace]; + if( !pFace || !pFaceLight ) + continue; + + if( texinfo[pFace->texinfo].flags & TEX_SPECIAL ) + continue; + +#if 0 + bool bDisp = ( pFace->dispinfo != -1 ); +#endif + // + // for each sample + // + for( int ndxSample = 0; ndxSample < pFaceLight->numsamples; ndxSample++ ) + { + sample_t *pSample = &pFaceLight->sample[ndxSample]; + if( pSample ) + { +#if 0 + if( bDisp ) + { + // test sample to see if the displacement samples resides in solid + if( SampleInSolid( pSample ) ) + { + totalSamplesInSolid++; + continue; + } + } +#endif + + // create the sample handle + SampleHandle_t sampleHandle = ndxSample; + sampleHandle |= ( ndxFace << 16 ); + + SampleData_AddSample( pSample, sampleHandle ); + } + + } + + totalSamples += pFaceLight->numsamples; + } + +#if 0 + // not implemented yet!!! + Msg( "%d samples in solid\n", totalSamplesInSolid ); +#endif + + // log the distribution + SampleData_Log(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::InsertPatchSampleDataIntoHashTable( void ) +{ + // don't insert patch samples if we are not bouncing light + if( numbounce <= 0 ) + return; + + int totalPatchSamples = 0; + + for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) + { + dface_t *pFace = &g_pFaces[ndxFace]; + facelight_t *pFaceLight = &facelight[ndxFace]; + if( !pFace || !pFaceLight ) + continue; + + if( texinfo[pFace->texinfo].flags & TEX_SPECIAL ) + continue; + + // + // for each patch + // + CPatch *pNextPatch = NULL; + if( g_FacePatches.Element( ndxFace ) != g_FacePatches.InvalidIndex() ) + { + for( CPatch *pPatch = &g_Patches.Element( g_FacePatches.Element( ndxFace ) ); pPatch; pPatch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( pPatch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( pPatch->ndxNext ); + } + + // skip patches with children + if( pPatch->child1 != g_Patches.InvalidIndex() ) + continue; + + int ndxPatch = pPatch - g_Patches.Base(); + PatchSampleData_AddSample( pPatch, ndxPatch ); + + totalPatchSamples++; + } + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::StartTimer( const char *name ) +{ + Msg( name ); + m_Timer.Start(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::EndTimer( void ) +{ + m_Timer.End(); + CCycleCount duration = m_Timer.GetDuration(); + double seconds = duration.GetSeconds(); + + Msg( "Done<%1.4lf sec>\n", seconds ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::BuildDispSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // get the tree assosciated with the face + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return false; + + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // calculate the steps in uv space + float stepU = 1.0f / ( float )width; + float stepV = 1.0f / ( float )height; + float halfStepU = stepU * 0.5f; + float halfStepV = stepV * 0.5f; + + // + // build the winding points (used to generate world space winding and + // calculate the area of the "sample") + // + int ndxU, ndxV; + + CUtlVector samples; + samples.SetCount( SINGLEMAP ); + sample_t *pSamples = samples.Base(); + + CUtlVector worldPoints; + worldPoints.SetCount( SINGLEMAP ); + Vector *pWorldPoints = worldPoints.Base(); + + for( ndxV = 0; ndxV < ( height + 1 ); ndxV++ ) + { + for( ndxU = 0; ndxU < ( width + 1 ); ndxU++ ) + { + int ndx = ( ndxV * ( width + 1 ) ) + ndxU; + + Vector2D uv( ndxU * stepU, ndxV * stepV ); + pDispTree->DispUVToSurfPoint( uv, pWorldPoints[ndx], 0.0f ); + } + } + + for( ndxV = 0; ndxV < height; ndxV++ ) + { + for( ndxU = 0; ndxU < width; ndxU++ ) + { + // build the winding + winding_t *pWinding = AllocWinding( 4 ); + if( pWinding ) + { + pWinding->numpoints = 4; + pWinding->p[0] = pWorldPoints[(ndxV*(width+1))+ndxU]; + pWinding->p[1] = pWorldPoints[((ndxV+1)*(width+1))+ndxU]; + pWinding->p[2] = pWorldPoints[((ndxV+1)*(width+1))+(ndxU+1)]; + pWinding->p[3] = pWorldPoints[(ndxV*(width+1))+(ndxU+1)]; + + // calculate the area + float area = WindingArea( pWinding ); + + int ndxSample = ( ndxV * width ) + ndxU; + pSamples[ndxSample].w = pWinding; + pSamples[ndxSample].area = area; + } + else + { + Msg( "BuildDispSamples: WARNING - failed winding allocation\n" ); + } + } + } + + // + // build the samples points (based on s, t and sampleoffset (center of samples); + // generates world space position and normal) + // + for( ndxV = 0; ndxV < height; ndxV++ ) + { + for( ndxU = 0; ndxU < width; ndxU++ ) + { + int ndxSample = ( ndxV * width ) + ndxU; + pSamples[ndxSample].s = ndxU; + pSamples[ndxSample].t = ndxV; + pSamples[ndxSample].coord[0] = ( ndxU * stepU ) + halfStepU; + pSamples[ndxSample].coord[1] = ( ndxV * stepV ) + halfStepV; + pDispTree->DispUVToSurfPoint( pSamples[ndxSample].coord, pSamples[ndxSample].pos, 1.0f ); + pDispTree->DispUVToSurfNormal( pSamples[ndxSample].coord, pSamples[ndxSample].normal ); + } + } + + // + // copy over samples + // + pFaceLight->numsamples = width * height; + pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); + if( !pFaceLight->sample ) + return false; + + memcpy( pFaceLight->sample, pSamples, pFaceLight->numsamples * sizeof( *pFaceLight->sample ) ); + + // statistics - warning?! + if( pFaceLight->numsamples == 0 ) + { + Msg( "BuildDispSamples: WARNING - no samples %d\n", pLightInfo->face - g_pFaces ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::BuildDispLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // get the tree assosciated with the face + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return false; + + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // calcuate actual luxel points + pFaceLight->numluxels = width * height; + pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); + pFaceLight->luxelNormals = ( Vector* )calloc( pFaceLight->numluxels, sizeof( Vector ) ); + if( !pFaceLight->luxel || !pFaceLight->luxelNormals ) + return false; + + float stepU = 1.0f / ( float )( width - 1 ); + float stepV = 1.0f / ( float )( height - 1 ); + + for( int ndxV = 0; ndxV < height; ndxV++ ) + { + for( int ndxU = 0; ndxU < width; ndxU++ ) + { + int ndxLuxel = ( ndxV * width ) + ndxU; + + Vector2D uv( ndxU * stepU, ndxV * stepV ); + pDispTree->DispUVToSurfPoint( uv, pFaceLight->luxel[ndxLuxel], 1.0f ); + pDispTree->DispUVToSurfNormal( uv, pFaceLight->luxelNormals[ndxLuxel] ); + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::BuildDispSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // get the tree assosciated with the face + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return false; + + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // calcuate actual luxel points + pFaceLight->numsamples = width * height; + pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); + if( !pFaceLight->sample ) + return false; + + pFaceLight->numluxels = width * height; + pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); + pFaceLight->luxelNormals = ( Vector* )calloc( pFaceLight->numluxels, sizeof( Vector ) ); + if( !pFaceLight->luxel || !pFaceLight->luxelNormals ) + return false; + + float stepU = 1.0f / ( float )( width - 1 ); + float stepV = 1.0f / ( float )( height - 1 ); + float halfStepU = stepU * 0.5f; + float halfStepV = stepV * 0.5f; + + for( int ndxV = 0; ndxV < height; ndxV++ ) + { + for( int ndxU = 0; ndxU < width; ndxU++ ) + { + int ndx = ( ndxV * width ) + ndxU; + + pFaceLight->sample[ndx].s = ndxU; + pFaceLight->sample[ndx].t = ndxV; + pFaceLight->sample[ndx].coord[0] = ( ndxU * stepU ) + halfStepU; + pFaceLight->sample[ndx].coord[1] = ( ndxV * stepV ) + halfStepV; + + pDispTree->DispUVToSurfPoint( pFaceLight->sample[ndx].coord, pFaceLight->sample[ndx].pos, 1.0f ); + pDispTree->DispUVToSurfNormal( pFaceLight->sample[ndx].coord, pFaceLight->sample[ndx].normal ); + + pFaceLight->luxel[ndx] = pFaceLight->sample[ndx].pos; + pFaceLight->luxelNormals[ndx] = pFaceLight->sample[ndx].normal; + } + } + + return true; +} diff --git a/mp/src/utils/vrad/vraddll.cpp b/mp/src/utils/vrad/vraddll.cpp index 87f8d7fc..bc9f2f45 100644 --- a/mp/src/utils/vrad/vraddll.cpp +++ b/mp/src/utils/vrad/vraddll.cpp @@ -1,243 +1,243 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -//#include -#include "vraddll.h" -#include "bsplib.h" -#include "vrad.h" -#include "map_shared.h" -#include "lightmap.h" -#include "threads.h" - - -static CUtlVector g_LastGoodLightData; -static CUtlVector g_FacesTouched; - - -static CVRadDLL g_VRadDLL; -EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVRadDLL, IVRadDLL, VRAD_INTERFACE_VERSION, g_VRadDLL ); -EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVRadDLL, ILaunchableDLL, LAUNCHABLE_DLL_INTERFACE_VERSION, g_VRadDLL ); - - -// ---------------------------------------------------------------------------- // -// temporary static array data size tracking -// original data size = 143 megs -// - converting ddispindices, ddispverts, g_dispinfo, and dlightdata to CUtlVector -// - 51 megs -// ---------------------------------------------------------------------------- // - -class dat -{ -public: - char *name; - int size; -}; -#define DATENTRY(name) {#name, sizeof(name)} - -dat g_Dats[] = -{ - DATENTRY(dmodels), - DATENTRY(dvisdata), - DATENTRY(dlightdataLDR), - DATENTRY(dlightdataHDR), - DATENTRY(dentdata), - DATENTRY(dleafs), - DATENTRY(dplanes), - DATENTRY(dvertexes), - DATENTRY(g_vertnormalindices), - DATENTRY(g_vertnormals), - DATENTRY(texinfo), - DATENTRY(dtexdata), - DATENTRY(g_dispinfo), - DATENTRY(dorigfaces), - DATENTRY(g_primitives), - DATENTRY(g_primverts), - DATENTRY(g_primindices), - DATENTRY(dfaces), - DATENTRY(dedges), - DATENTRY(dleaffaces), - DATENTRY(dleafbrushes), - DATENTRY(dsurfedges), - DATENTRY(dbrushes), - DATENTRY(dbrushsides), - DATENTRY(dareas), - DATENTRY(dareaportals), - DATENTRY(dworldlights), - DATENTRY(dleafwaterdata), - DATENTRY(g_ClipPortalVerts), - DATENTRY(g_CubemapSamples), - DATENTRY(g_TexDataStringData), - DATENTRY(g_TexDataStringTable), - DATENTRY(g_Overlays) -}; - -int CalcDatSize() -{ - int ret = 0; - int count = sizeof( g_Dats ) / sizeof( g_Dats[0] ); - - int i; - for( i=1; i < count; i++ ) - { - if( g_Dats[i-1].size > g_Dats[i].size ) - { - dat temp = g_Dats[i-1]; - g_Dats[i-1] = g_Dats[i]; - g_Dats[i] = temp; - - if( i > 1 ) - i -= 2; - else - i -= 1; - } - } - - for( i=0; i < count; i++ ) - ret += g_Dats[i].size; - - return ret; -} - -int g_TotalDatSize = CalcDatSize(); - - - - -int CVRadDLL::main( int argc, char **argv ) -{ - return VRAD_Main( argc, argv ); -} - - -bool CVRadDLL::Init( char const *pFilename ) -{ - VRAD_Init(); - - // Set options and run vrad startup code. - do_fast = true; - g_bLowPriorityThreads = true; - g_pIncremental = GetIncremental(); - - VRAD_LoadBSP( pFilename ); - return true; -} - - -void CVRadDLL::Release() -{ -} - - -void CVRadDLL::GetBSPInfo( CBSPInfo *pInfo ) -{ - pInfo->dlightdata = pdlightdata->Base(); - pInfo->lightdatasize = pdlightdata->Count(); - - pInfo->dfaces = dfaces; - pInfo->m_pFacesTouched = g_FacesTouched.Base(); - pInfo->numfaces = numfaces; - - pInfo->dvertexes = dvertexes; - pInfo->numvertexes = numvertexes; - - pInfo->dedges = dedges; - pInfo->numedges = numedges; - - pInfo->dsurfedges = dsurfedges; - pInfo->numsurfedges = numsurfedges; - - pInfo->texinfo = texinfo.Base(); - pInfo->numtexinfo = texinfo.Count(); - - pInfo->g_dispinfo = g_dispinfo.Base(); - pInfo->g_numdispinfo = g_dispinfo.Count(); - - pInfo->dtexdata = dtexdata; - pInfo->numtexdata = numtexdata; - - pInfo->texDataStringData = g_TexDataStringData.Base(); - pInfo->nTexDataStringData = g_TexDataStringData.Count(); - - pInfo->texDataStringTable = g_TexDataStringTable.Base(); - pInfo->nTexDataStringTable = g_TexDataStringTable.Count(); -} - - -bool CVRadDLL::DoIncrementalLight( char const *pVMFFile ) -{ - char tempPath[MAX_PATH], tempFilename[MAX_PATH]; - GetTempPath( sizeof( tempPath ), tempPath ); - GetTempFileName( tempPath, "vmf_entities_", 0, tempFilename ); - - FileHandle_t fp = g_pFileSystem->Open( tempFilename, "wb" ); - if( !fp ) - return false; - - g_pFileSystem->Write( pVMFFile, strlen(pVMFFile)+1, fp ); - g_pFileSystem->Close( fp ); - - // Parse the new entities. - if( !LoadEntsFromMapFile( tempFilename ) ) - return false; - - // Create lights. - CreateDirectLights(); - - // set up sky cameras - ProcessSkyCameras(); - - g_bInterrupt = false; - if( RadWorld_Go() ) - { - // Save off the last finished lighting results for the BSP. - g_LastGoodLightData.CopyArray( pdlightdata->Base(), pdlightdata->Count() ); - if( g_pIncremental ) - g_pIncremental->GetFacesTouched( g_FacesTouched ); - - return true; - } - else - { - g_iCurFace = 0; - return false; - } -} - - -bool CVRadDLL::Serialize() -{ - if( !g_pIncremental ) - return false; - - if( g_LastGoodLightData.Count() > 0 ) - { - pdlightdata->CopyArray( g_LastGoodLightData.Base(), g_LastGoodLightData.Count() ); - - if( g_pIncremental->Serialize() ) - { - // Delete this so it doesn't keep re-saving it. - g_LastGoodLightData.Purge(); - return true; - } - } - - return false; -} - - -float CVRadDLL::GetPercentComplete() -{ - return (float)g_iCurFace / numfaces; -} - - -void CVRadDLL::Interrupt() -{ - g_bInterrupt = true; -} - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +//#include +#include "vraddll.h" +#include "bsplib.h" +#include "vrad.h" +#include "map_shared.h" +#include "lightmap.h" +#include "threads.h" + + +static CUtlVector g_LastGoodLightData; +static CUtlVector g_FacesTouched; + + +static CVRadDLL g_VRadDLL; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVRadDLL, IVRadDLL, VRAD_INTERFACE_VERSION, g_VRadDLL ); +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVRadDLL, ILaunchableDLL, LAUNCHABLE_DLL_INTERFACE_VERSION, g_VRadDLL ); + + +// ---------------------------------------------------------------------------- // +// temporary static array data size tracking +// original data size = 143 megs +// - converting ddispindices, ddispverts, g_dispinfo, and dlightdata to CUtlVector +// - 51 megs +// ---------------------------------------------------------------------------- // + +class dat +{ +public: + char *name; + int size; +}; +#define DATENTRY(name) {#name, sizeof(name)} + +dat g_Dats[] = +{ + DATENTRY(dmodels), + DATENTRY(dvisdata), + DATENTRY(dlightdataLDR), + DATENTRY(dlightdataHDR), + DATENTRY(dentdata), + DATENTRY(dleafs), + DATENTRY(dplanes), + DATENTRY(dvertexes), + DATENTRY(g_vertnormalindices), + DATENTRY(g_vertnormals), + DATENTRY(texinfo), + DATENTRY(dtexdata), + DATENTRY(g_dispinfo), + DATENTRY(dorigfaces), + DATENTRY(g_primitives), + DATENTRY(g_primverts), + DATENTRY(g_primindices), + DATENTRY(dfaces), + DATENTRY(dedges), + DATENTRY(dleaffaces), + DATENTRY(dleafbrushes), + DATENTRY(dsurfedges), + DATENTRY(dbrushes), + DATENTRY(dbrushsides), + DATENTRY(dareas), + DATENTRY(dareaportals), + DATENTRY(dworldlights), + DATENTRY(dleafwaterdata), + DATENTRY(g_ClipPortalVerts), + DATENTRY(g_CubemapSamples), + DATENTRY(g_TexDataStringData), + DATENTRY(g_TexDataStringTable), + DATENTRY(g_Overlays) +}; + +int CalcDatSize() +{ + int ret = 0; + int count = sizeof( g_Dats ) / sizeof( g_Dats[0] ); + + int i; + for( i=1; i < count; i++ ) + { + if( g_Dats[i-1].size > g_Dats[i].size ) + { + dat temp = g_Dats[i-1]; + g_Dats[i-1] = g_Dats[i]; + g_Dats[i] = temp; + + if( i > 1 ) + i -= 2; + else + i -= 1; + } + } + + for( i=0; i < count; i++ ) + ret += g_Dats[i].size; + + return ret; +} + +int g_TotalDatSize = CalcDatSize(); + + + + +int CVRadDLL::main( int argc, char **argv ) +{ + return VRAD_Main( argc, argv ); +} + + +bool CVRadDLL::Init( char const *pFilename ) +{ + VRAD_Init(); + + // Set options and run vrad startup code. + do_fast = true; + g_bLowPriorityThreads = true; + g_pIncremental = GetIncremental(); + + VRAD_LoadBSP( pFilename ); + return true; +} + + +void CVRadDLL::Release() +{ +} + + +void CVRadDLL::GetBSPInfo( CBSPInfo *pInfo ) +{ + pInfo->dlightdata = pdlightdata->Base(); + pInfo->lightdatasize = pdlightdata->Count(); + + pInfo->dfaces = dfaces; + pInfo->m_pFacesTouched = g_FacesTouched.Base(); + pInfo->numfaces = numfaces; + + pInfo->dvertexes = dvertexes; + pInfo->numvertexes = numvertexes; + + pInfo->dedges = dedges; + pInfo->numedges = numedges; + + pInfo->dsurfedges = dsurfedges; + pInfo->numsurfedges = numsurfedges; + + pInfo->texinfo = texinfo.Base(); + pInfo->numtexinfo = texinfo.Count(); + + pInfo->g_dispinfo = g_dispinfo.Base(); + pInfo->g_numdispinfo = g_dispinfo.Count(); + + pInfo->dtexdata = dtexdata; + pInfo->numtexdata = numtexdata; + + pInfo->texDataStringData = g_TexDataStringData.Base(); + pInfo->nTexDataStringData = g_TexDataStringData.Count(); + + pInfo->texDataStringTable = g_TexDataStringTable.Base(); + pInfo->nTexDataStringTable = g_TexDataStringTable.Count(); +} + + +bool CVRadDLL::DoIncrementalLight( char const *pVMFFile ) +{ + char tempPath[MAX_PATH], tempFilename[MAX_PATH]; + GetTempPath( sizeof( tempPath ), tempPath ); + GetTempFileName( tempPath, "vmf_entities_", 0, tempFilename ); + + FileHandle_t fp = g_pFileSystem->Open( tempFilename, "wb" ); + if( !fp ) + return false; + + g_pFileSystem->Write( pVMFFile, strlen(pVMFFile)+1, fp ); + g_pFileSystem->Close( fp ); + + // Parse the new entities. + if( !LoadEntsFromMapFile( tempFilename ) ) + return false; + + // Create lights. + CreateDirectLights(); + + // set up sky cameras + ProcessSkyCameras(); + + g_bInterrupt = false; + if( RadWorld_Go() ) + { + // Save off the last finished lighting results for the BSP. + g_LastGoodLightData.CopyArray( pdlightdata->Base(), pdlightdata->Count() ); + if( g_pIncremental ) + g_pIncremental->GetFacesTouched( g_FacesTouched ); + + return true; + } + else + { + g_iCurFace = 0; + return false; + } +} + + +bool CVRadDLL::Serialize() +{ + if( !g_pIncremental ) + return false; + + if( g_LastGoodLightData.Count() > 0 ) + { + pdlightdata->CopyArray( g_LastGoodLightData.Base(), g_LastGoodLightData.Count() ); + + if( g_pIncremental->Serialize() ) + { + // Delete this so it doesn't keep re-saving it. + g_LastGoodLightData.Purge(); + return true; + } + } + + return false; +} + + +float CVRadDLL::GetPercentComplete() +{ + return (float)g_iCurFace / numfaces; +} + + +void CVRadDLL::Interrupt() +{ + g_bInterrupt = true; +} + + diff --git a/mp/src/utils/vrad/vraddll.h b/mp/src/utils/vrad/vraddll.h index 506e8eb6..988daca5 100644 --- a/mp/src/utils/vrad/vraddll.h +++ b/mp/src/utils/vrad/vraddll.h @@ -1,34 +1,34 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#ifndef VRADDLL_H -#define VRADDLL_H -#ifdef _WIN32 -#pragma once -#endif - - -#include "ivraddll.h" -#include "ilaunchabledll.h" - - -class CVRadDLL : public IVRadDLL, public ILaunchableDLL -{ -// IVRadDLL overrides. -public: - virtual int main( int argc, char **argv ); - virtual bool Init( char const *pFilename ); - virtual void Release(); - virtual void GetBSPInfo( CBSPInfo *pInfo ); - virtual bool DoIncrementalLight( char const *pVMFFile ); - virtual bool Serialize(); - virtual float GetPercentComplete(); - virtual void Interrupt(); -}; - - -#endif // VRADDLL_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VRADDLL_H +#define VRADDLL_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "ivraddll.h" +#include "ilaunchabledll.h" + + +class CVRadDLL : public IVRadDLL, public ILaunchableDLL +{ +// IVRadDLL overrides. +public: + virtual int main( int argc, char **argv ); + virtual bool Init( char const *pFilename ); + virtual void Release(); + virtual void GetBSPInfo( CBSPInfo *pInfo ); + virtual bool DoIncrementalLight( char const *pVMFFile ); + virtual bool Serialize(); + virtual float GetPercentComplete(); + virtual void Interrupt(); +}; + + +#endif // VRADDLL_H diff --git a/mp/src/utils/vrad/vradstaticprops.cpp b/mp/src/utils/vrad/vradstaticprops.cpp index f240d94f..f4f04a69 100644 --- a/mp/src/utils/vrad/vradstaticprops.cpp +++ b/mp/src/utils/vrad/vradstaticprops.cpp @@ -1,1915 +1,1915 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Revision: $ -// $NoKeywords: $ -// -// This file contains code to allow us to associate client data with bsp leaves. -// -//=============================================================================// - -#include "vrad.h" -#include "mathlib/vector.h" -#include "UtlBuffer.h" -#include "utlvector.h" -#include "GameBSPFile.h" -#include "BSPTreeData.h" -#include "VPhysics_Interface.h" -#include "Studio.h" -#include "Optimize.h" -#include "Bsplib.h" -#include "CModel.h" -#include "PhysDll.h" -#include "phyfile.h" -#include "collisionutils.h" -#include "tier1/KeyValues.h" -#include "pacifier.h" -#include "materialsystem/imaterial.h" -#include "materialsystem/hardwareverts.h" -#include "byteswap.h" -#include "mpivrad.h" -#include "vtf/vtf.h" -#include "tier1/utldict.h" -#include "tier1/utlsymbol.h" - -#include "messbuf.h" -#include "vmpi.h" -#include "vmpi_distribute_work.h" - - -#define ALIGN_TO_POW2(x,y) (((x)+(y-1))&~(y-1)) - -// identifies a vertex embedded in solid -// lighting will be copied from nearest valid neighbor -struct badVertex_t -{ - int m_ColorVertex; - Vector m_Position; - Vector m_Normal; -}; - -// a final colored vertex -struct colorVertex_t -{ - Vector m_Color; - Vector m_Position; - bool m_bValid; -}; - -class CComputeStaticPropLightingResults -{ -public: - ~CComputeStaticPropLightingResults() - { - m_ColorVertsArrays.PurgeAndDeleteElements(); - } - - CUtlVector< CUtlVector* > m_ColorVertsArrays; -}; - -//----------------------------------------------------------------------------- -// Globals -//----------------------------------------------------------------------------- -CUtlSymbolTable g_ForcedTextureShadowsModels; - -// DON'T USE THIS FROM WITHIN A THREAD. THERE IS A THREAD CONTEXT CREATED -// INSIDE PropTested_t. USE THAT INSTEAD. -IPhysicsCollision *s_pPhysCollision = NULL; - -//----------------------------------------------------------------------------- -// Vrad's static prop manager -//----------------------------------------------------------------------------- - -class CVradStaticPropMgr : public IVradStaticPropMgr -{ -public: - // constructor, destructor - CVradStaticPropMgr(); - virtual ~CVradStaticPropMgr(); - - // methods of IStaticPropMgr - void Init(); - void Shutdown(); - - // iterate all the instanced static props and compute their vertex lighting - void ComputeLighting( int iThread ); - -private: - // VMPI stuff. - static void VMPI_ProcessStaticProp_Static( int iThread, uint64 iStaticProp, MessageBuffer *pBuf ); - static void VMPI_ReceiveStaticPropResults_Static( uint64 iStaticProp, MessageBuffer *pBuf, int iWorker ); - void VMPI_ProcessStaticProp( int iThread, int iStaticProp, MessageBuffer *pBuf ); - void VMPI_ReceiveStaticPropResults( int iStaticProp, MessageBuffer *pBuf, int iWorker ); - - // local thread version - static void ThreadComputeStaticPropLighting( int iThread, void *pUserData ); - void ComputeLightingForProp( int iThread, int iStaticProp ); - - // Methods associated with unserializing static props - void UnserializeModelDict( CUtlBuffer& buf ); - void UnserializeModels( CUtlBuffer& buf ); - void UnserializeStaticProps(); - - // Creates a collision model - void CreateCollisionModel( char const* pModelName ); - -private: - // Unique static prop models - struct StaticPropDict_t - { - vcollide_t m_loadedModel; - CPhysCollide* m_pModel; - Vector m_Mins; // Bounding box is in local coordinates - Vector m_Maxs; - studiohdr_t* m_pStudioHdr; - CUtlBuffer m_VtxBuf; - CUtlVector m_textureShadowIndex; // each texture has an index if this model casts texture shadows - CUtlVector m_triangleMaterialIndex;// each triangle has an index if this model casts texture shadows - }; - - struct MeshData_t - { - CUtlVector m_Verts; - int m_nLod; - }; - - // A static prop instance - struct CStaticProp - { - Vector m_Origin; - QAngle m_Angles; - Vector m_mins; - Vector m_maxs; - Vector m_LightingOrigin; - int m_ModelIdx; - BSPTreeDataHandle_t m_Handle; - CUtlVector m_MeshData; - int m_Flags; - bool m_bLightingOriginValid; - }; - - // Enumeration context - struct EnumContext_t - { - PropTested_t* m_pPropTested; - Ray_t const* m_pRay; - }; - - // The list of all static props - CUtlVector m_StaticPropDict; - CUtlVector m_StaticProps; - - bool m_bIgnoreStaticPropTrace; - - void ComputeLighting( CStaticProp &prop, int iThread, int prop_index, CComputeStaticPropLightingResults *pResults ); - void ApplyLightingToStaticProp( CStaticProp &prop, const CComputeStaticPropLightingResults *pResults ); - - void SerializeLighting(); - void AddPolysForRayTrace(); - void BuildTriList( CStaticProp &prop ); -}; - - -//----------------------------------------------------------------------------- -// Expose IVradStaticPropMgr to vrad -//----------------------------------------------------------------------------- - -static CVradStaticPropMgr g_StaticPropMgr; -IVradStaticPropMgr* StaticPropMgr() -{ - return &g_StaticPropMgr; -} - - -//----------------------------------------------------------------------------- -// constructor, destructor -//----------------------------------------------------------------------------- - -CVradStaticPropMgr::CVradStaticPropMgr() -{ - // set to ignore static prop traces - m_bIgnoreStaticPropTrace = false; -} - -CVradStaticPropMgr::~CVradStaticPropMgr() -{ -} - -//----------------------------------------------------------------------------- -// Makes sure the studio model is a static prop -//----------------------------------------------------------------------------- - -bool IsStaticProp( studiohdr_t* pHdr ) -{ - if (!(pHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP)) - return false; - - return true; -} - - -//----------------------------------------------------------------------------- -// Load a file into a Utlbuf -//----------------------------------------------------------------------------- -static bool LoadFile( char const* pFileName, CUtlBuffer& buf ) -{ - if ( !g_pFullFileSystem ) - return false; - - return g_pFullFileSystem->ReadFile( pFileName, NULL, buf ); -} - - -//----------------------------------------------------------------------------- -// Constructs the file name from the model name -//----------------------------------------------------------------------------- -static char const* ConstructFileName( char const* pModelName ) -{ - static char buf[1024]; - sprintf( buf, "%s%s", gamedir, pModelName ); - return buf; -} - - -//----------------------------------------------------------------------------- -// Computes a convex hull from a studio mesh -//----------------------------------------------------------------------------- -static CPhysConvex* ComputeConvexHull( mstudiomesh_t* pMesh, studiohdr_t *pStudioHdr ) -{ - const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData( (void *)pStudioHdr ); - Assert( vertData ); // This can only return NULL on X360 for now - - // Generate a list of all verts in the mesh - Vector** ppVerts = (Vector**)_alloca(pMesh->numvertices * sizeof(Vector*) ); - for (int i = 0; i < pMesh->numvertices; ++i) - { - ppVerts[i] = vertData->Position(i); - } - - // Generate a convex hull from the verts - return s_pPhysCollision->ConvexFromVerts( ppVerts, pMesh->numvertices ); -} - - -//----------------------------------------------------------------------------- -// Computes a convex hull from the studio model -//----------------------------------------------------------------------------- -CPhysCollide* ComputeConvexHull( studiohdr_t* pStudioHdr ) -{ - CUtlVector convexHulls; - - for (int body = 0; body < pStudioHdr->numbodyparts; ++body ) - { - mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( body ); - for( int model = 0; model < pBodyPart->nummodels; ++model ) - { - mstudiomodel_t *pStudioModel = pBodyPart->pModel( model ); - for( int mesh = 0; mesh < pStudioModel->nummeshes; ++mesh ) - { - // Make a convex hull for each mesh - // NOTE: This won't work unless the model has been compiled - // with $staticprop - mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( mesh ); - convexHulls.AddToTail( ComputeConvexHull( pStudioMesh, pStudioHdr ) ); - } - } - } - - // Convert an array of convex elements to a compiled collision model - // (this deletes the convex elements) - return s_pPhysCollision->ConvertConvexToCollide( convexHulls.Base(), convexHulls.Size() ); -} - - -//----------------------------------------------------------------------------- -// Load studio model vertex data from a file... -//----------------------------------------------------------------------------- - -bool LoadStudioModel( char const* pModelName, CUtlBuffer& buf ) -{ - // No luck, gotta build it - // Construct the file name... - if (!LoadFile( pModelName, buf )) - { - Warning("Error! Unable to load model \"%s\"\n", pModelName ); - return false; - } - - // Check that it's valid - if (strncmp ((const char *) buf.PeekGet(), "IDST", 4) && - strncmp ((const char *) buf.PeekGet(), "IDAG", 4)) - { - Warning("Error! Invalid model file \"%s\"\n", pModelName ); - return false; - } - - studiohdr_t* pHdr = (studiohdr_t*)buf.PeekGet(); - - Studio_ConvertStudioHdrToNewVersion( pHdr ); - - if (pHdr->version != STUDIO_VERSION) - { - Warning("Error! Invalid model version \"%s\"\n", pModelName ); - return false; - } - - if (!IsStaticProp(pHdr)) - { - Warning("Error! To use model \"%s\"\n" - " as a static prop, it must be compiled with $staticprop!\n", pModelName ); - return false; - } - - // ensure reset - pHdr->pVertexBase = NULL; - pHdr->pIndexBase = NULL; - - return true; -} - -bool LoadStudioCollisionModel( char const* pModelName, CUtlBuffer& buf ) -{ - char tmp[1024]; - Q_strncpy( tmp, pModelName, sizeof( tmp ) ); - Q_SetExtension( tmp, ".phy", sizeof( tmp ) ); - // No luck, gotta build it - if (!LoadFile( tmp, buf )) - { - // this is not an error, the model simply has no PHY file - return false; - } - - phyheader_t *header = (phyheader_t *)buf.PeekGet(); - - if ( header->size != sizeof(*header) || header->solidCount <= 0 ) - return false; - - return true; -} - -bool LoadVTXFile( char const* pModelName, const studiohdr_t *pStudioHdr, CUtlBuffer& buf ) -{ - char filename[MAX_PATH]; - - // construct filename - Q_StripExtension( pModelName, filename, sizeof( filename ) ); - strcat( filename, ".dx80.vtx" ); - - if ( !LoadFile( filename, buf ) ) - { - Warning( "Error! Unable to load file \"%s\"\n", filename ); - return false; - } - - OptimizedModel::FileHeader_t* pVtxHdr = (OptimizedModel::FileHeader_t *)buf.Base(); - - // Check that it's valid - if ( pVtxHdr->version != OPTIMIZED_MODEL_FILE_VERSION ) - { - Warning( "Error! Invalid VTX file version: %d, expected %d \"%s\"\n", pVtxHdr->version, OPTIMIZED_MODEL_FILE_VERSION, filename ); - return false; - } - if ( pVtxHdr->checkSum != pStudioHdr->checksum ) - { - Warning( "Error! Invalid VTX file checksum: %d, expected %d \"%s\"\n", pVtxHdr->checkSum, pStudioHdr->checksum, filename ); - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Gets a vertex position from a strip index -//----------------------------------------------------------------------------- -inline static Vector* PositionFromIndex( const mstudio_meshvertexdata_t *vertData, mstudiomesh_t* pMesh, OptimizedModel::StripGroupHeader_t* pStripGroup, int i ) -{ - OptimizedModel::Vertex_t* pVert = pStripGroup->pVertex( i ); - return vertData->Position( pVert->origMeshVertID ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Writes a glview text file containing the collision surface in question -// Input : *pCollide - -// *pFilename - -//----------------------------------------------------------------------------- -void DumpCollideToGlView( vcollide_t *pCollide, const char *pFilename ) -{ - if ( !pCollide ) - return; - - Msg("Writing %s...\n", pFilename ); - - FILE *fp = fopen( pFilename, "w" ); - for (int i = 0; i < pCollide->solidCount; ++i) - { - Vector *outVerts; - int vertCount = s_pPhysCollision->CreateDebugMesh( pCollide->solids[i], &outVerts ); - int triCount = vertCount / 3; - int vert = 0; - - unsigned char r = (i & 1) * 64 + 64; - unsigned char g = (i & 2) * 64 + 64; - unsigned char b = (i & 4) * 64 + 64; - - float fr = r / 255.0f; - float fg = g / 255.0f; - float fb = b / 255.0f; - - for ( int i = 0; i < triCount; i++ ) - { - fprintf( fp, "3\n" ); - fprintf( fp, "%6.3f %6.3f %6.3f %.2f %.3f %.3f\n", - outVerts[vert].x, outVerts[vert].y, outVerts[vert].z, fr, fg, fb ); - vert++; - fprintf( fp, "%6.3f %6.3f %6.3f %.2f %.3f %.3f\n", - outVerts[vert].x, outVerts[vert].y, outVerts[vert].z, fr, fg, fb ); - vert++; - fprintf( fp, "%6.3f %6.3f %6.3f %.2f %.3f %.3f\n", - outVerts[vert].x, outVerts[vert].y, outVerts[vert].z, fr, fg, fb ); - vert++; - } - s_pPhysCollision->DestroyDebugMesh( vertCount, outVerts ); - } - fclose( fp ); -} - - -static bool PointInTriangle( const Vector2D &p, const Vector2D &v0, const Vector2D &v1, const Vector2D &v2 ) -{ - float coords[3]; - GetBarycentricCoords2D( v0, v1, v2, p, coords ); - for ( int i = 0; i < 3; i++ ) - { - if ( coords[i] < 0.0f || coords[i] > 1.0f ) - return false; - } - float sum = coords[0] + coords[1] + coords[2]; - if ( sum > 1.0f ) - return false; - return true; -} - -bool LoadFileIntoBuffer( CUtlBuffer &buf, const char *pFilename ) -{ - FileHandle_t fileHandle = g_pFileSystem->Open( pFilename, "rb" ); - if ( !fileHandle ) - return false; - - // Get the file size - int texSize = g_pFileSystem->Size( fileHandle ); - buf.EnsureCapacity( texSize ); - int nBytesRead = g_pFileSystem->Read( buf.Base(), texSize, fileHandle ); - g_pFileSystem->Close( fileHandle ); - buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead ); - buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); - return true; -} - -// keeps a list of all textures that cast shadows via alpha channel -class CShadowTextureList -{ -public: - // This loads a vtf and converts it to RGB8888 format - unsigned char *LoadVTFRGB8888( const char *pName, int *pWidth, int *pHeight, bool *pClampU, bool *pClampV ) - { - char szPath[MAX_PATH]; - Q_strncpy( szPath, "materials/", sizeof( szPath ) ); - Q_strncat( szPath, pName, sizeof( szPath ), COPY_ALL_CHARACTERS ); - Q_strncat( szPath, ".vtf", sizeof( szPath ), COPY_ALL_CHARACTERS ); - Q_FixSlashes( szPath, CORRECT_PATH_SEPARATOR ); - - CUtlBuffer buf; - if ( !LoadFileIntoBuffer( buf, szPath ) ) - return NULL; - IVTFTexture *pTex = CreateVTFTexture(); - if (!pTex->Unserialize( buf )) - return NULL; - Msg("Loaded alpha texture %s\n", szPath ); - unsigned char *pSrcImage = pTex->ImageData( 0, 0, 0, 0, 0, 0 ); - int iWidth = pTex->Width(); - int iHeight = pTex->Height(); - ImageFormat dstFormat = IMAGE_FORMAT_RGBA8888; - ImageFormat srcFormat = pTex->Format(); - *pClampU = (pTex->Flags() & TEXTUREFLAGS_CLAMPS) ? true : false; - *pClampV = (pTex->Flags() & TEXTUREFLAGS_CLAMPT) ? true : false; - unsigned char *pDstImage = new unsigned char[ImageLoader::GetMemRequired( iWidth, iHeight, 1, dstFormat, false )]; - - if( !ImageLoader::ConvertImageFormat( pSrcImage, srcFormat, - pDstImage, dstFormat, iWidth, iHeight, 0, 0 ) ) - { - delete[] pDstImage; - return NULL; - } - - *pWidth = iWidth; - *pHeight = iHeight; - return pDstImage; - } - - // Checks the database for the material and loads if necessary - // returns true if found and pIndex will be the index, -1 if no alpha shadows - bool FindOrLoadIfValid( const char *pMaterialName, int *pIndex ) - { - *pIndex = -1; - int index = m_Textures.Find(pMaterialName); - bool bFound = false; - if ( index != m_Textures.InvalidIndex() ) - { - bFound = true; - *pIndex = index; - } - else - { - KeyValues *pVMT = new KeyValues("vmt"); - CUtlBuffer buf(0,0,CUtlBuffer::TEXT_BUFFER); - LoadFileIntoBuffer( buf, pMaterialName ); - if ( pVMT->LoadFromBuffer( pMaterialName, buf ) ) - { - bFound = true; - if ( pVMT->FindKey("$translucent") || pVMT->FindKey("$alphatest") ) - { - KeyValues *pBaseTexture = pVMT->FindKey("$basetexture"); - if ( pBaseTexture ) - { - const char *pBaseTextureName = pBaseTexture->GetString(); - if ( pBaseTextureName ) - { - int w, h; - bool bClampU = false; - bool bClampV = false; - unsigned char *pImageBits = LoadVTFRGB8888( pBaseTextureName, &w, &h, &bClampU, &bClampV ); - if ( pImageBits ) - { - int index = m_Textures.Insert( pMaterialName ); - m_Textures[index].InitFromRGB8888( w, h, pImageBits ); - *pIndex = index; - if ( pVMT->FindKey("$nocull") ) - { - // UNDONE: Support this? Do we need to emit two triangles? - m_Textures[index].allowBackface = true; - } - m_Textures[index].clampU = bClampU; - m_Textures[index].clampV = bClampV; - delete[] pImageBits; - } - } - } - } - - } - pVMT->deleteThis(); - } - - return bFound; - } - - - // iterate the textures for the model and load each one into the database - // this is used on models marked to cast texture shadows - void LoadAllTexturesForModel( studiohdr_t *pHdr, int *pTextureList ) - { - for ( int i = 0; i < pHdr->numtextures; i++ ) - { - int textureIndex = -1; - // try to add each texture to the transparent shadow manager - char szPath[MAX_PATH]; - - // iterate quietly through all specified directories until a valid material is found - for ( int j = 0; j < pHdr->numcdtextures; j++ ) - { - Q_strncpy( szPath, "materials/", sizeof( szPath ) ); - Q_strncat( szPath, pHdr->pCdtexture( j ), sizeof( szPath ) ); - const char *textureName = pHdr->pTexture( i )->pszName(); - Q_strncat( szPath, textureName, sizeof( szPath ), COPY_ALL_CHARACTERS ); - Q_strncat( szPath, ".vmt", sizeof( szPath ), COPY_ALL_CHARACTERS ); - Q_FixSlashes( szPath, CORRECT_PATH_SEPARATOR ); - if ( FindOrLoadIfValid( szPath, &textureIndex ) ) - break; - } - - pTextureList[i] = textureIndex; - } - } - - int AddMaterialEntry( int shadowTextureIndex, const Vector2D &t0, const Vector2D &t1, const Vector2D &t2 ) - { - int index = m_MaterialEntries.AddToTail(); - m_MaterialEntries[index].textureIndex = shadowTextureIndex; - m_MaterialEntries[index].uv[0] = t0; - m_MaterialEntries[index].uv[1] = t1; - m_MaterialEntries[index].uv[2] = t2; - return index; - } - - // HACKHACK: Compute the average coverage for this triangle by sampling the AABB of its texture space - float ComputeCoverageForTriangle( int shadowTextureIndex, const Vector2D &t0, const Vector2D &t1, const Vector2D &t2 ) - { - float umin = min(t0.x, t1.x); - umin = min(umin, t2.x); - float umax = max(t0.x, t1.x); - umax = max(umax, t2.x); - - float vmin = min(t0.y, t1.y); - vmin = min(vmin, t2.y); - float vmax = max(t0.y, t1.y); - vmax = max(vmax, t2.y); - - // UNDONE: Do something about tiling - umin = clamp(umin, 0, 1); - umax = clamp(umax, 0, 1); - vmin = clamp(vmin, 0, 1); - vmax = clamp(vmax, 0, 1); - Assert(umin>=0.0f && umax <= 1.0f); - Assert(vmin>=0.0f && vmax <= 1.0f); - const alphatexture_t &tex = m_Textures.Element(shadowTextureIndex); - int u0 = umin * (tex.width-1); - int u1 = umax * (tex.width-1); - int v0 = vmin * (tex.height-1); - int v1 = vmax * (tex.height-1); - - int total = 0; - int count = 0; - for ( int v = v0; v <= v1; v++ ) - { - int row = (v * tex.width); - for ( int u = u0; u <= u1; u++ ) - { - total += tex.pAlphaTexels[row + u]; - count++; - } - } - if ( count ) - { - float coverage = float(total) / (count * 255.0f); - return coverage; - } - return 1.0f; - } - - int SampleMaterial( int materialIndex, const Vector &coords, bool bBackface ) - { - const materialentry_t &mat = m_MaterialEntries[materialIndex]; - const alphatexture_t &tex = m_Textures.Element(m_MaterialEntries[materialIndex].textureIndex); - if ( bBackface && !tex.allowBackface ) - return 0; - Vector2D uv = coords.x * mat.uv[0] + coords.y * mat.uv[1] + coords.z * mat.uv[2]; - int u = RoundFloatToInt( uv[0] * tex.width ); - int v = RoundFloatToInt( uv[1] * tex.height ); - - // asume power of 2, clamp or wrap - // UNDONE: Support clamp? This code should work -#if 0 - u = tex.clampU ? clamp(u,0,(tex.width-1)) : (u & (tex.width-1)); - v = tex.clampV ? clamp(v,0,(tex.height-1)) : (v & (tex.height-1)); -#else - // for now always wrap - u &= (tex.width-1); - v &= (tex.height-1); -#endif - - return tex.pAlphaTexels[v * tex.width + u]; - } - - struct alphatexture_t - { - short width; - short height; - bool allowBackface; - bool clampU; - bool clampV; - unsigned char *pAlphaTexels; - - void InitFromRGB8888( int w, int h, unsigned char *pTexels ) - { - width = w; - height = h; - pAlphaTexels = new unsigned char[w*h]; - for ( int i = 0; i < h; i++ ) - { - for ( int j = 0; j < w; j++ ) - { - int index = (i*w) + j; - pAlphaTexels[index] = pTexels[index*4 + 3]; - } - } - } - }; - struct materialentry_t - { - int textureIndex; - Vector2D uv[3]; - }; - // this is the list of textures we've loaded - // only load each one once - CUtlDict< alphatexture_t, unsigned short > m_Textures; - CUtlVector m_MaterialEntries; -}; - -// global to keep the shadow-casting texture list and their alpha bits -CShadowTextureList g_ShadowTextureList; - -float ComputeCoverageFromTexture( float b0, float b1, float b2, int32 hitID ) -{ - const float alphaScale = 1.0f / 255.0f; - // UNDONE: Pass ray down to determine backfacing? - //Vector normal( tri.m_flNx, tri.m_flNy, tri.m_flNz ); - //bool bBackface = DotProduct(delta, tri.N) > 0 ? true : false; - Vector coords(b0,b1,b2); - return alphaScale * g_ShadowTextureList.SampleMaterial( g_RtEnv.GetTriangleMaterial(hitID), coords, false ); -} - -// this is here to strip models/ or .mdl or whatnot -void CleanModelName( const char *pModelName, char *pOutput, int outLen ) -{ - // strip off leading models/ if it exists - const char *pModelDir = "models/"; - int modelLen = Q_strlen(pModelDir); - - if ( !Q_strnicmp(pModelName, pModelDir, modelLen ) ) - { - pModelName += modelLen; - } - Q_strncpy( pOutput, pModelName, outLen ); - - // truncate any .mdl extension - char *dot = strchr(pOutput,'.'); - if ( dot ) - { - *dot = 0; - } - -} - - -void ForceTextureShadowsOnModel( const char *pModelName ) -{ - char buf[1024]; - CleanModelName( pModelName, buf, sizeof(buf) ); - if ( !g_ForcedTextureShadowsModels.Find(buf).IsValid()) - { - g_ForcedTextureShadowsModels.AddString(buf); - } -} - -bool IsModelTextureShadowsForced( const char *pModelName ) -{ - char buf[1024]; - CleanModelName( pModelName, buf, sizeof(buf) ); - return g_ForcedTextureShadowsModels.Find(buf).IsValid(); -} - - -//----------------------------------------------------------------------------- -// Creates a collision model (based on the render geometry!) -//----------------------------------------------------------------------------- -void CVradStaticPropMgr::CreateCollisionModel( char const* pModelName ) -{ - CUtlBuffer buf; - CUtlBuffer bufvtx; - CUtlBuffer bufphy; - - int i = m_StaticPropDict.AddToTail(); - m_StaticPropDict[i].m_pModel = NULL; - m_StaticPropDict[i].m_pStudioHdr = NULL; - - if ( !LoadStudioModel( pModelName, buf ) ) - { - VectorCopy( vec3_origin, m_StaticPropDict[i].m_Mins ); - VectorCopy( vec3_origin, m_StaticPropDict[i].m_Maxs ); - return; - } - - studiohdr_t* pHdr = (studiohdr_t*)buf.Base(); - - VectorCopy( pHdr->hull_min, m_StaticPropDict[i].m_Mins ); - VectorCopy( pHdr->hull_max, m_StaticPropDict[i].m_Maxs ); - - if ( LoadStudioCollisionModel( pModelName, bufphy ) ) - { - phyheader_t header; - bufphy.Get( &header, sizeof(header) ); - - vcollide_t *pCollide = &m_StaticPropDict[i].m_loadedModel; - s_pPhysCollision->VCollideLoad( pCollide, header.solidCount, (const char *)bufphy.PeekGet(), bufphy.TellPut() - bufphy.TellGet() ); - m_StaticPropDict[i].m_pModel = m_StaticPropDict[i].m_loadedModel.solids[0]; - - /* - static int propNum = 0; - char tmp[128]; - sprintf( tmp, "staticprop%03d.txt", propNum ); - DumpCollideToGlView( pCollide, tmp ); - ++propNum; - */ - } - else - { - // mark this as unused - m_StaticPropDict[i].m_loadedModel.solidCount = 0; - - // CPhysCollide* pPhys = CreatePhysCollide( pHdr, pVtxHdr ); - m_StaticPropDict[i].m_pModel = ComputeConvexHull( pHdr ); - } - - // clone it - m_StaticPropDict[i].m_pStudioHdr = (studiohdr_t *)malloc( buf.Size() ); - memcpy( m_StaticPropDict[i].m_pStudioHdr, (studiohdr_t*)buf.Base(), buf.Size() ); - - if ( !LoadVTXFile( pModelName, m_StaticPropDict[i].m_pStudioHdr, m_StaticPropDict[i].m_VtxBuf ) ) - { - // failed, leave state identified as disabled - m_StaticPropDict[i].m_VtxBuf.Purge(); - } - - if ( g_bTextureShadows ) - { - if ( (pHdr->flags & STUDIOHDR_FLAGS_CAST_TEXTURE_SHADOWS) || IsModelTextureShadowsForced(pModelName) ) - { - m_StaticPropDict[i].m_textureShadowIndex.RemoveAll(); - m_StaticPropDict[i].m_triangleMaterialIndex.RemoveAll(); - m_StaticPropDict[i].m_textureShadowIndex.AddMultipleToTail( pHdr->numtextures ); - g_ShadowTextureList.LoadAllTexturesForModel( pHdr, m_StaticPropDict[i].m_textureShadowIndex.Base() ); - } - } -} - - -//----------------------------------------------------------------------------- -// Unserialize static prop model dictionary -//----------------------------------------------------------------------------- -void CVradStaticPropMgr::UnserializeModelDict( CUtlBuffer& buf ) -{ - int count = buf.GetInt(); - while ( --count >= 0 ) - { - StaticPropDictLump_t lump; - buf.Get( &lump, sizeof(StaticPropDictLump_t) ); - - CreateCollisionModel( lump.m_Name ); - } -} - -void CVradStaticPropMgr::UnserializeModels( CUtlBuffer& buf ) -{ - int count = buf.GetInt(); - - m_StaticProps.AddMultipleToTail(count); - for ( int i = 0; i < count; ++i ) - { - StaticPropLump_t lump; - buf.Get( &lump, sizeof(StaticPropLump_t) ); - - VectorCopy( lump.m_Origin, m_StaticProps[i].m_Origin ); - VectorCopy( lump.m_Angles, m_StaticProps[i].m_Angles ); - VectorCopy( lump.m_LightingOrigin, m_StaticProps[i].m_LightingOrigin ); - m_StaticProps[i].m_bLightingOriginValid = ( lump.m_Flags & STATIC_PROP_USE_LIGHTING_ORIGIN ) > 0; - m_StaticProps[i].m_ModelIdx = lump.m_PropType; - m_StaticProps[i].m_Handle = TREEDATA_INVALID_HANDLE; - m_StaticProps[i].m_Flags = lump.m_Flags; - } -} - -//----------------------------------------------------------------------------- -// Unserialize static props -//----------------------------------------------------------------------------- - -void CVradStaticPropMgr::UnserializeStaticProps() -{ - // Unserialize static props, insert them into the appropriate leaves - GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); - int size = g_GameLumps.GameLumpSize( handle ); - if (!size) - return; - - if ( g_GameLumps.GetGameLumpVersion( handle ) != GAMELUMP_STATIC_PROPS_VERSION ) - { - Error( "Cannot load the static props... encountered a stale map version. Re-vbsp the map." ); - } - - if ( g_GameLumps.GetGameLump( handle ) ) - { - CUtlBuffer buf( g_GameLumps.GetGameLump(handle), size, CUtlBuffer::READ_ONLY ); - UnserializeModelDict( buf ); - - // Skip the leaf list data - int count = buf.GetInt(); - buf.SeekGet( CUtlBuffer::SEEK_CURRENT, count * sizeof(StaticPropLeafLump_t) ); - - UnserializeModels( buf ); - } -} - -//----------------------------------------------------------------------------- -// Level init, shutdown -//----------------------------------------------------------------------------- - -void CVradStaticPropMgr::Init() -{ - CreateInterfaceFn physicsFactory = GetPhysicsFactory(); - if ( !physicsFactory ) - Error( "Unable to load vphysics DLL." ); - - s_pPhysCollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); - if( !s_pPhysCollision ) - { - Error( "Unable to get '%s' for physics interface.", VPHYSICS_COLLISION_INTERFACE_VERSION ); - return; - } - - // Read in static props that have been compiled into the bsp file - UnserializeStaticProps(); -} - -void CVradStaticPropMgr::Shutdown() -{ - - // Remove all static prop model data - for (int i = m_StaticPropDict.Size(); --i >= 0; ) - { - studiohdr_t *pStudioHdr = m_StaticPropDict[i].m_pStudioHdr; - if ( pStudioHdr ) - { - if ( pStudioHdr->pVertexBase ) - { - free( pStudioHdr->pVertexBase ); - } - free( pStudioHdr ); - } - } - - m_StaticProps.Purge(); - m_StaticPropDict.Purge(); -} - -void ComputeLightmapColor( dface_t* pFace, Vector &color ) -{ - texinfo_t* pTex = &texinfo[pFace->texinfo]; - if ( pTex->flags & SURF_SKY ) - { - // sky ambient already accounted for in direct component - return; - } -} - -bool PositionInSolid( Vector &position ) -{ - int ndxLeaf = PointLeafnum( position ); - if ( dleafs[ndxLeaf].contents & CONTENTS_SOLID ) - { - // position embedded in solid - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -// Trace from a vertex to each direct light source, accumulating its contribution. -//----------------------------------------------------------------------------- -void ComputeDirectLightingAtPoint( Vector &position, Vector &normal, Vector &outColor, int iThread, - int static_prop_id_to_skip=-1, int nLFlags = 0) -{ - SSE_sampleLightOutput_t sampleOutput; - - outColor.Init(); - - // Iterate over all direct lights and accumulate their contribution - int cluster = ClusterFromPoint( position ); - for ( directlight_t *dl = activelights; dl != NULL; dl = dl->next ) - { - if ( dl->light.style ) - { - // skip lights with style - continue; - } - - // is this lights cluster visible? - if ( !PVSCheck( dl->pvs, cluster ) ) - continue; - - // push the vertex towards the light to avoid surface acne - Vector adjusted_pos = position; - float flEpsilon = 0.0; - - if (dl->light.type != emit_skyambient) - { - // push towards the light - Vector fudge; - if ( dl->light.type == emit_skylight ) - fudge = -( dl->light.normal); - else - { - fudge = dl->light.origin-position; - VectorNormalize( fudge ); - } - fudge *= 4.0; - adjusted_pos += fudge; - } - else - { - // push out along normal - adjusted_pos += 4.0 * normal; -// flEpsilon = 1.0; - } - - FourVectors adjusted_pos4; - FourVectors normal4; - adjusted_pos4.DuplicateVector( adjusted_pos ); - normal4.DuplicateVector( normal ); - - GatherSampleLightSSE( sampleOutput, dl, -1, adjusted_pos4, &normal4, 1, iThread, nLFlags | GATHERLFLAGS_FORCE_FAST, - static_prop_id_to_skip, flEpsilon ); - - VectorMA( outColor, sampleOutput.m_flFalloff.m128_f32[0] * sampleOutput.m_flDot[0].m128_f32[0], dl->light.intensity, outColor ); - } -} - -//----------------------------------------------------------------------------- -// Takes the results from a ComputeLighting call and applies it to the static prop in question. -//----------------------------------------------------------------------------- -void CVradStaticPropMgr::ApplyLightingToStaticProp( CStaticProp &prop, const CComputeStaticPropLightingResults *pResults ) -{ - if ( pResults->m_ColorVertsArrays.Count() == 0 ) - return; - - StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; - studiohdr_t *pStudioHdr = dict.m_pStudioHdr; - OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); - Assert( pStudioHdr && pVtxHdr ); - - int iCurColorVertsArray = 0; - for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) - { - OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart( bodyID ); - mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); - - for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) - { - OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel( modelID ); - mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); - - const CUtlVector &colorVerts = *pResults->m_ColorVertsArrays[iCurColorVertsArray++]; - - for ( int nLod = 0; nLod < pVtxHdr->numLODs; nLod++ ) - { - OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLod ); - - for ( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh ) - { - mstudiomesh_t* pMesh = pStudioModel->pMesh( nMesh ); - OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh( nMesh ); - - for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup ) - { - OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup( nGroup ); - int nMeshIdx = prop.m_MeshData.AddToTail(); - prop.m_MeshData[nMeshIdx].m_Verts.AddMultipleToTail( pStripGroup->numVerts ); - prop.m_MeshData[nMeshIdx].m_nLod = nLod; - - for ( int nVertex = 0; nVertex < pStripGroup->numVerts; ++nVertex ) - { - int nIndex = pMesh->vertexoffset + pStripGroup->pVertex( nVertex )->origMeshVertID; - - Assert( nIndex < pStudioModel->numvertices ); - prop.m_MeshData[nMeshIdx].m_Verts[nVertex] = colorVerts[nIndex].m_Color; - } - } - } - } - } - } -} - -//----------------------------------------------------------------------------- -// Trace rays from each unique vertex, accumulating direct and indirect -// sources at each ray termination. Use the winding data to distribute the unique vertexes -// into the rendering layout. -//----------------------------------------------------------------------------- -void CVradStaticPropMgr::ComputeLighting( CStaticProp &prop, int iThread, int prop_index, CComputeStaticPropLightingResults *pResults ) -{ - CUtlVector badVerts; - - StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; - studiohdr_t *pStudioHdr = dict.m_pStudioHdr; - OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); - if ( !pStudioHdr || !pVtxHdr ) - { - // must have model and its verts for lighting computation - // game will fallback to fullbright - return; - } - - if (prop.m_Flags & STATIC_PROP_NO_PER_VERTEX_LIGHTING ) - return; - - VMPI_SetCurrentStage( "ComputeLighting" ); - - for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) - { - mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); - - for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) - { - mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); - - // light all unique vertexes - CUtlVector *pColorVertsArray = new CUtlVector; - pResults->m_ColorVertsArrays.AddToTail( pColorVertsArray ); - - CUtlVector &colorVerts = *pColorVertsArray; - colorVerts.EnsureCount( pStudioModel->numvertices ); - memset( colorVerts.Base(), 0, colorVerts.Count() * sizeof(colorVertex_t) ); - - int numVertexes = 0; - for ( int meshID = 0; meshID < pStudioModel->nummeshes; ++meshID ) - { - mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID ); - const mstudio_meshvertexdata_t *vertData = pStudioMesh->GetVertexData((void *)pStudioHdr); - Assert( vertData ); // This can only return NULL on X360 for now - for ( int vertexID = 0; vertexID < pStudioMesh->numvertices; ++vertexID ) - { - Vector sampleNormal; - Vector samplePosition; - // transform position and normal into world coordinate system - matrix3x4_t matrix; - AngleMatrix( prop.m_Angles, prop.m_Origin, matrix ); - VectorTransform( *vertData->Position( vertexID ), matrix, samplePosition ); - AngleMatrix( prop.m_Angles, matrix ); - VectorTransform( *vertData->Normal( vertexID ), matrix, sampleNormal ); - - if ( PositionInSolid( samplePosition ) ) - { - // vertex is in solid, add to the bad list, and recover later - badVertex_t badVertex; - badVertex.m_ColorVertex = numVertexes; - badVertex.m_Position = samplePosition; - badVertex.m_Normal = sampleNormal; - badVerts.AddToTail( badVertex ); - } - else - { - Vector direct_pos=samplePosition; - int skip_prop = -1; - if ( g_bDisablePropSelfShadowing || ( prop.m_Flags & STATIC_PROP_NO_SELF_SHADOWING ) ) - { - skip_prop = prop_index; - } - - int nFlags = ( prop.m_Flags & STATIC_PROP_IGNORE_NORMALS ) ? GATHERLFLAGS_IGNORE_NORMALS : 0; - - Vector directColor(0,0,0); - ComputeDirectLightingAtPoint( direct_pos, - sampleNormal, directColor, iThread, - skip_prop, nFlags ); - Vector indirectColor(0,0,0); - - if (g_bShowStaticPropNormals) - { - directColor= sampleNormal; - directColor += Vector(1.0,1.0,1.0); - directColor *= 50.0; - } - else - { - if (numbounce >= 1) - ComputeIndirectLightingAtPoint( - samplePosition, sampleNormal, - indirectColor, iThread, true, - ( prop.m_Flags & STATIC_PROP_IGNORE_NORMALS) != 0 ); - } - - colorVerts[numVertexes].m_bValid = true; - colorVerts[numVertexes].m_Position = samplePosition; - VectorAdd( directColor, indirectColor, colorVerts[numVertexes].m_Color ); - } - - numVertexes++; - } - } - - // color in the bad vertexes - // when entire model has no lighting origin and no valid neighbors - // must punt, leave black coloring - if ( badVerts.Count() && ( prop.m_bLightingOriginValid || badVerts.Count() != numVertexes ) ) - { - for ( int nBadVertex = 0; nBadVertex < badVerts.Count(); nBadVertex++ ) - { - Vector bestPosition; - if ( prop.m_bLightingOriginValid ) - { - // use the specified lighting origin - VectorCopy( prop.m_LightingOrigin, bestPosition ); - } - else - { - // find the closest valid neighbor - int best = 0; - float closest = FLT_MAX; - for ( int nColorVertex = 0; nColorVertex < numVertexes; nColorVertex++ ) - { - if ( !colorVerts[nColorVertex].m_bValid ) - { - // skip invalid neighbors - continue; - } - Vector delta; - VectorSubtract( colorVerts[nColorVertex].m_Position, badVerts[nBadVertex].m_Position, delta ); - float distance = VectorLength( delta ); - if ( distance < closest ) - { - closest = distance; - best = nColorVertex; - } - } - - // use the best neighbor as the direction to crawl - VectorCopy( colorVerts[best].m_Position, bestPosition ); - } - - // crawl toward best position - // sudivide to determine a closer valid point to the bad vertex, and re-light - Vector midPosition; - int numIterations = 20; - while ( --numIterations > 0 ) - { - VectorAdd( bestPosition, badVerts[nBadVertex].m_Position, midPosition ); - VectorScale( midPosition, 0.5f, midPosition ); - if ( PositionInSolid( midPosition ) ) - break; - bestPosition = midPosition; - } - - // re-light from better position - Vector directColor; - ComputeDirectLightingAtPoint( bestPosition, badVerts[nBadVertex].m_Normal, directColor, iThread ); - - Vector indirectColor; - ComputeIndirectLightingAtPoint( bestPosition, badVerts[nBadVertex].m_Normal, - indirectColor, iThread, true ); - - // save results, not changing valid status - // to ensure this offset position is not considered as a viable candidate - colorVerts[badVerts[nBadVertex].m_ColorVertex].m_Position = bestPosition; - VectorAdd( directColor, indirectColor, colorVerts[badVerts[nBadVertex].m_ColorVertex].m_Color ); - } - } - - // discard bad verts - badVerts.Purge(); - } - } -} - -//----------------------------------------------------------------------------- -// Write the lighitng to bsp pak lump -//----------------------------------------------------------------------------- -void CVradStaticPropMgr::SerializeLighting() -{ - char filename[MAX_PATH]; - CUtlBuffer utlBuf; - - // illuminate them all - int count = m_StaticProps.Count(); - if ( !count ) - { - // nothing to do - return; - } - - char mapName[MAX_PATH]; - Q_FileBase( source, mapName, sizeof( mapName ) ); - - int size; - for (int i = 0; i < count; ++i) - { - // no need to write this file if we didn't compute the data - // props marked this way will not load the info anyway - if ( m_StaticProps[i].m_Flags & STATIC_PROP_NO_PER_VERTEX_LIGHTING ) - continue; - - if (g_bHDR) - { - sprintf( filename, "sp_hdr_%d.vhv", i ); - } - else - { - sprintf( filename, "sp_%d.vhv", i ); - } - - int totalVertexes = 0; - for ( int j=0; jm_nVersion = VHV_VERSION; - pVhvHdr->m_nChecksum = m_StaticPropDict[m_StaticProps[i].m_ModelIdx].m_pStudioHdr->checksum; - pVhvHdr->m_nVertexFlags = VERTEX_COLOR; - pVhvHdr->m_nVertexSize = 4; - pVhvHdr->m_nVertexes = totalVertexes; - pVhvHdr->m_nMeshes = m_StaticProps[i].m_MeshData.Count(); - - for (int n=0; nm_nMeshes; n++) - { - // construct mesh dictionary - HardwareVerts::MeshHeader_t *pMesh = pVhvHdr->pMesh( n ); - pMesh->m_nLod = m_StaticProps[i].m_MeshData[n].m_nLod; - pMesh->m_nVertexes = m_StaticProps[i].m_MeshData[n].m_Verts.Count(); - pMesh->m_nOffset = (unsigned int)pVertexData - (unsigned int)pVhvHdr; - - // construct vertexes - for (int k=0; km_nVertexes; k++) - { - Vector &vector = m_StaticProps[i].m_MeshData[n].m_Verts[k]; - - ColorRGBExp32 rgbColor; - VectorToColorRGBExp32( vector, rgbColor ); - unsigned char dstColor[4]; - ConvertRGBExp32ToRGBA8888( &rgbColor, dstColor ); - - // b,g,r,a order - pVertexData[0] = dstColor[2]; - pVertexData[1] = dstColor[1]; - pVertexData[2] = dstColor[0]; - pVertexData[3] = dstColor[3]; - pVertexData += 4; - } - } - - // align to end of file - pVertexData = (unsigned char *)((unsigned int)pVertexData - (unsigned int)pVhvHdr); - pVertexData = (unsigned char*)pVhvHdr + ALIGN_TO_POW2( (unsigned int)pVertexData, 512 ); - - AddBufferToPak( GetPakFile(), filename, (void*)pVhvHdr, pVertexData - (unsigned char*)pVhvHdr, false ); - } -} - -void CVradStaticPropMgr::VMPI_ProcessStaticProp_Static( int iThread, uint64 iStaticProp, MessageBuffer *pBuf ) -{ - g_StaticPropMgr.VMPI_ProcessStaticProp( iThread, iStaticProp, pBuf ); -} - -void CVradStaticPropMgr::VMPI_ReceiveStaticPropResults_Static( uint64 iStaticProp, MessageBuffer *pBuf, int iWorker ) -{ - g_StaticPropMgr.VMPI_ReceiveStaticPropResults( iStaticProp, pBuf, iWorker ); -} - -//----------------------------------------------------------------------------- -// Called on workers to do the computation for a static prop and send -// it to the master. -//----------------------------------------------------------------------------- -void CVradStaticPropMgr::VMPI_ProcessStaticProp( int iThread, int iStaticProp, MessageBuffer *pBuf ) -{ - // Compute the lighting. - CComputeStaticPropLightingResults results; - ComputeLighting( m_StaticProps[iStaticProp], iThread, iStaticProp, &results ); - - VMPI_SetCurrentStage( "EncodeLightingResults" ); - - // Encode the results. - int nLists = results.m_ColorVertsArrays.Count(); - pBuf->write( &nLists, sizeof( nLists ) ); - - for ( int i=0; i < nLists; i++ ) - { - CUtlVector &curList = *results.m_ColorVertsArrays[i]; - int count = curList.Count(); - pBuf->write( &count, sizeof( count ) ); - pBuf->write( curList.Base(), curList.Count() * sizeof( colorVertex_t ) ); - } -} - -//----------------------------------------------------------------------------- -// Called on the master when a worker finishes processing a static prop. -//----------------------------------------------------------------------------- -void CVradStaticPropMgr::VMPI_ReceiveStaticPropResults( int iStaticProp, MessageBuffer *pBuf, int iWorker ) -{ - // Read in the results. - CComputeStaticPropLightingResults results; - - int nLists; - pBuf->read( &nLists, sizeof( nLists ) ); - - for ( int i=0; i < nLists; i++ ) - { - CUtlVector *pList = new CUtlVector; - results.m_ColorVertsArrays.AddToTail( pList ); - - int count; - pBuf->read( &count, sizeof( count ) ); - pList->SetSize( count ); - pBuf->read( pList->Base(), count * sizeof( colorVertex_t ) ); - } - - // Apply the results. - ApplyLightingToStaticProp( m_StaticProps[iStaticProp], &results ); -} - - -void CVradStaticPropMgr::ComputeLightingForProp( int iThread, int iStaticProp ) -{ - // Compute the lighting. - CComputeStaticPropLightingResults results; - ComputeLighting( m_StaticProps[iStaticProp], iThread, iStaticProp, &results ); - ApplyLightingToStaticProp( m_StaticProps[iStaticProp], &results ); -} - -void CVradStaticPropMgr::ThreadComputeStaticPropLighting( int iThread, void *pUserData ) -{ - while (1) - { - int j = GetThreadWork (); - if (j == -1) - break; - CComputeStaticPropLightingResults results; - g_StaticPropMgr.ComputeLightingForProp( iThread, j ); - } -} - -//----------------------------------------------------------------------------- -// Computes lighting for the static props. -// Must be after all other surface lighting has been computed for the indirect sampling. -//----------------------------------------------------------------------------- -void CVradStaticPropMgr::ComputeLighting( int iThread ) -{ - // illuminate them all - int count = m_StaticProps.Count(); - if ( !count ) - { - // nothing to do - return; - } - - StartPacifier( "Computing static prop lighting : " ); - - // ensure any traces against us are ignored because we have no inherit lighting contribution - m_bIgnoreStaticPropTrace = true; - - if ( g_bUseMPI ) - { - // Distribute the work among the workers. - VMPI_SetCurrentStage( "CVradStaticPropMgr::ComputeLighting" ); - - DistributeWork( - count, - VMPI_DISTRIBUTEWORK_PACKETID, - &CVradStaticPropMgr::VMPI_ProcessStaticProp_Static, - &CVradStaticPropMgr::VMPI_ReceiveStaticPropResults_Static ); - } - else - { - RunThreadsOn(count, true, ThreadComputeStaticPropLighting); - } - - // restore default - m_bIgnoreStaticPropTrace = false; - - // save data to bsp - SerializeLighting(); - - EndPacifier( true ); -} - -//----------------------------------------------------------------------------- -// Adds all static prop polys to the ray trace store. -//----------------------------------------------------------------------------- -void CVradStaticPropMgr::AddPolysForRayTrace( void ) -{ - int count = m_StaticProps.Count(); - if ( !count ) - { - // nothing to do - return; - } - - // Triangle coverage of 1 (full coverage) - Vector fullCoverage; - fullCoverage.x = 1.0f; - - for ( int nProp = 0; nProp < count; ++nProp ) - { - CStaticProp &prop = m_StaticProps[nProp]; - StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; - - if ( prop.m_Flags & STATIC_PROP_NO_SHADOW ) - continue; - - // If not using static prop polys, use AABB - if ( !g_bStaticPropPolys ) - { - if ( dict.m_pModel ) - { - VMatrix xform; - xform.SetupMatrixOrgAngles ( prop.m_Origin, prop.m_Angles ); - ICollisionQuery *queryModel = s_pPhysCollision->CreateQueryModel( dict.m_pModel ); - for ( int nConvex = 0; nConvex < queryModel->ConvexCount(); ++nConvex ) - { - for ( int nTri = 0; nTri < queryModel->TriangleCount( nConvex ); ++nTri ) - { - Vector verts[3]; - queryModel->GetTriangleVerts( nConvex, nTri, verts ); - for ( int nVert = 0; nVert < 3; ++nVert ) - verts[nVert] = xform.VMul4x3(verts[nVert]); - g_RtEnv.AddTriangle ( TRACE_ID_STATICPROP | nProp, verts[0], verts[1], verts[2], fullCoverage ); - } - } - s_pPhysCollision->DestroyQueryModel( queryModel ); - } - else - { - VectorAdd ( dict.m_Mins, prop.m_Origin, prop.m_mins ); - VectorAdd ( dict.m_Maxs, prop.m_Origin, prop.m_maxs ); - g_RtEnv.AddAxisAlignedRectangularSolid ( TRACE_ID_STATICPROP | nProp, prop.m_mins, prop.m_maxs, fullCoverage ); - } - - continue; - } - - studiohdr_t *pStudioHdr = dict.m_pStudioHdr; - OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); - if ( !pStudioHdr || !pVtxHdr ) - { - // must have model and its verts for decoding triangles - return; - } - // only init the triangle table the first time - bool bInitTriangles = dict.m_triangleMaterialIndex.Count() ? false : true; - int triangleIndex = 0; - - // meshes are deeply hierarchial, divided between three stores, follow the white rabbit - // body parts -> models -> lod meshes -> strip groups -> strips - // the vertices and indices are pooled, the trick is knowing the offset to determine your indexed base - for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) - { - OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart( bodyID ); - mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); - - for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) - { - OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel( modelID ); - mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); - - // assuming lod 0, could iterate if required - int nLod = 0; - OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLod ); - - for ( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh ) - { - // check if this mesh's material is in the no shadow material name list - mstudiomesh_t* pMesh = pStudioModel->pMesh( nMesh ); - mstudiotexture_t *pTxtr=pStudioHdr->pTexture(pMesh->material); - //printf("mat idx=%d mat name=%s\n",pMesh->material,pTxtr->pszName()); - bool bSkipThisMesh = false; - for(int check=0; checkpszName(), - g_NonShadowCastingMaterialStrings[check] ) ) - { - //printf("skip mat name=%s\n",pTxtr->pszName()); - bSkipThisMesh = true; - break; - } - } - if ( bSkipThisMesh) - continue; - - int shadowTextureIndex = -1; - if ( dict.m_textureShadowIndex.Count() ) - { - shadowTextureIndex = dict.m_textureShadowIndex[pMesh->material]; - } - - - OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh( nMesh ); - const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData( (void *)pStudioHdr ); - Assert( vertData ); // This can only return NULL on X360 for now - - for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup ) - { - OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup( nGroup ); - - int nStrip; - for ( nStrip = 0; nStrip < pStripGroup->numStrips; nStrip++ ) - { - OptimizedModel::StripHeader_t *pStrip = pStripGroup->pStrip( nStrip ); - - if ( pStrip->flags & OptimizedModel::STRIP_IS_TRILIST ) - { - for ( int i = 0; i < pStrip->numIndices; i += 3 ) - { - int idx = pStrip->indexOffset + i; - - unsigned short i1 = *pStripGroup->pIndex( idx ); - unsigned short i2 = *pStripGroup->pIndex( idx + 1 ); - unsigned short i3 = *pStripGroup->pIndex( idx + 2 ); - - int vertex1 = pStripGroup->pVertex( i1 )->origMeshVertID; - int vertex2 = pStripGroup->pVertex( i2 )->origMeshVertID; - int vertex3 = pStripGroup->pVertex( i3 )->origMeshVertID; - - // transform position into world coordinate system - matrix3x4_t matrix; - AngleMatrix( prop.m_Angles, prop.m_Origin, matrix ); - - Vector position1; - Vector position2; - Vector position3; - VectorTransform( *vertData->Position( vertex1 ), matrix, position1 ); - VectorTransform( *vertData->Position( vertex2 ), matrix, position2 ); - VectorTransform( *vertData->Position( vertex3 ), matrix, position3 ); - unsigned short flags = 0; - int materialIndex = -1; - Vector color = vec3_origin; - if ( shadowTextureIndex >= 0 ) - { - if ( bInitTriangles ) - { - // add texture space and texture index to material database - // now - float coverage = g_ShadowTextureList.ComputeCoverageForTriangle(shadowTextureIndex, *vertData->Texcoord(vertex1), *vertData->Texcoord(vertex2), *vertData->Texcoord(vertex3) ); - if ( coverage < 1.0f ) - { - materialIndex = g_ShadowTextureList.AddMaterialEntry( shadowTextureIndex, *vertData->Texcoord(vertex1), *vertData->Texcoord(vertex2), *vertData->Texcoord(vertex3) ); - color.x = coverage; - } - else - { - materialIndex = -1; - } - dict.m_triangleMaterialIndex.AddToTail(materialIndex); - } - else - { - materialIndex = dict.m_triangleMaterialIndex[triangleIndex]; - triangleIndex++; - } - if ( materialIndex >= 0 ) - { - flags = FCACHETRI_TRANSPARENT; - } - } -// printf( "\ngl 3\n" ); -// printf( "gl %6.3f %6.3f %6.3f 1 0 0\n", XYZ(position1)); -// printf( "gl %6.3f %6.3f %6.3f 0 1 0\n", XYZ(position2)); -// printf( "gl %6.3f %6.3f %6.3f 0 0 1\n", XYZ(position3)); - g_RtEnv.AddTriangle( TRACE_ID_STATICPROP | nProp, - position1, position2, position3, - color, flags, materialIndex); - } - } - else - { - // all tris expected to be discrete tri lists - // must fixme if stripping ever occurs - printf( "unexpected strips found\n" ); - Assert( 0 ); - return; - } - } - } - } - } - } - } -} - -struct tl_tri_t -{ - Vector p0; - Vector p1; - Vector p2; - Vector n0; - Vector n1; - Vector n2; - - bool operator == (const tl_tri_t &t) const - { - return ( p0 == t.p0 && - p1 == t.p1 && - p2 == t.p2 && - n0 == t.n0 && - n1 == t.n1 && - n2 == t.n2 ); - } -}; - -struct tl_vert_t -{ - Vector m_position; - CUtlLinkedList< tl_tri_t, int > m_triList; -}; - -void AddTriVertsToList( CUtlVector< tl_vert_t > &triListVerts, int vertIndex, Vector vertPosition, Vector p0, Vector p1, Vector p2, Vector n0, Vector n1, Vector n2 ) -{ - tl_tri_t tlTri; - - tlTri.p0 = p0; - tlTri.p1 = p1; - tlTri.p2 = p2; - tlTri.n0 = n0; - tlTri.n1 = n1; - tlTri.n2 = n2; - - triListVerts.EnsureCapacity( vertIndex+1 ); - - triListVerts[vertIndex].m_position = vertPosition; - - int index = triListVerts[vertIndex].m_triList.Find( tlTri ); - if ( !triListVerts[vertIndex].m_triList.IsValidIndex( index ) ) - { - // not in list, add to list of triangles - triListVerts[vertIndex].m_triList.AddToTail( tlTri ); - } -} - -//----------------------------------------------------------------------------- -// Builds a list of tris for every vertex -//----------------------------------------------------------------------------- -void CVradStaticPropMgr::BuildTriList( CStaticProp &prop ) -{ - // the generated list will consist of a list of verts - // each vert will have a linked list of triangles that it belongs to - CUtlVector< tl_vert_t > triListVerts; - - StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; - studiohdr_t *pStudioHdr = dict.m_pStudioHdr; - OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); - if ( !pStudioHdr || !pVtxHdr ) - { - // must have model and its verts for decoding triangles - return; - } - - // meshes are deeply hierarchial, divided between three stores, follow the white rabbit - // body parts -> models -> lod meshes -> strip groups -> strips - // the vertices and indices are pooled, the trick is knowing the offset to determine your indexed base - for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) - { - OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart( bodyID ); - mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); - - for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) - { - OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel( modelID ); - mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); - - // get the specified lod, assuming lod 0 - int nLod = 0; - OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLod ); - - // must reset because each model has their own vertexes [0..n] - // in order for this to be monolithic for the entire prop the list must be segmented - triListVerts.Purge(); - - for ( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh ) - { - mstudiomesh_t* pMesh = pStudioModel->pMesh( nMesh ); - OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh( nMesh ); - const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData( (void *)pStudioHdr ); - Assert( vertData ); // This can only return NULL on X360 for now - - for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup ) - { - OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup( nGroup ); - - int nStrip; - for ( nStrip = 0; nStrip < pStripGroup->numStrips; nStrip++ ) - { - OptimizedModel::StripHeader_t *pStrip = pStripGroup->pStrip( nStrip ); - - if ( pStrip->flags & OptimizedModel::STRIP_IS_TRILIST ) - { - for ( int i = 0; i < pStrip->numIndices; i += 3 ) - { - int idx = pStrip->indexOffset + i; - - unsigned short i1 = *pStripGroup->pIndex( idx ); - unsigned short i2 = *pStripGroup->pIndex( idx + 1 ); - unsigned short i3 = *pStripGroup->pIndex( idx + 2 ); - - int vertex1 = pStripGroup->pVertex( i1 )->origMeshVertID; - int vertex2 = pStripGroup->pVertex( i2 )->origMeshVertID; - int vertex3 = pStripGroup->pVertex( i3 )->origMeshVertID; - - // transform position into world coordinate system - matrix3x4_t matrix; - AngleMatrix( prop.m_Angles, prop.m_Origin, matrix ); - - Vector position1; - Vector position2; - Vector position3; - VectorTransform( *vertData->Position( vertex1 ), matrix, position1 ); - VectorTransform( *vertData->Position( vertex2 ), matrix, position2 ); - VectorTransform( *vertData->Position( vertex3 ), matrix, position3 ); - - Vector normal1; - Vector normal2; - Vector normal3; - VectorTransform( *vertData->Normal( vertex1 ), matrix, normal1 ); - VectorTransform( *vertData->Normal( vertex2 ), matrix, normal2 ); - VectorTransform( *vertData->Normal( vertex3 ), matrix, normal3 ); - - AddTriVertsToList( triListVerts, pMesh->vertexoffset + vertex1, position1, position1, position2, position3, normal1, normal2, normal3 ); - AddTriVertsToList( triListVerts, pMesh->vertexoffset + vertex2, position2, position1, position2, position3, normal1, normal2, normal3 ); - AddTriVertsToList( triListVerts, pMesh->vertexoffset + vertex3, position3, position1, position2, position3, normal1, normal2, normal3 ); - } - } - else - { - // all tris expected to be discrete tri lists - // must fixme if stripping ever occurs - printf( "unexpected strips found\n" ); - Assert( 0 ); - return; - } - } - } - } - } - } -} - -const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void *pModelData ) -{ - studiohdr_t *pActiveStudioHdr = static_cast(pModelData); - Assert( pActiveStudioHdr ); - - if ( pActiveStudioHdr->pVertexBase ) - { - return (vertexFileHeader_t *)pActiveStudioHdr->pVertexBase; - } - - // mandatory callback to make requested data resident - // load and persist the vertex file - char fileName[MAX_PATH]; - strcpy( fileName, "models/" ); - strcat( fileName, pActiveStudioHdr->pszName() ); - Q_StripExtension( fileName, fileName, sizeof( fileName ) ); - strcat( fileName, ".vvd" ); - - // load the model - FileHandle_t fileHandle = g_pFileSystem->Open( fileName, "rb" ); - if ( !fileHandle ) - { - Error( "Unable to load vertex data \"%s\"\n", fileName ); - } - - // Get the file size - int vvdSize = g_pFileSystem->Size( fileHandle ); - if ( vvdSize == 0 ) - { - g_pFileSystem->Close( fileHandle ); - Error( "Bad size for vertex data \"%s\"\n", fileName ); - } - - vertexFileHeader_t *pVvdHdr = (vertexFileHeader_t *)malloc( vvdSize ); - g_pFileSystem->Read( pVvdHdr, vvdSize, fileHandle ); - g_pFileSystem->Close( fileHandle ); - - // check header - if ( pVvdHdr->id != MODEL_VERTEX_FILE_ID ) - { - Error("Error Vertex File %s id %d should be %d\n", fileName, pVvdHdr->id, MODEL_VERTEX_FILE_ID); - } - if ( pVvdHdr->version != MODEL_VERTEX_FILE_VERSION ) - { - Error("Error Vertex File %s version %d should be %d\n", fileName, pVvdHdr->version, MODEL_VERTEX_FILE_VERSION); - } - if ( pVvdHdr->checksum != pActiveStudioHdr->checksum ) - { - Error("Error Vertex File %s checksum %d should be %d\n", fileName, pVvdHdr->checksum, pActiveStudioHdr->checksum); - } - - // need to perform mesh relocation fixups - // allocate a new copy - vertexFileHeader_t *pNewVvdHdr = (vertexFileHeader_t *)malloc( vvdSize ); - if ( !pNewVvdHdr ) - { - Error( "Error allocating %d bytes for Vertex File '%s'\n", vvdSize, fileName ); - } - - // load vertexes and run fixups - Studio_LoadVertexes( pVvdHdr, pNewVvdHdr, 0, true ); - - // discard original - free( pVvdHdr ); - pVvdHdr = pNewVvdHdr; - - pActiveStudioHdr->pVertexBase = (void*)pVvdHdr; - return pVvdHdr; -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Revision: $ +// $NoKeywords: $ +// +// This file contains code to allow us to associate client data with bsp leaves. +// +//=============================================================================// + +#include "vrad.h" +#include "mathlib/vector.h" +#include "UtlBuffer.h" +#include "utlvector.h" +#include "GameBSPFile.h" +#include "BSPTreeData.h" +#include "VPhysics_Interface.h" +#include "Studio.h" +#include "Optimize.h" +#include "Bsplib.h" +#include "CModel.h" +#include "PhysDll.h" +#include "phyfile.h" +#include "collisionutils.h" +#include "tier1/KeyValues.h" +#include "pacifier.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/hardwareverts.h" +#include "byteswap.h" +#include "mpivrad.h" +#include "vtf/vtf.h" +#include "tier1/utldict.h" +#include "tier1/utlsymbol.h" + +#include "messbuf.h" +#include "vmpi.h" +#include "vmpi_distribute_work.h" + + +#define ALIGN_TO_POW2(x,y) (((x)+(y-1))&~(y-1)) + +// identifies a vertex embedded in solid +// lighting will be copied from nearest valid neighbor +struct badVertex_t +{ + int m_ColorVertex; + Vector m_Position; + Vector m_Normal; +}; + +// a final colored vertex +struct colorVertex_t +{ + Vector m_Color; + Vector m_Position; + bool m_bValid; +}; + +class CComputeStaticPropLightingResults +{ +public: + ~CComputeStaticPropLightingResults() + { + m_ColorVertsArrays.PurgeAndDeleteElements(); + } + + CUtlVector< CUtlVector* > m_ColorVertsArrays; +}; + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- +CUtlSymbolTable g_ForcedTextureShadowsModels; + +// DON'T USE THIS FROM WITHIN A THREAD. THERE IS A THREAD CONTEXT CREATED +// INSIDE PropTested_t. USE THAT INSTEAD. +IPhysicsCollision *s_pPhysCollision = NULL; + +//----------------------------------------------------------------------------- +// Vrad's static prop manager +//----------------------------------------------------------------------------- + +class CVradStaticPropMgr : public IVradStaticPropMgr +{ +public: + // constructor, destructor + CVradStaticPropMgr(); + virtual ~CVradStaticPropMgr(); + + // methods of IStaticPropMgr + void Init(); + void Shutdown(); + + // iterate all the instanced static props and compute their vertex lighting + void ComputeLighting( int iThread ); + +private: + // VMPI stuff. + static void VMPI_ProcessStaticProp_Static( int iThread, uint64 iStaticProp, MessageBuffer *pBuf ); + static void VMPI_ReceiveStaticPropResults_Static( uint64 iStaticProp, MessageBuffer *pBuf, int iWorker ); + void VMPI_ProcessStaticProp( int iThread, int iStaticProp, MessageBuffer *pBuf ); + void VMPI_ReceiveStaticPropResults( int iStaticProp, MessageBuffer *pBuf, int iWorker ); + + // local thread version + static void ThreadComputeStaticPropLighting( int iThread, void *pUserData ); + void ComputeLightingForProp( int iThread, int iStaticProp ); + + // Methods associated with unserializing static props + void UnserializeModelDict( CUtlBuffer& buf ); + void UnserializeModels( CUtlBuffer& buf ); + void UnserializeStaticProps(); + + // Creates a collision model + void CreateCollisionModel( char const* pModelName ); + +private: + // Unique static prop models + struct StaticPropDict_t + { + vcollide_t m_loadedModel; + CPhysCollide* m_pModel; + Vector m_Mins; // Bounding box is in local coordinates + Vector m_Maxs; + studiohdr_t* m_pStudioHdr; + CUtlBuffer m_VtxBuf; + CUtlVector m_textureShadowIndex; // each texture has an index if this model casts texture shadows + CUtlVector m_triangleMaterialIndex;// each triangle has an index if this model casts texture shadows + }; + + struct MeshData_t + { + CUtlVector m_Verts; + int m_nLod; + }; + + // A static prop instance + struct CStaticProp + { + Vector m_Origin; + QAngle m_Angles; + Vector m_mins; + Vector m_maxs; + Vector m_LightingOrigin; + int m_ModelIdx; + BSPTreeDataHandle_t m_Handle; + CUtlVector m_MeshData; + int m_Flags; + bool m_bLightingOriginValid; + }; + + // Enumeration context + struct EnumContext_t + { + PropTested_t* m_pPropTested; + Ray_t const* m_pRay; + }; + + // The list of all static props + CUtlVector m_StaticPropDict; + CUtlVector m_StaticProps; + + bool m_bIgnoreStaticPropTrace; + + void ComputeLighting( CStaticProp &prop, int iThread, int prop_index, CComputeStaticPropLightingResults *pResults ); + void ApplyLightingToStaticProp( CStaticProp &prop, const CComputeStaticPropLightingResults *pResults ); + + void SerializeLighting(); + void AddPolysForRayTrace(); + void BuildTriList( CStaticProp &prop ); +}; + + +//----------------------------------------------------------------------------- +// Expose IVradStaticPropMgr to vrad +//----------------------------------------------------------------------------- + +static CVradStaticPropMgr g_StaticPropMgr; +IVradStaticPropMgr* StaticPropMgr() +{ + return &g_StaticPropMgr; +} + + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +CVradStaticPropMgr::CVradStaticPropMgr() +{ + // set to ignore static prop traces + m_bIgnoreStaticPropTrace = false; +} + +CVradStaticPropMgr::~CVradStaticPropMgr() +{ +} + +//----------------------------------------------------------------------------- +// Makes sure the studio model is a static prop +//----------------------------------------------------------------------------- + +bool IsStaticProp( studiohdr_t* pHdr ) +{ + if (!(pHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP)) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Load a file into a Utlbuf +//----------------------------------------------------------------------------- +static bool LoadFile( char const* pFileName, CUtlBuffer& buf ) +{ + if ( !g_pFullFileSystem ) + return false; + + return g_pFullFileSystem->ReadFile( pFileName, NULL, buf ); +} + + +//----------------------------------------------------------------------------- +// Constructs the file name from the model name +//----------------------------------------------------------------------------- +static char const* ConstructFileName( char const* pModelName ) +{ + static char buf[1024]; + sprintf( buf, "%s%s", gamedir, pModelName ); + return buf; +} + + +//----------------------------------------------------------------------------- +// Computes a convex hull from a studio mesh +//----------------------------------------------------------------------------- +static CPhysConvex* ComputeConvexHull( mstudiomesh_t* pMesh, studiohdr_t *pStudioHdr ) +{ + const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData( (void *)pStudioHdr ); + Assert( vertData ); // This can only return NULL on X360 for now + + // Generate a list of all verts in the mesh + Vector** ppVerts = (Vector**)_alloca(pMesh->numvertices * sizeof(Vector*) ); + for (int i = 0; i < pMesh->numvertices; ++i) + { + ppVerts[i] = vertData->Position(i); + } + + // Generate a convex hull from the verts + return s_pPhysCollision->ConvexFromVerts( ppVerts, pMesh->numvertices ); +} + + +//----------------------------------------------------------------------------- +// Computes a convex hull from the studio model +//----------------------------------------------------------------------------- +CPhysCollide* ComputeConvexHull( studiohdr_t* pStudioHdr ) +{ + CUtlVector convexHulls; + + for (int body = 0; body < pStudioHdr->numbodyparts; ++body ) + { + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( body ); + for( int model = 0; model < pBodyPart->nummodels; ++model ) + { + mstudiomodel_t *pStudioModel = pBodyPart->pModel( model ); + for( int mesh = 0; mesh < pStudioModel->nummeshes; ++mesh ) + { + // Make a convex hull for each mesh + // NOTE: This won't work unless the model has been compiled + // with $staticprop + mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( mesh ); + convexHulls.AddToTail( ComputeConvexHull( pStudioMesh, pStudioHdr ) ); + } + } + } + + // Convert an array of convex elements to a compiled collision model + // (this deletes the convex elements) + return s_pPhysCollision->ConvertConvexToCollide( convexHulls.Base(), convexHulls.Size() ); +} + + +//----------------------------------------------------------------------------- +// Load studio model vertex data from a file... +//----------------------------------------------------------------------------- + +bool LoadStudioModel( char const* pModelName, CUtlBuffer& buf ) +{ + // No luck, gotta build it + // Construct the file name... + if (!LoadFile( pModelName, buf )) + { + Warning("Error! Unable to load model \"%s\"\n", pModelName ); + return false; + } + + // Check that it's valid + if (strncmp ((const char *) buf.PeekGet(), "IDST", 4) && + strncmp ((const char *) buf.PeekGet(), "IDAG", 4)) + { + Warning("Error! Invalid model file \"%s\"\n", pModelName ); + return false; + } + + studiohdr_t* pHdr = (studiohdr_t*)buf.PeekGet(); + + Studio_ConvertStudioHdrToNewVersion( pHdr ); + + if (pHdr->version != STUDIO_VERSION) + { + Warning("Error! Invalid model version \"%s\"\n", pModelName ); + return false; + } + + if (!IsStaticProp(pHdr)) + { + Warning("Error! To use model \"%s\"\n" + " as a static prop, it must be compiled with $staticprop!\n", pModelName ); + return false; + } + + // ensure reset + pHdr->pVertexBase = NULL; + pHdr->pIndexBase = NULL; + + return true; +} + +bool LoadStudioCollisionModel( char const* pModelName, CUtlBuffer& buf ) +{ + char tmp[1024]; + Q_strncpy( tmp, pModelName, sizeof( tmp ) ); + Q_SetExtension( tmp, ".phy", sizeof( tmp ) ); + // No luck, gotta build it + if (!LoadFile( tmp, buf )) + { + // this is not an error, the model simply has no PHY file + return false; + } + + phyheader_t *header = (phyheader_t *)buf.PeekGet(); + + if ( header->size != sizeof(*header) || header->solidCount <= 0 ) + return false; + + return true; +} + +bool LoadVTXFile( char const* pModelName, const studiohdr_t *pStudioHdr, CUtlBuffer& buf ) +{ + char filename[MAX_PATH]; + + // construct filename + Q_StripExtension( pModelName, filename, sizeof( filename ) ); + strcat( filename, ".dx80.vtx" ); + + if ( !LoadFile( filename, buf ) ) + { + Warning( "Error! Unable to load file \"%s\"\n", filename ); + return false; + } + + OptimizedModel::FileHeader_t* pVtxHdr = (OptimizedModel::FileHeader_t *)buf.Base(); + + // Check that it's valid + if ( pVtxHdr->version != OPTIMIZED_MODEL_FILE_VERSION ) + { + Warning( "Error! Invalid VTX file version: %d, expected %d \"%s\"\n", pVtxHdr->version, OPTIMIZED_MODEL_FILE_VERSION, filename ); + return false; + } + if ( pVtxHdr->checkSum != pStudioHdr->checksum ) + { + Warning( "Error! Invalid VTX file checksum: %d, expected %d \"%s\"\n", pVtxHdr->checkSum, pStudioHdr->checksum, filename ); + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Gets a vertex position from a strip index +//----------------------------------------------------------------------------- +inline static Vector* PositionFromIndex( const mstudio_meshvertexdata_t *vertData, mstudiomesh_t* pMesh, OptimizedModel::StripGroupHeader_t* pStripGroup, int i ) +{ + OptimizedModel::Vertex_t* pVert = pStripGroup->pVertex( i ); + return vertData->Position( pVert->origMeshVertID ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Writes a glview text file containing the collision surface in question +// Input : *pCollide - +// *pFilename - +//----------------------------------------------------------------------------- +void DumpCollideToGlView( vcollide_t *pCollide, const char *pFilename ) +{ + if ( !pCollide ) + return; + + Msg("Writing %s...\n", pFilename ); + + FILE *fp = fopen( pFilename, "w" ); + for (int i = 0; i < pCollide->solidCount; ++i) + { + Vector *outVerts; + int vertCount = s_pPhysCollision->CreateDebugMesh( pCollide->solids[i], &outVerts ); + int triCount = vertCount / 3; + int vert = 0; + + unsigned char r = (i & 1) * 64 + 64; + unsigned char g = (i & 2) * 64 + 64; + unsigned char b = (i & 4) * 64 + 64; + + float fr = r / 255.0f; + float fg = g / 255.0f; + float fb = b / 255.0f; + + for ( int i = 0; i < triCount; i++ ) + { + fprintf( fp, "3\n" ); + fprintf( fp, "%6.3f %6.3f %6.3f %.2f %.3f %.3f\n", + outVerts[vert].x, outVerts[vert].y, outVerts[vert].z, fr, fg, fb ); + vert++; + fprintf( fp, "%6.3f %6.3f %6.3f %.2f %.3f %.3f\n", + outVerts[vert].x, outVerts[vert].y, outVerts[vert].z, fr, fg, fb ); + vert++; + fprintf( fp, "%6.3f %6.3f %6.3f %.2f %.3f %.3f\n", + outVerts[vert].x, outVerts[vert].y, outVerts[vert].z, fr, fg, fb ); + vert++; + } + s_pPhysCollision->DestroyDebugMesh( vertCount, outVerts ); + } + fclose( fp ); +} + + +static bool PointInTriangle( const Vector2D &p, const Vector2D &v0, const Vector2D &v1, const Vector2D &v2 ) +{ + float coords[3]; + GetBarycentricCoords2D( v0, v1, v2, p, coords ); + for ( int i = 0; i < 3; i++ ) + { + if ( coords[i] < 0.0f || coords[i] > 1.0f ) + return false; + } + float sum = coords[0] + coords[1] + coords[2]; + if ( sum > 1.0f ) + return false; + return true; +} + +bool LoadFileIntoBuffer( CUtlBuffer &buf, const char *pFilename ) +{ + FileHandle_t fileHandle = g_pFileSystem->Open( pFilename, "rb" ); + if ( !fileHandle ) + return false; + + // Get the file size + int texSize = g_pFileSystem->Size( fileHandle ); + buf.EnsureCapacity( texSize ); + int nBytesRead = g_pFileSystem->Read( buf.Base(), texSize, fileHandle ); + g_pFileSystem->Close( fileHandle ); + buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead ); + buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + return true; +} + +// keeps a list of all textures that cast shadows via alpha channel +class CShadowTextureList +{ +public: + // This loads a vtf and converts it to RGB8888 format + unsigned char *LoadVTFRGB8888( const char *pName, int *pWidth, int *pHeight, bool *pClampU, bool *pClampV ) + { + char szPath[MAX_PATH]; + Q_strncpy( szPath, "materials/", sizeof( szPath ) ); + Q_strncat( szPath, pName, sizeof( szPath ), COPY_ALL_CHARACTERS ); + Q_strncat( szPath, ".vtf", sizeof( szPath ), COPY_ALL_CHARACTERS ); + Q_FixSlashes( szPath, CORRECT_PATH_SEPARATOR ); + + CUtlBuffer buf; + if ( !LoadFileIntoBuffer( buf, szPath ) ) + return NULL; + IVTFTexture *pTex = CreateVTFTexture(); + if (!pTex->Unserialize( buf )) + return NULL; + Msg("Loaded alpha texture %s\n", szPath ); + unsigned char *pSrcImage = pTex->ImageData( 0, 0, 0, 0, 0, 0 ); + int iWidth = pTex->Width(); + int iHeight = pTex->Height(); + ImageFormat dstFormat = IMAGE_FORMAT_RGBA8888; + ImageFormat srcFormat = pTex->Format(); + *pClampU = (pTex->Flags() & TEXTUREFLAGS_CLAMPS) ? true : false; + *pClampV = (pTex->Flags() & TEXTUREFLAGS_CLAMPT) ? true : false; + unsigned char *pDstImage = new unsigned char[ImageLoader::GetMemRequired( iWidth, iHeight, 1, dstFormat, false )]; + + if( !ImageLoader::ConvertImageFormat( pSrcImage, srcFormat, + pDstImage, dstFormat, iWidth, iHeight, 0, 0 ) ) + { + delete[] pDstImage; + return NULL; + } + + *pWidth = iWidth; + *pHeight = iHeight; + return pDstImage; + } + + // Checks the database for the material and loads if necessary + // returns true if found and pIndex will be the index, -1 if no alpha shadows + bool FindOrLoadIfValid( const char *pMaterialName, int *pIndex ) + { + *pIndex = -1; + int index = m_Textures.Find(pMaterialName); + bool bFound = false; + if ( index != m_Textures.InvalidIndex() ) + { + bFound = true; + *pIndex = index; + } + else + { + KeyValues *pVMT = new KeyValues("vmt"); + CUtlBuffer buf(0,0,CUtlBuffer::TEXT_BUFFER); + LoadFileIntoBuffer( buf, pMaterialName ); + if ( pVMT->LoadFromBuffer( pMaterialName, buf ) ) + { + bFound = true; + if ( pVMT->FindKey("$translucent") || pVMT->FindKey("$alphatest") ) + { + KeyValues *pBaseTexture = pVMT->FindKey("$basetexture"); + if ( pBaseTexture ) + { + const char *pBaseTextureName = pBaseTexture->GetString(); + if ( pBaseTextureName ) + { + int w, h; + bool bClampU = false; + bool bClampV = false; + unsigned char *pImageBits = LoadVTFRGB8888( pBaseTextureName, &w, &h, &bClampU, &bClampV ); + if ( pImageBits ) + { + int index = m_Textures.Insert( pMaterialName ); + m_Textures[index].InitFromRGB8888( w, h, pImageBits ); + *pIndex = index; + if ( pVMT->FindKey("$nocull") ) + { + // UNDONE: Support this? Do we need to emit two triangles? + m_Textures[index].allowBackface = true; + } + m_Textures[index].clampU = bClampU; + m_Textures[index].clampV = bClampV; + delete[] pImageBits; + } + } + } + } + + } + pVMT->deleteThis(); + } + + return bFound; + } + + + // iterate the textures for the model and load each one into the database + // this is used on models marked to cast texture shadows + void LoadAllTexturesForModel( studiohdr_t *pHdr, int *pTextureList ) + { + for ( int i = 0; i < pHdr->numtextures; i++ ) + { + int textureIndex = -1; + // try to add each texture to the transparent shadow manager + char szPath[MAX_PATH]; + + // iterate quietly through all specified directories until a valid material is found + for ( int j = 0; j < pHdr->numcdtextures; j++ ) + { + Q_strncpy( szPath, "materials/", sizeof( szPath ) ); + Q_strncat( szPath, pHdr->pCdtexture( j ), sizeof( szPath ) ); + const char *textureName = pHdr->pTexture( i )->pszName(); + Q_strncat( szPath, textureName, sizeof( szPath ), COPY_ALL_CHARACTERS ); + Q_strncat( szPath, ".vmt", sizeof( szPath ), COPY_ALL_CHARACTERS ); + Q_FixSlashes( szPath, CORRECT_PATH_SEPARATOR ); + if ( FindOrLoadIfValid( szPath, &textureIndex ) ) + break; + } + + pTextureList[i] = textureIndex; + } + } + + int AddMaterialEntry( int shadowTextureIndex, const Vector2D &t0, const Vector2D &t1, const Vector2D &t2 ) + { + int index = m_MaterialEntries.AddToTail(); + m_MaterialEntries[index].textureIndex = shadowTextureIndex; + m_MaterialEntries[index].uv[0] = t0; + m_MaterialEntries[index].uv[1] = t1; + m_MaterialEntries[index].uv[2] = t2; + return index; + } + + // HACKHACK: Compute the average coverage for this triangle by sampling the AABB of its texture space + float ComputeCoverageForTriangle( int shadowTextureIndex, const Vector2D &t0, const Vector2D &t1, const Vector2D &t2 ) + { + float umin = min(t0.x, t1.x); + umin = min(umin, t2.x); + float umax = max(t0.x, t1.x); + umax = max(umax, t2.x); + + float vmin = min(t0.y, t1.y); + vmin = min(vmin, t2.y); + float vmax = max(t0.y, t1.y); + vmax = max(vmax, t2.y); + + // UNDONE: Do something about tiling + umin = clamp(umin, 0, 1); + umax = clamp(umax, 0, 1); + vmin = clamp(vmin, 0, 1); + vmax = clamp(vmax, 0, 1); + Assert(umin>=0.0f && umax <= 1.0f); + Assert(vmin>=0.0f && vmax <= 1.0f); + const alphatexture_t &tex = m_Textures.Element(shadowTextureIndex); + int u0 = umin * (tex.width-1); + int u1 = umax * (tex.width-1); + int v0 = vmin * (tex.height-1); + int v1 = vmax * (tex.height-1); + + int total = 0; + int count = 0; + for ( int v = v0; v <= v1; v++ ) + { + int row = (v * tex.width); + for ( int u = u0; u <= u1; u++ ) + { + total += tex.pAlphaTexels[row + u]; + count++; + } + } + if ( count ) + { + float coverage = float(total) / (count * 255.0f); + return coverage; + } + return 1.0f; + } + + int SampleMaterial( int materialIndex, const Vector &coords, bool bBackface ) + { + const materialentry_t &mat = m_MaterialEntries[materialIndex]; + const alphatexture_t &tex = m_Textures.Element(m_MaterialEntries[materialIndex].textureIndex); + if ( bBackface && !tex.allowBackface ) + return 0; + Vector2D uv = coords.x * mat.uv[0] + coords.y * mat.uv[1] + coords.z * mat.uv[2]; + int u = RoundFloatToInt( uv[0] * tex.width ); + int v = RoundFloatToInt( uv[1] * tex.height ); + + // asume power of 2, clamp or wrap + // UNDONE: Support clamp? This code should work +#if 0 + u = tex.clampU ? clamp(u,0,(tex.width-1)) : (u & (tex.width-1)); + v = tex.clampV ? clamp(v,0,(tex.height-1)) : (v & (tex.height-1)); +#else + // for now always wrap + u &= (tex.width-1); + v &= (tex.height-1); +#endif + + return tex.pAlphaTexels[v * tex.width + u]; + } + + struct alphatexture_t + { + short width; + short height; + bool allowBackface; + bool clampU; + bool clampV; + unsigned char *pAlphaTexels; + + void InitFromRGB8888( int w, int h, unsigned char *pTexels ) + { + width = w; + height = h; + pAlphaTexels = new unsigned char[w*h]; + for ( int i = 0; i < h; i++ ) + { + for ( int j = 0; j < w; j++ ) + { + int index = (i*w) + j; + pAlphaTexels[index] = pTexels[index*4 + 3]; + } + } + } + }; + struct materialentry_t + { + int textureIndex; + Vector2D uv[3]; + }; + // this is the list of textures we've loaded + // only load each one once + CUtlDict< alphatexture_t, unsigned short > m_Textures; + CUtlVector m_MaterialEntries; +}; + +// global to keep the shadow-casting texture list and their alpha bits +CShadowTextureList g_ShadowTextureList; + +float ComputeCoverageFromTexture( float b0, float b1, float b2, int32 hitID ) +{ + const float alphaScale = 1.0f / 255.0f; + // UNDONE: Pass ray down to determine backfacing? + //Vector normal( tri.m_flNx, tri.m_flNy, tri.m_flNz ); + //bool bBackface = DotProduct(delta, tri.N) > 0 ? true : false; + Vector coords(b0,b1,b2); + return alphaScale * g_ShadowTextureList.SampleMaterial( g_RtEnv.GetTriangleMaterial(hitID), coords, false ); +} + +// this is here to strip models/ or .mdl or whatnot +void CleanModelName( const char *pModelName, char *pOutput, int outLen ) +{ + // strip off leading models/ if it exists + const char *pModelDir = "models/"; + int modelLen = Q_strlen(pModelDir); + + if ( !Q_strnicmp(pModelName, pModelDir, modelLen ) ) + { + pModelName += modelLen; + } + Q_strncpy( pOutput, pModelName, outLen ); + + // truncate any .mdl extension + char *dot = strchr(pOutput,'.'); + if ( dot ) + { + *dot = 0; + } + +} + + +void ForceTextureShadowsOnModel( const char *pModelName ) +{ + char buf[1024]; + CleanModelName( pModelName, buf, sizeof(buf) ); + if ( !g_ForcedTextureShadowsModels.Find(buf).IsValid()) + { + g_ForcedTextureShadowsModels.AddString(buf); + } +} + +bool IsModelTextureShadowsForced( const char *pModelName ) +{ + char buf[1024]; + CleanModelName( pModelName, buf, sizeof(buf) ); + return g_ForcedTextureShadowsModels.Find(buf).IsValid(); +} + + +//----------------------------------------------------------------------------- +// Creates a collision model (based on the render geometry!) +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::CreateCollisionModel( char const* pModelName ) +{ + CUtlBuffer buf; + CUtlBuffer bufvtx; + CUtlBuffer bufphy; + + int i = m_StaticPropDict.AddToTail(); + m_StaticPropDict[i].m_pModel = NULL; + m_StaticPropDict[i].m_pStudioHdr = NULL; + + if ( !LoadStudioModel( pModelName, buf ) ) + { + VectorCopy( vec3_origin, m_StaticPropDict[i].m_Mins ); + VectorCopy( vec3_origin, m_StaticPropDict[i].m_Maxs ); + return; + } + + studiohdr_t* pHdr = (studiohdr_t*)buf.Base(); + + VectorCopy( pHdr->hull_min, m_StaticPropDict[i].m_Mins ); + VectorCopy( pHdr->hull_max, m_StaticPropDict[i].m_Maxs ); + + if ( LoadStudioCollisionModel( pModelName, bufphy ) ) + { + phyheader_t header; + bufphy.Get( &header, sizeof(header) ); + + vcollide_t *pCollide = &m_StaticPropDict[i].m_loadedModel; + s_pPhysCollision->VCollideLoad( pCollide, header.solidCount, (const char *)bufphy.PeekGet(), bufphy.TellPut() - bufphy.TellGet() ); + m_StaticPropDict[i].m_pModel = m_StaticPropDict[i].m_loadedModel.solids[0]; + + /* + static int propNum = 0; + char tmp[128]; + sprintf( tmp, "staticprop%03d.txt", propNum ); + DumpCollideToGlView( pCollide, tmp ); + ++propNum; + */ + } + else + { + // mark this as unused + m_StaticPropDict[i].m_loadedModel.solidCount = 0; + + // CPhysCollide* pPhys = CreatePhysCollide( pHdr, pVtxHdr ); + m_StaticPropDict[i].m_pModel = ComputeConvexHull( pHdr ); + } + + // clone it + m_StaticPropDict[i].m_pStudioHdr = (studiohdr_t *)malloc( buf.Size() ); + memcpy( m_StaticPropDict[i].m_pStudioHdr, (studiohdr_t*)buf.Base(), buf.Size() ); + + if ( !LoadVTXFile( pModelName, m_StaticPropDict[i].m_pStudioHdr, m_StaticPropDict[i].m_VtxBuf ) ) + { + // failed, leave state identified as disabled + m_StaticPropDict[i].m_VtxBuf.Purge(); + } + + if ( g_bTextureShadows ) + { + if ( (pHdr->flags & STUDIOHDR_FLAGS_CAST_TEXTURE_SHADOWS) || IsModelTextureShadowsForced(pModelName) ) + { + m_StaticPropDict[i].m_textureShadowIndex.RemoveAll(); + m_StaticPropDict[i].m_triangleMaterialIndex.RemoveAll(); + m_StaticPropDict[i].m_textureShadowIndex.AddMultipleToTail( pHdr->numtextures ); + g_ShadowTextureList.LoadAllTexturesForModel( pHdr, m_StaticPropDict[i].m_textureShadowIndex.Base() ); + } + } +} + + +//----------------------------------------------------------------------------- +// Unserialize static prop model dictionary +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::UnserializeModelDict( CUtlBuffer& buf ) +{ + int count = buf.GetInt(); + while ( --count >= 0 ) + { + StaticPropDictLump_t lump; + buf.Get( &lump, sizeof(StaticPropDictLump_t) ); + + CreateCollisionModel( lump.m_Name ); + } +} + +void CVradStaticPropMgr::UnserializeModels( CUtlBuffer& buf ) +{ + int count = buf.GetInt(); + + m_StaticProps.AddMultipleToTail(count); + for ( int i = 0; i < count; ++i ) + { + StaticPropLump_t lump; + buf.Get( &lump, sizeof(StaticPropLump_t) ); + + VectorCopy( lump.m_Origin, m_StaticProps[i].m_Origin ); + VectorCopy( lump.m_Angles, m_StaticProps[i].m_Angles ); + VectorCopy( lump.m_LightingOrigin, m_StaticProps[i].m_LightingOrigin ); + m_StaticProps[i].m_bLightingOriginValid = ( lump.m_Flags & STATIC_PROP_USE_LIGHTING_ORIGIN ) > 0; + m_StaticProps[i].m_ModelIdx = lump.m_PropType; + m_StaticProps[i].m_Handle = TREEDATA_INVALID_HANDLE; + m_StaticProps[i].m_Flags = lump.m_Flags; + } +} + +//----------------------------------------------------------------------------- +// Unserialize static props +//----------------------------------------------------------------------------- + +void CVradStaticPropMgr::UnserializeStaticProps() +{ + // Unserialize static props, insert them into the appropriate leaves + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); + int size = g_GameLumps.GameLumpSize( handle ); + if (!size) + return; + + if ( g_GameLumps.GetGameLumpVersion( handle ) != GAMELUMP_STATIC_PROPS_VERSION ) + { + Error( "Cannot load the static props... encountered a stale map version. Re-vbsp the map." ); + } + + if ( g_GameLumps.GetGameLump( handle ) ) + { + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), size, CUtlBuffer::READ_ONLY ); + UnserializeModelDict( buf ); + + // Skip the leaf list data + int count = buf.GetInt(); + buf.SeekGet( CUtlBuffer::SEEK_CURRENT, count * sizeof(StaticPropLeafLump_t) ); + + UnserializeModels( buf ); + } +} + +//----------------------------------------------------------------------------- +// Level init, shutdown +//----------------------------------------------------------------------------- + +void CVradStaticPropMgr::Init() +{ + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); + if ( !physicsFactory ) + Error( "Unable to load vphysics DLL." ); + + s_pPhysCollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); + if( !s_pPhysCollision ) + { + Error( "Unable to get '%s' for physics interface.", VPHYSICS_COLLISION_INTERFACE_VERSION ); + return; + } + + // Read in static props that have been compiled into the bsp file + UnserializeStaticProps(); +} + +void CVradStaticPropMgr::Shutdown() +{ + + // Remove all static prop model data + for (int i = m_StaticPropDict.Size(); --i >= 0; ) + { + studiohdr_t *pStudioHdr = m_StaticPropDict[i].m_pStudioHdr; + if ( pStudioHdr ) + { + if ( pStudioHdr->pVertexBase ) + { + free( pStudioHdr->pVertexBase ); + } + free( pStudioHdr ); + } + } + + m_StaticProps.Purge(); + m_StaticPropDict.Purge(); +} + +void ComputeLightmapColor( dface_t* pFace, Vector &color ) +{ + texinfo_t* pTex = &texinfo[pFace->texinfo]; + if ( pTex->flags & SURF_SKY ) + { + // sky ambient already accounted for in direct component + return; + } +} + +bool PositionInSolid( Vector &position ) +{ + int ndxLeaf = PointLeafnum( position ); + if ( dleafs[ndxLeaf].contents & CONTENTS_SOLID ) + { + // position embedded in solid + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Trace from a vertex to each direct light source, accumulating its contribution. +//----------------------------------------------------------------------------- +void ComputeDirectLightingAtPoint( Vector &position, Vector &normal, Vector &outColor, int iThread, + int static_prop_id_to_skip=-1, int nLFlags = 0) +{ + SSE_sampleLightOutput_t sampleOutput; + + outColor.Init(); + + // Iterate over all direct lights and accumulate their contribution + int cluster = ClusterFromPoint( position ); + for ( directlight_t *dl = activelights; dl != NULL; dl = dl->next ) + { + if ( dl->light.style ) + { + // skip lights with style + continue; + } + + // is this lights cluster visible? + if ( !PVSCheck( dl->pvs, cluster ) ) + continue; + + // push the vertex towards the light to avoid surface acne + Vector adjusted_pos = position; + float flEpsilon = 0.0; + + if (dl->light.type != emit_skyambient) + { + // push towards the light + Vector fudge; + if ( dl->light.type == emit_skylight ) + fudge = -( dl->light.normal); + else + { + fudge = dl->light.origin-position; + VectorNormalize( fudge ); + } + fudge *= 4.0; + adjusted_pos += fudge; + } + else + { + // push out along normal + adjusted_pos += 4.0 * normal; +// flEpsilon = 1.0; + } + + FourVectors adjusted_pos4; + FourVectors normal4; + adjusted_pos4.DuplicateVector( adjusted_pos ); + normal4.DuplicateVector( normal ); + + GatherSampleLightSSE( sampleOutput, dl, -1, adjusted_pos4, &normal4, 1, iThread, nLFlags | GATHERLFLAGS_FORCE_FAST, + static_prop_id_to_skip, flEpsilon ); + + VectorMA( outColor, sampleOutput.m_flFalloff.m128_f32[0] * sampleOutput.m_flDot[0].m128_f32[0], dl->light.intensity, outColor ); + } +} + +//----------------------------------------------------------------------------- +// Takes the results from a ComputeLighting call and applies it to the static prop in question. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::ApplyLightingToStaticProp( CStaticProp &prop, const CComputeStaticPropLightingResults *pResults ) +{ + if ( pResults->m_ColorVertsArrays.Count() == 0 ) + return; + + StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; + studiohdr_t *pStudioHdr = dict.m_pStudioHdr; + OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); + Assert( pStudioHdr && pVtxHdr ); + + int iCurColorVertsArray = 0; + for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) + { + OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart( bodyID ); + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); + + for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) + { + OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel( modelID ); + mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); + + const CUtlVector &colorVerts = *pResults->m_ColorVertsArrays[iCurColorVertsArray++]; + + for ( int nLod = 0; nLod < pVtxHdr->numLODs; nLod++ ) + { + OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLod ); + + for ( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh ) + { + mstudiomesh_t* pMesh = pStudioModel->pMesh( nMesh ); + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh( nMesh ); + + for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup ) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup( nGroup ); + int nMeshIdx = prop.m_MeshData.AddToTail(); + prop.m_MeshData[nMeshIdx].m_Verts.AddMultipleToTail( pStripGroup->numVerts ); + prop.m_MeshData[nMeshIdx].m_nLod = nLod; + + for ( int nVertex = 0; nVertex < pStripGroup->numVerts; ++nVertex ) + { + int nIndex = pMesh->vertexoffset + pStripGroup->pVertex( nVertex )->origMeshVertID; + + Assert( nIndex < pStudioModel->numvertices ); + prop.m_MeshData[nMeshIdx].m_Verts[nVertex] = colorVerts[nIndex].m_Color; + } + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Trace rays from each unique vertex, accumulating direct and indirect +// sources at each ray termination. Use the winding data to distribute the unique vertexes +// into the rendering layout. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::ComputeLighting( CStaticProp &prop, int iThread, int prop_index, CComputeStaticPropLightingResults *pResults ) +{ + CUtlVector badVerts; + + StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; + studiohdr_t *pStudioHdr = dict.m_pStudioHdr; + OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); + if ( !pStudioHdr || !pVtxHdr ) + { + // must have model and its verts for lighting computation + // game will fallback to fullbright + return; + } + + if (prop.m_Flags & STATIC_PROP_NO_PER_VERTEX_LIGHTING ) + return; + + VMPI_SetCurrentStage( "ComputeLighting" ); + + for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) + { + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); + + for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) + { + mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); + + // light all unique vertexes + CUtlVector *pColorVertsArray = new CUtlVector; + pResults->m_ColorVertsArrays.AddToTail( pColorVertsArray ); + + CUtlVector &colorVerts = *pColorVertsArray; + colorVerts.EnsureCount( pStudioModel->numvertices ); + memset( colorVerts.Base(), 0, colorVerts.Count() * sizeof(colorVertex_t) ); + + int numVertexes = 0; + for ( int meshID = 0; meshID < pStudioModel->nummeshes; ++meshID ) + { + mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID ); + const mstudio_meshvertexdata_t *vertData = pStudioMesh->GetVertexData((void *)pStudioHdr); + Assert( vertData ); // This can only return NULL on X360 for now + for ( int vertexID = 0; vertexID < pStudioMesh->numvertices; ++vertexID ) + { + Vector sampleNormal; + Vector samplePosition; + // transform position and normal into world coordinate system + matrix3x4_t matrix; + AngleMatrix( prop.m_Angles, prop.m_Origin, matrix ); + VectorTransform( *vertData->Position( vertexID ), matrix, samplePosition ); + AngleMatrix( prop.m_Angles, matrix ); + VectorTransform( *vertData->Normal( vertexID ), matrix, sampleNormal ); + + if ( PositionInSolid( samplePosition ) ) + { + // vertex is in solid, add to the bad list, and recover later + badVertex_t badVertex; + badVertex.m_ColorVertex = numVertexes; + badVertex.m_Position = samplePosition; + badVertex.m_Normal = sampleNormal; + badVerts.AddToTail( badVertex ); + } + else + { + Vector direct_pos=samplePosition; + int skip_prop = -1; + if ( g_bDisablePropSelfShadowing || ( prop.m_Flags & STATIC_PROP_NO_SELF_SHADOWING ) ) + { + skip_prop = prop_index; + } + + int nFlags = ( prop.m_Flags & STATIC_PROP_IGNORE_NORMALS ) ? GATHERLFLAGS_IGNORE_NORMALS : 0; + + Vector directColor(0,0,0); + ComputeDirectLightingAtPoint( direct_pos, + sampleNormal, directColor, iThread, + skip_prop, nFlags ); + Vector indirectColor(0,0,0); + + if (g_bShowStaticPropNormals) + { + directColor= sampleNormal; + directColor += Vector(1.0,1.0,1.0); + directColor *= 50.0; + } + else + { + if (numbounce >= 1) + ComputeIndirectLightingAtPoint( + samplePosition, sampleNormal, + indirectColor, iThread, true, + ( prop.m_Flags & STATIC_PROP_IGNORE_NORMALS) != 0 ); + } + + colorVerts[numVertexes].m_bValid = true; + colorVerts[numVertexes].m_Position = samplePosition; + VectorAdd( directColor, indirectColor, colorVerts[numVertexes].m_Color ); + } + + numVertexes++; + } + } + + // color in the bad vertexes + // when entire model has no lighting origin and no valid neighbors + // must punt, leave black coloring + if ( badVerts.Count() && ( prop.m_bLightingOriginValid || badVerts.Count() != numVertexes ) ) + { + for ( int nBadVertex = 0; nBadVertex < badVerts.Count(); nBadVertex++ ) + { + Vector bestPosition; + if ( prop.m_bLightingOriginValid ) + { + // use the specified lighting origin + VectorCopy( prop.m_LightingOrigin, bestPosition ); + } + else + { + // find the closest valid neighbor + int best = 0; + float closest = FLT_MAX; + for ( int nColorVertex = 0; nColorVertex < numVertexes; nColorVertex++ ) + { + if ( !colorVerts[nColorVertex].m_bValid ) + { + // skip invalid neighbors + continue; + } + Vector delta; + VectorSubtract( colorVerts[nColorVertex].m_Position, badVerts[nBadVertex].m_Position, delta ); + float distance = VectorLength( delta ); + if ( distance < closest ) + { + closest = distance; + best = nColorVertex; + } + } + + // use the best neighbor as the direction to crawl + VectorCopy( colorVerts[best].m_Position, bestPosition ); + } + + // crawl toward best position + // sudivide to determine a closer valid point to the bad vertex, and re-light + Vector midPosition; + int numIterations = 20; + while ( --numIterations > 0 ) + { + VectorAdd( bestPosition, badVerts[nBadVertex].m_Position, midPosition ); + VectorScale( midPosition, 0.5f, midPosition ); + if ( PositionInSolid( midPosition ) ) + break; + bestPosition = midPosition; + } + + // re-light from better position + Vector directColor; + ComputeDirectLightingAtPoint( bestPosition, badVerts[nBadVertex].m_Normal, directColor, iThread ); + + Vector indirectColor; + ComputeIndirectLightingAtPoint( bestPosition, badVerts[nBadVertex].m_Normal, + indirectColor, iThread, true ); + + // save results, not changing valid status + // to ensure this offset position is not considered as a viable candidate + colorVerts[badVerts[nBadVertex].m_ColorVertex].m_Position = bestPosition; + VectorAdd( directColor, indirectColor, colorVerts[badVerts[nBadVertex].m_ColorVertex].m_Color ); + } + } + + // discard bad verts + badVerts.Purge(); + } + } +} + +//----------------------------------------------------------------------------- +// Write the lighitng to bsp pak lump +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::SerializeLighting() +{ + char filename[MAX_PATH]; + CUtlBuffer utlBuf; + + // illuminate them all + int count = m_StaticProps.Count(); + if ( !count ) + { + // nothing to do + return; + } + + char mapName[MAX_PATH]; + Q_FileBase( source, mapName, sizeof( mapName ) ); + + int size; + for (int i = 0; i < count; ++i) + { + // no need to write this file if we didn't compute the data + // props marked this way will not load the info anyway + if ( m_StaticProps[i].m_Flags & STATIC_PROP_NO_PER_VERTEX_LIGHTING ) + continue; + + if (g_bHDR) + { + sprintf( filename, "sp_hdr_%d.vhv", i ); + } + else + { + sprintf( filename, "sp_%d.vhv", i ); + } + + int totalVertexes = 0; + for ( int j=0; jm_nVersion = VHV_VERSION; + pVhvHdr->m_nChecksum = m_StaticPropDict[m_StaticProps[i].m_ModelIdx].m_pStudioHdr->checksum; + pVhvHdr->m_nVertexFlags = VERTEX_COLOR; + pVhvHdr->m_nVertexSize = 4; + pVhvHdr->m_nVertexes = totalVertexes; + pVhvHdr->m_nMeshes = m_StaticProps[i].m_MeshData.Count(); + + for (int n=0; nm_nMeshes; n++) + { + // construct mesh dictionary + HardwareVerts::MeshHeader_t *pMesh = pVhvHdr->pMesh( n ); + pMesh->m_nLod = m_StaticProps[i].m_MeshData[n].m_nLod; + pMesh->m_nVertexes = m_StaticProps[i].m_MeshData[n].m_Verts.Count(); + pMesh->m_nOffset = (unsigned int)pVertexData - (unsigned int)pVhvHdr; + + // construct vertexes + for (int k=0; km_nVertexes; k++) + { + Vector &vector = m_StaticProps[i].m_MeshData[n].m_Verts[k]; + + ColorRGBExp32 rgbColor; + VectorToColorRGBExp32( vector, rgbColor ); + unsigned char dstColor[4]; + ConvertRGBExp32ToRGBA8888( &rgbColor, dstColor ); + + // b,g,r,a order + pVertexData[0] = dstColor[2]; + pVertexData[1] = dstColor[1]; + pVertexData[2] = dstColor[0]; + pVertexData[3] = dstColor[3]; + pVertexData += 4; + } + } + + // align to end of file + pVertexData = (unsigned char *)((unsigned int)pVertexData - (unsigned int)pVhvHdr); + pVertexData = (unsigned char*)pVhvHdr + ALIGN_TO_POW2( (unsigned int)pVertexData, 512 ); + + AddBufferToPak( GetPakFile(), filename, (void*)pVhvHdr, pVertexData - (unsigned char*)pVhvHdr, false ); + } +} + +void CVradStaticPropMgr::VMPI_ProcessStaticProp_Static( int iThread, uint64 iStaticProp, MessageBuffer *pBuf ) +{ + g_StaticPropMgr.VMPI_ProcessStaticProp( iThread, iStaticProp, pBuf ); +} + +void CVradStaticPropMgr::VMPI_ReceiveStaticPropResults_Static( uint64 iStaticProp, MessageBuffer *pBuf, int iWorker ) +{ + g_StaticPropMgr.VMPI_ReceiveStaticPropResults( iStaticProp, pBuf, iWorker ); +} + +//----------------------------------------------------------------------------- +// Called on workers to do the computation for a static prop and send +// it to the master. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::VMPI_ProcessStaticProp( int iThread, int iStaticProp, MessageBuffer *pBuf ) +{ + // Compute the lighting. + CComputeStaticPropLightingResults results; + ComputeLighting( m_StaticProps[iStaticProp], iThread, iStaticProp, &results ); + + VMPI_SetCurrentStage( "EncodeLightingResults" ); + + // Encode the results. + int nLists = results.m_ColorVertsArrays.Count(); + pBuf->write( &nLists, sizeof( nLists ) ); + + for ( int i=0; i < nLists; i++ ) + { + CUtlVector &curList = *results.m_ColorVertsArrays[i]; + int count = curList.Count(); + pBuf->write( &count, sizeof( count ) ); + pBuf->write( curList.Base(), curList.Count() * sizeof( colorVertex_t ) ); + } +} + +//----------------------------------------------------------------------------- +// Called on the master when a worker finishes processing a static prop. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::VMPI_ReceiveStaticPropResults( int iStaticProp, MessageBuffer *pBuf, int iWorker ) +{ + // Read in the results. + CComputeStaticPropLightingResults results; + + int nLists; + pBuf->read( &nLists, sizeof( nLists ) ); + + for ( int i=0; i < nLists; i++ ) + { + CUtlVector *pList = new CUtlVector; + results.m_ColorVertsArrays.AddToTail( pList ); + + int count; + pBuf->read( &count, sizeof( count ) ); + pList->SetSize( count ); + pBuf->read( pList->Base(), count * sizeof( colorVertex_t ) ); + } + + // Apply the results. + ApplyLightingToStaticProp( m_StaticProps[iStaticProp], &results ); +} + + +void CVradStaticPropMgr::ComputeLightingForProp( int iThread, int iStaticProp ) +{ + // Compute the lighting. + CComputeStaticPropLightingResults results; + ComputeLighting( m_StaticProps[iStaticProp], iThread, iStaticProp, &results ); + ApplyLightingToStaticProp( m_StaticProps[iStaticProp], &results ); +} + +void CVradStaticPropMgr::ThreadComputeStaticPropLighting( int iThread, void *pUserData ) +{ + while (1) + { + int j = GetThreadWork (); + if (j == -1) + break; + CComputeStaticPropLightingResults results; + g_StaticPropMgr.ComputeLightingForProp( iThread, j ); + } +} + +//----------------------------------------------------------------------------- +// Computes lighting for the static props. +// Must be after all other surface lighting has been computed for the indirect sampling. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::ComputeLighting( int iThread ) +{ + // illuminate them all + int count = m_StaticProps.Count(); + if ( !count ) + { + // nothing to do + return; + } + + StartPacifier( "Computing static prop lighting : " ); + + // ensure any traces against us are ignored because we have no inherit lighting contribution + m_bIgnoreStaticPropTrace = true; + + if ( g_bUseMPI ) + { + // Distribute the work among the workers. + VMPI_SetCurrentStage( "CVradStaticPropMgr::ComputeLighting" ); + + DistributeWork( + count, + VMPI_DISTRIBUTEWORK_PACKETID, + &CVradStaticPropMgr::VMPI_ProcessStaticProp_Static, + &CVradStaticPropMgr::VMPI_ReceiveStaticPropResults_Static ); + } + else + { + RunThreadsOn(count, true, ThreadComputeStaticPropLighting); + } + + // restore default + m_bIgnoreStaticPropTrace = false; + + // save data to bsp + SerializeLighting(); + + EndPacifier( true ); +} + +//----------------------------------------------------------------------------- +// Adds all static prop polys to the ray trace store. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::AddPolysForRayTrace( void ) +{ + int count = m_StaticProps.Count(); + if ( !count ) + { + // nothing to do + return; + } + + // Triangle coverage of 1 (full coverage) + Vector fullCoverage; + fullCoverage.x = 1.0f; + + for ( int nProp = 0; nProp < count; ++nProp ) + { + CStaticProp &prop = m_StaticProps[nProp]; + StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; + + if ( prop.m_Flags & STATIC_PROP_NO_SHADOW ) + continue; + + // If not using static prop polys, use AABB + if ( !g_bStaticPropPolys ) + { + if ( dict.m_pModel ) + { + VMatrix xform; + xform.SetupMatrixOrgAngles ( prop.m_Origin, prop.m_Angles ); + ICollisionQuery *queryModel = s_pPhysCollision->CreateQueryModel( dict.m_pModel ); + for ( int nConvex = 0; nConvex < queryModel->ConvexCount(); ++nConvex ) + { + for ( int nTri = 0; nTri < queryModel->TriangleCount( nConvex ); ++nTri ) + { + Vector verts[3]; + queryModel->GetTriangleVerts( nConvex, nTri, verts ); + for ( int nVert = 0; nVert < 3; ++nVert ) + verts[nVert] = xform.VMul4x3(verts[nVert]); + g_RtEnv.AddTriangle ( TRACE_ID_STATICPROP | nProp, verts[0], verts[1], verts[2], fullCoverage ); + } + } + s_pPhysCollision->DestroyQueryModel( queryModel ); + } + else + { + VectorAdd ( dict.m_Mins, prop.m_Origin, prop.m_mins ); + VectorAdd ( dict.m_Maxs, prop.m_Origin, prop.m_maxs ); + g_RtEnv.AddAxisAlignedRectangularSolid ( TRACE_ID_STATICPROP | nProp, prop.m_mins, prop.m_maxs, fullCoverage ); + } + + continue; + } + + studiohdr_t *pStudioHdr = dict.m_pStudioHdr; + OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); + if ( !pStudioHdr || !pVtxHdr ) + { + // must have model and its verts for decoding triangles + return; + } + // only init the triangle table the first time + bool bInitTriangles = dict.m_triangleMaterialIndex.Count() ? false : true; + int triangleIndex = 0; + + // meshes are deeply hierarchial, divided between three stores, follow the white rabbit + // body parts -> models -> lod meshes -> strip groups -> strips + // the vertices and indices are pooled, the trick is knowing the offset to determine your indexed base + for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) + { + OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart( bodyID ); + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); + + for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) + { + OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel( modelID ); + mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); + + // assuming lod 0, could iterate if required + int nLod = 0; + OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLod ); + + for ( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh ) + { + // check if this mesh's material is in the no shadow material name list + mstudiomesh_t* pMesh = pStudioModel->pMesh( nMesh ); + mstudiotexture_t *pTxtr=pStudioHdr->pTexture(pMesh->material); + //printf("mat idx=%d mat name=%s\n",pMesh->material,pTxtr->pszName()); + bool bSkipThisMesh = false; + for(int check=0; checkpszName(), + g_NonShadowCastingMaterialStrings[check] ) ) + { + //printf("skip mat name=%s\n",pTxtr->pszName()); + bSkipThisMesh = true; + break; + } + } + if ( bSkipThisMesh) + continue; + + int shadowTextureIndex = -1; + if ( dict.m_textureShadowIndex.Count() ) + { + shadowTextureIndex = dict.m_textureShadowIndex[pMesh->material]; + } + + + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh( nMesh ); + const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData( (void *)pStudioHdr ); + Assert( vertData ); // This can only return NULL on X360 for now + + for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup ) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup( nGroup ); + + int nStrip; + for ( nStrip = 0; nStrip < pStripGroup->numStrips; nStrip++ ) + { + OptimizedModel::StripHeader_t *pStrip = pStripGroup->pStrip( nStrip ); + + if ( pStrip->flags & OptimizedModel::STRIP_IS_TRILIST ) + { + for ( int i = 0; i < pStrip->numIndices; i += 3 ) + { + int idx = pStrip->indexOffset + i; + + unsigned short i1 = *pStripGroup->pIndex( idx ); + unsigned short i2 = *pStripGroup->pIndex( idx + 1 ); + unsigned short i3 = *pStripGroup->pIndex( idx + 2 ); + + int vertex1 = pStripGroup->pVertex( i1 )->origMeshVertID; + int vertex2 = pStripGroup->pVertex( i2 )->origMeshVertID; + int vertex3 = pStripGroup->pVertex( i3 )->origMeshVertID; + + // transform position into world coordinate system + matrix3x4_t matrix; + AngleMatrix( prop.m_Angles, prop.m_Origin, matrix ); + + Vector position1; + Vector position2; + Vector position3; + VectorTransform( *vertData->Position( vertex1 ), matrix, position1 ); + VectorTransform( *vertData->Position( vertex2 ), matrix, position2 ); + VectorTransform( *vertData->Position( vertex3 ), matrix, position3 ); + unsigned short flags = 0; + int materialIndex = -1; + Vector color = vec3_origin; + if ( shadowTextureIndex >= 0 ) + { + if ( bInitTriangles ) + { + // add texture space and texture index to material database + // now + float coverage = g_ShadowTextureList.ComputeCoverageForTriangle(shadowTextureIndex, *vertData->Texcoord(vertex1), *vertData->Texcoord(vertex2), *vertData->Texcoord(vertex3) ); + if ( coverage < 1.0f ) + { + materialIndex = g_ShadowTextureList.AddMaterialEntry( shadowTextureIndex, *vertData->Texcoord(vertex1), *vertData->Texcoord(vertex2), *vertData->Texcoord(vertex3) ); + color.x = coverage; + } + else + { + materialIndex = -1; + } + dict.m_triangleMaterialIndex.AddToTail(materialIndex); + } + else + { + materialIndex = dict.m_triangleMaterialIndex[triangleIndex]; + triangleIndex++; + } + if ( materialIndex >= 0 ) + { + flags = FCACHETRI_TRANSPARENT; + } + } +// printf( "\ngl 3\n" ); +// printf( "gl %6.3f %6.3f %6.3f 1 0 0\n", XYZ(position1)); +// printf( "gl %6.3f %6.3f %6.3f 0 1 0\n", XYZ(position2)); +// printf( "gl %6.3f %6.3f %6.3f 0 0 1\n", XYZ(position3)); + g_RtEnv.AddTriangle( TRACE_ID_STATICPROP | nProp, + position1, position2, position3, + color, flags, materialIndex); + } + } + else + { + // all tris expected to be discrete tri lists + // must fixme if stripping ever occurs + printf( "unexpected strips found\n" ); + Assert( 0 ); + return; + } + } + } + } + } + } + } +} + +struct tl_tri_t +{ + Vector p0; + Vector p1; + Vector p2; + Vector n0; + Vector n1; + Vector n2; + + bool operator == (const tl_tri_t &t) const + { + return ( p0 == t.p0 && + p1 == t.p1 && + p2 == t.p2 && + n0 == t.n0 && + n1 == t.n1 && + n2 == t.n2 ); + } +}; + +struct tl_vert_t +{ + Vector m_position; + CUtlLinkedList< tl_tri_t, int > m_triList; +}; + +void AddTriVertsToList( CUtlVector< tl_vert_t > &triListVerts, int vertIndex, Vector vertPosition, Vector p0, Vector p1, Vector p2, Vector n0, Vector n1, Vector n2 ) +{ + tl_tri_t tlTri; + + tlTri.p0 = p0; + tlTri.p1 = p1; + tlTri.p2 = p2; + tlTri.n0 = n0; + tlTri.n1 = n1; + tlTri.n2 = n2; + + triListVerts.EnsureCapacity( vertIndex+1 ); + + triListVerts[vertIndex].m_position = vertPosition; + + int index = triListVerts[vertIndex].m_triList.Find( tlTri ); + if ( !triListVerts[vertIndex].m_triList.IsValidIndex( index ) ) + { + // not in list, add to list of triangles + triListVerts[vertIndex].m_triList.AddToTail( tlTri ); + } +} + +//----------------------------------------------------------------------------- +// Builds a list of tris for every vertex +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::BuildTriList( CStaticProp &prop ) +{ + // the generated list will consist of a list of verts + // each vert will have a linked list of triangles that it belongs to + CUtlVector< tl_vert_t > triListVerts; + + StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; + studiohdr_t *pStudioHdr = dict.m_pStudioHdr; + OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); + if ( !pStudioHdr || !pVtxHdr ) + { + // must have model and its verts for decoding triangles + return; + } + + // meshes are deeply hierarchial, divided between three stores, follow the white rabbit + // body parts -> models -> lod meshes -> strip groups -> strips + // the vertices and indices are pooled, the trick is knowing the offset to determine your indexed base + for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) + { + OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart( bodyID ); + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); + + for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) + { + OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel( modelID ); + mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); + + // get the specified lod, assuming lod 0 + int nLod = 0; + OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLod ); + + // must reset because each model has their own vertexes [0..n] + // in order for this to be monolithic for the entire prop the list must be segmented + triListVerts.Purge(); + + for ( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh ) + { + mstudiomesh_t* pMesh = pStudioModel->pMesh( nMesh ); + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh( nMesh ); + const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData( (void *)pStudioHdr ); + Assert( vertData ); // This can only return NULL on X360 for now + + for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup ) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup( nGroup ); + + int nStrip; + for ( nStrip = 0; nStrip < pStripGroup->numStrips; nStrip++ ) + { + OptimizedModel::StripHeader_t *pStrip = pStripGroup->pStrip( nStrip ); + + if ( pStrip->flags & OptimizedModel::STRIP_IS_TRILIST ) + { + for ( int i = 0; i < pStrip->numIndices; i += 3 ) + { + int idx = pStrip->indexOffset + i; + + unsigned short i1 = *pStripGroup->pIndex( idx ); + unsigned short i2 = *pStripGroup->pIndex( idx + 1 ); + unsigned short i3 = *pStripGroup->pIndex( idx + 2 ); + + int vertex1 = pStripGroup->pVertex( i1 )->origMeshVertID; + int vertex2 = pStripGroup->pVertex( i2 )->origMeshVertID; + int vertex3 = pStripGroup->pVertex( i3 )->origMeshVertID; + + // transform position into world coordinate system + matrix3x4_t matrix; + AngleMatrix( prop.m_Angles, prop.m_Origin, matrix ); + + Vector position1; + Vector position2; + Vector position3; + VectorTransform( *vertData->Position( vertex1 ), matrix, position1 ); + VectorTransform( *vertData->Position( vertex2 ), matrix, position2 ); + VectorTransform( *vertData->Position( vertex3 ), matrix, position3 ); + + Vector normal1; + Vector normal2; + Vector normal3; + VectorTransform( *vertData->Normal( vertex1 ), matrix, normal1 ); + VectorTransform( *vertData->Normal( vertex2 ), matrix, normal2 ); + VectorTransform( *vertData->Normal( vertex3 ), matrix, normal3 ); + + AddTriVertsToList( triListVerts, pMesh->vertexoffset + vertex1, position1, position1, position2, position3, normal1, normal2, normal3 ); + AddTriVertsToList( triListVerts, pMesh->vertexoffset + vertex2, position2, position1, position2, position3, normal1, normal2, normal3 ); + AddTriVertsToList( triListVerts, pMesh->vertexoffset + vertex3, position3, position1, position2, position3, normal1, normal2, normal3 ); + } + } + else + { + // all tris expected to be discrete tri lists + // must fixme if stripping ever occurs + printf( "unexpected strips found\n" ); + Assert( 0 ); + return; + } + } + } + } + } + } +} + +const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void *pModelData ) +{ + studiohdr_t *pActiveStudioHdr = static_cast(pModelData); + Assert( pActiveStudioHdr ); + + if ( pActiveStudioHdr->pVertexBase ) + { + return (vertexFileHeader_t *)pActiveStudioHdr->pVertexBase; + } + + // mandatory callback to make requested data resident + // load and persist the vertex file + char fileName[MAX_PATH]; + strcpy( fileName, "models/" ); + strcat( fileName, pActiveStudioHdr->pszName() ); + Q_StripExtension( fileName, fileName, sizeof( fileName ) ); + strcat( fileName, ".vvd" ); + + // load the model + FileHandle_t fileHandle = g_pFileSystem->Open( fileName, "rb" ); + if ( !fileHandle ) + { + Error( "Unable to load vertex data \"%s\"\n", fileName ); + } + + // Get the file size + int vvdSize = g_pFileSystem->Size( fileHandle ); + if ( vvdSize == 0 ) + { + g_pFileSystem->Close( fileHandle ); + Error( "Bad size for vertex data \"%s\"\n", fileName ); + } + + vertexFileHeader_t *pVvdHdr = (vertexFileHeader_t *)malloc( vvdSize ); + g_pFileSystem->Read( pVvdHdr, vvdSize, fileHandle ); + g_pFileSystem->Close( fileHandle ); + + // check header + if ( pVvdHdr->id != MODEL_VERTEX_FILE_ID ) + { + Error("Error Vertex File %s id %d should be %d\n", fileName, pVvdHdr->id, MODEL_VERTEX_FILE_ID); + } + if ( pVvdHdr->version != MODEL_VERTEX_FILE_VERSION ) + { + Error("Error Vertex File %s version %d should be %d\n", fileName, pVvdHdr->version, MODEL_VERTEX_FILE_VERSION); + } + if ( pVvdHdr->checksum != pActiveStudioHdr->checksum ) + { + Error("Error Vertex File %s checksum %d should be %d\n", fileName, pVvdHdr->checksum, pActiveStudioHdr->checksum); + } + + // need to perform mesh relocation fixups + // allocate a new copy + vertexFileHeader_t *pNewVvdHdr = (vertexFileHeader_t *)malloc( vvdSize ); + if ( !pNewVvdHdr ) + { + Error( "Error allocating %d bytes for Vertex File '%s'\n", vvdSize, fileName ); + } + + // load vertexes and run fixups + Studio_LoadVertexes( pVvdHdr, pNewVvdHdr, 0, true ); + + // discard original + free( pVvdHdr ); + pVvdHdr = pNewVvdHdr; + + pActiveStudioHdr->pVertexBase = (void*)pVvdHdr; + return pVvdHdr; +} diff --git a/mp/src/utils/vrad_launcher/stdafx.cpp b/mp/src/utils/vrad_launcher/stdafx.cpp index ecf65582..983b8ba5 100644 --- a/mp/src/utils/vrad_launcher/stdafx.cpp +++ b/mp/src/utils/vrad_launcher/stdafx.cpp @@ -1,15 +1,15 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// stdafx.cpp : source file that includes just the standard includes -// vrad_launcher.pch will be the pre-compiled header -// stdafx.obj will contain the pre-compiled type information - -#include "stdafx.h" - -// TODO: reference any additional headers you need in STDAFX.H -// and not in this file +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// vrad_launcher.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/mp/src/utils/vrad_launcher/stdafx.h b/mp/src/utils/vrad_launcher/stdafx.h index 1c89a814..36daff0a 100644 --- a/mp/src/utils/vrad_launcher/stdafx.h +++ b/mp/src/utils/vrad_launcher/stdafx.h @@ -1,32 +1,32 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// stdafx.h : include file for standard system include files, -// or project specific include files that are used frequently, but -// are changed infrequently -// - -#if !defined(AFX_STDAFX_H__4A047C84_94D7_4563_A08C_35E52A52AECC__INCLUDED_) -#define AFX_STDAFX_H__4A047C84_94D7_4563_A08C_35E52A52AECC__INCLUDED_ - -#if _MSC_VER > 1000 -#pragma once -#endif // _MSC_VER > 1000 - -#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers - -#include -#include -#include "interface.h" -#include "ivraddll.h" - -// TODO: reference additional headers your program requires here - -//{{AFX_INSERT_LOCATION}} -// Microsoft Visual C++ will insert additional declarations immediately before the previous line. - -#endif // !defined(AFX_STDAFX_H__4A047C84_94D7_4563_A08C_35E52A52AECC__INCLUDED_) +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__4A047C84_94D7_4563_A08C_35E52A52AECC__INCLUDED_) +#define AFX_STDAFX_H__4A047C84_94D7_4563_A08C_35E52A52AECC__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include +#include +#include "interface.h" +#include "ivraddll.h" + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__4A047C84_94D7_4563_A08C_35E52A52AECC__INCLUDED_) diff --git a/mp/src/utils/vrad_launcher/vrad_launcher.cpp b/mp/src/utils/vrad_launcher/vrad_launcher.cpp index fc458172..a4d31834 100644 --- a/mp/src/utils/vrad_launcher/vrad_launcher.cpp +++ b/mp/src/utils/vrad_launcher/vrad_launcher.cpp @@ -1,145 +1,145 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// vrad_launcher.cpp : Defines the entry point for the console application. -// - -#include "stdafx.h" -#include -#include "tier1/strtools.h" -#include "tier0/icommandline.h" - - -char* GetLastErrorString() -{ - static char err[2048]; - - LPVOID lpMsgBuf; - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language - (LPTSTR) &lpMsgBuf, - 0, - NULL - ); - - strncpy( err, (char*)lpMsgBuf, sizeof( err ) ); - LocalFree( lpMsgBuf ); - - err[ sizeof( err ) - 1 ] = 0; - - return err; -} - - -void MakeFullPath( const char *pIn, char *pOut, int outLen ) -{ - if ( pIn[0] == '/' || pIn[0] == '\\' || pIn[1] == ':' ) - { - // It's already a full path. - Q_strncpy( pOut, pIn, outLen ); - } - else - { - _getcwd( pOut, outLen ); - Q_strncat( pOut, "\\", outLen, COPY_ALL_CHARACTERS ); - Q_strncat( pOut, pIn, outLen, COPY_ALL_CHARACTERS ); - } -} - -int main(int argc, char* argv[]) -{ - char dllName[512]; - - CommandLine()->CreateCmdLine( argc, argv ); - - // check whether they used the -both switch. If this is specified, vrad will be run - // twice, once with -hdr and once without - int both_arg=0; - for(int arg=1;argmain( argc, argv ); - Sys_UnloadModule( pModule ); - pModule=0; - } - return returnValue; -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vrad_launcher.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" +#include +#include "tier1/strtools.h" +#include "tier0/icommandline.h" + + +char* GetLastErrorString() +{ + static char err[2048]; + + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + + strncpy( err, (char*)lpMsgBuf, sizeof( err ) ); + LocalFree( lpMsgBuf ); + + err[ sizeof( err ) - 1 ] = 0; + + return err; +} + + +void MakeFullPath( const char *pIn, char *pOut, int outLen ) +{ + if ( pIn[0] == '/' || pIn[0] == '\\' || pIn[1] == ':' ) + { + // It's already a full path. + Q_strncpy( pOut, pIn, outLen ); + } + else + { + _getcwd( pOut, outLen ); + Q_strncat( pOut, "\\", outLen, COPY_ALL_CHARACTERS ); + Q_strncat( pOut, pIn, outLen, COPY_ALL_CHARACTERS ); + } +} + +int main(int argc, char* argv[]) +{ + char dllName[512]; + + CommandLine()->CreateCmdLine( argc, argv ); + + // check whether they used the -both switch. If this is specified, vrad will be run + // twice, once with -hdr and once without + int both_arg=0; + for(int arg=1;argmain( argc, argv ); + Sys_UnloadModule( pModule ); + pModule=0; + } + return returnValue; +} + diff --git a/mp/src/utils/vrad_launcher/vrad_launcher.vpc b/mp/src/utils/vrad_launcher/vrad_launcher.vpc index c9868357..293e5514 100644 --- a/mp/src/utils/vrad_launcher/vrad_launcher.vpc +++ b/mp/src/utils/vrad_launcher/vrad_launcher.vpc @@ -1,49 +1,49 @@ -//----------------------------------------------------------------------------- -// VRAD_LAUNCHER.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" -$Macro OUTBINNAME "vrad" - -$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" - -$Configuration -{ - $Compiler - { - $Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)" - $PrecompiledHeaderFile "Debug/vrad_launcher.pch" - } -} - -$Project "Vrad_launcher" -{ - $Folder "Source Files" - { - -$File "$SRCDIR\public\tier0\memoverride.cpp" - - $File "vrad_launcher.cpp" - - $File "StdAfx.cpp" - { - $Configuration - { - $Compiler - { - $Create/UsePrecompiledHeader "Create Precompiled Header (/Yc)" - } - } - } - - } - - $Folder "Header Files" - { - $File "$SRCDIR\public\tier1\interface.h" - $File "$SRCDIR\public\ivraddll.h" - $File "StdAfx.h" - } -} +//----------------------------------------------------------------------------- +// VRAD_LAUNCHER.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" +$Macro OUTBINNAME "vrad" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Configuration +{ + $Compiler + { + $Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)" + $PrecompiledHeaderFile "Debug/vrad_launcher.pch" + } +} + +$Project "Vrad_launcher" +{ + $Folder "Source Files" + { + -$File "$SRCDIR\public\tier0\memoverride.cpp" + + $File "vrad_launcher.cpp" + + $File "StdAfx.cpp" + { + $Configuration + { + $Compiler + { + $Create/UsePrecompiledHeader "Create Precompiled Header (/Yc)" + } + } + } + + } + + $Folder "Header Files" + { + $File "$SRCDIR\public\tier1\interface.h" + $File "$SRCDIR\public\ivraddll.h" + $File "StdAfx.h" + } +} diff --git a/mp/src/utils/vtf2tga/vtf2tga.cpp b/mp/src/utils/vtf2tga/vtf2tga.cpp index 08c73340..acdace83 100644 --- a/mp/src/utils/vtf2tga/vtf2tga.cpp +++ b/mp/src/utils/vtf2tga/vtf2tga.cpp @@ -1,316 +1,316 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - -#include -#include "mathlib/mathlib.h" -#include "bitmap/tgawriter.h" -#include "tier1/strtools.h" -#include "vtf/vtf.h" -#include "tier1/UtlBuffer.h" -#include "tier0/dbg.h" -#include "tier0/icommandline.h" -#include "tier1/utlbuffer.h" -#include "tier2/tier2.h" -#include "filesystem.h" - - -//----------------------------------------------------------------------------- -// HDRFIXME: move this somewhere else. -//----------------------------------------------------------------------------- -static void PFMWrite( float *pFloatImage, const char *pFilename, int width, int height ) -{ - FILE *fp; - fp = fopen( pFilename, "wb" ); - fprintf( fp, "PF\n%d %d\n-1.000000\n", width, height ); - int i; - for( i = height-1; i >= 0; i-- ) - { - float *pRow = &pFloatImage[3 * width * i]; - fwrite( pRow, width * sizeof( float ) * 3, 1, fp ); - } - fclose( fp ); -} - -SpewRetval_t VTF2TGAOutputFunc( SpewType_t spewType, char const *pMsg ) -{ - printf( pMsg ); - fflush( stdout ); - - if (spewType == SPEW_ERROR) - return SPEW_ABORT; - return (spewType == SPEW_ASSERT) ? SPEW_DEBUGGER : SPEW_CONTINUE; -} - -static void Usage( void ) -{ - Error( "Usage: vtf2tga -i [-o ] [-mip]\n" ); - exit( -1 ); -} - -int main( int argc, char **argv ) -{ - SpewOutputFunc( VTF2TGAOutputFunc ); - CommandLine()->CreateCmdLine( argc, argv ); - MathLib_Init( 2.2f, 2.2f, 0.0f, 1.0f, false, false, false, false ); - InitDefaultFileSystem(); - - const char *pVTFFileName = CommandLine()->ParmValue( "-i" ); - const char *pTGAFileName = CommandLine()->ParmValue( "-o" ); - bool bGenerateMipLevels = CommandLine()->CheckParm( "-mip" ) != NULL; - if ( !pVTFFileName ) - { - Usage(); - } - - if ( !pTGAFileName ) - { - pTGAFileName = pVTFFileName; - } - - char pCurrentDirectory[MAX_PATH]; - if ( _getcwd( pCurrentDirectory, sizeof(pCurrentDirectory) ) == NULL ) - { - fprintf( stderr, "Unable to get the current directory\n" ); - return -1; - } - Q_StripTrailingSlash( pCurrentDirectory ); - - char pBuf[MAX_PATH]; - if ( !Q_IsAbsolutePath( pTGAFileName ) ) - { - Q_snprintf( pBuf, sizeof(pBuf), "%s\\%s", pCurrentDirectory, pTGAFileName ); - } - else - { - Q_strncpy( pBuf, pTGAFileName, sizeof(pBuf) ); - } - Q_FixSlashes( pBuf ); - - char pOutFileNameBase[MAX_PATH]; - Q_StripExtension( pBuf, pOutFileNameBase, MAX_PATH ); - - char pActualVTFFileName[MAX_PATH]; - Q_strncpy( pActualVTFFileName, pVTFFileName, MAX_PATH ); - if ( !Q_strstr( pActualVTFFileName, ".vtf" ) ) - { - Q_strcat( pActualVTFFileName, ".vtf", MAX_PATH ); - } - - FILE *vtfFp = fopen( pActualVTFFileName, "rb" ); - if( !vtfFp ) - { - Error( "Can't open %s\n", pActualVTFFileName ); - exit( -1 ); - } - - fseek( vtfFp, 0, SEEK_END ); - int srcVTFLength = ftell( vtfFp ); - fseek( vtfFp, 0, SEEK_SET ); - - CUtlBuffer buf; - buf.EnsureCapacity( srcVTFLength ); - int nBytesRead = fread( buf.Base(), 1, srcVTFLength, vtfFp ); - fclose( vtfFp ); - buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead ); - - IVTFTexture *pTex = CreateVTFTexture(); - if (!pTex->Unserialize( buf )) - { - Error( "*** Error reading in .VTF file %s\n", pActualVTFFileName ); - exit(-1); - } - - Msg( "vtf width: %d\n", pTex->Width() ); - Msg( "vtf height: %d\n", pTex->Height() ); - Msg( "vtf numFrames: %d\n", pTex->FrameCount() ); - - Msg( "TEXTUREFLAGS_POINTSAMPLE=%s\n", ( pTex->Flags() & TEXTUREFLAGS_POINTSAMPLE ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_TRILINEAR=%s\n", ( pTex->Flags() & TEXTUREFLAGS_TRILINEAR ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_CLAMPS=%s\n", ( pTex->Flags() & TEXTUREFLAGS_CLAMPS ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_CLAMPT=%s\n", ( pTex->Flags() & TEXTUREFLAGS_CLAMPT ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_CLAMPU=%s\n", ( pTex->Flags() & TEXTUREFLAGS_CLAMPU ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_BORDER=%s\n", ( pTex->Flags() & TEXTUREFLAGS_BORDER ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_ANISOTROPIC=%s\n", ( pTex->Flags() & TEXTUREFLAGS_ANISOTROPIC ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_HINT_DXT5=%s\n", ( pTex->Flags() & TEXTUREFLAGS_HINT_DXT5 ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_SRGB=%s\n", ( pTex->Flags() & TEXTUREFLAGS_SRGB ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_NORMAL=%s\n", ( pTex->Flags() & TEXTUREFLAGS_NORMAL ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_NOMIP=%s\n", ( pTex->Flags() & TEXTUREFLAGS_NOMIP ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_NOLOD=%s\n", ( pTex->Flags() & TEXTUREFLAGS_NOLOD ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_ALL_MIPS=%s\n", ( pTex->Flags() & TEXTUREFLAGS_ALL_MIPS ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_PROCEDURAL=%s\n", ( pTex->Flags() & TEXTUREFLAGS_PROCEDURAL ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_ONEBITALPHA=%s\n", ( pTex->Flags() & TEXTUREFLAGS_ONEBITALPHA ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_EIGHTBITALPHA=%s\n", ( pTex->Flags() & TEXTUREFLAGS_EIGHTBITALPHA ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_ENVMAP=%s\n", ( pTex->Flags() & TEXTUREFLAGS_ENVMAP ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_RENDERTARGET=%s\n", ( pTex->Flags() & TEXTUREFLAGS_RENDERTARGET ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_DEPTHRENDERTARGET=%s\n", ( pTex->Flags() & TEXTUREFLAGS_DEPTHRENDERTARGET ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_NODEBUGOVERRIDE=%s\n", ( pTex->Flags() & TEXTUREFLAGS_NODEBUGOVERRIDE ) ? "true" : "false" ); - Msg( "TEXTUREFLAGS_SINGLECOPY=%s\n", ( pTex->Flags() & TEXTUREFLAGS_SINGLECOPY ) ? "true" : "false" ); - - Vector vecReflectivity = pTex->Reflectivity(); - Msg( "vtf reflectivity: %f %f %f\n", vecReflectivity[0], vecReflectivity[1], vecReflectivity[2] ); - Msg( "transparency: " ); - if( pTex->Flags() & TEXTUREFLAGS_EIGHTBITALPHA ) - { - Msg( "eightbitalpha\n" ); - } - else if( pTex->Flags() & TEXTUREFLAGS_ONEBITALPHA ) - { - Msg( "onebitalpha\n" ); - } - else - { - Msg( "noalpha\n" ); - } - ImageFormat srcFormat = pTex->Format(); - Msg( "vtf format: %s\n", ImageLoader::GetName( srcFormat ) ); - - int iTGANameLen = Q_strlen( pOutFileNameBase ); - - int iFaceCount = pTex->FaceCount(); - int nFrameCount = pTex->FrameCount(); - bool bIsCubeMap = pTex->IsCubeMap(); - - int iLastMipLevel = bGenerateMipLevels ? pTex->MipCount() - 1 : 0; - for( int iFrame = 0; iFrame < nFrameCount; ++iFrame ) - { - for ( int iMipLevel = 0; iMipLevel <= iLastMipLevel; ++iMipLevel ) - { - int iWidth, iHeight, iDepth; - pTex->ComputeMipLevelDimensions( iMipLevel, &iWidth, &iHeight, &iDepth ); - - for (int iCubeFace = 0; iCubeFace < iFaceCount; ++iCubeFace) - { - for ( int z = 0; z < iDepth; ++z ) - { - // Construct output filename - char *pTempNameBuf = (char *)stackalloc( iTGANameLen + 13 ); - Q_strncpy( pTempNameBuf, pOutFileNameBase, iTGANameLen + 1 ); - char *pExt = Q_strrchr( pTempNameBuf, '.' ); - if ( pExt ) - { - pExt = 0; - } - - if ( bIsCubeMap ) - { - Assert( pTex->Depth() == 1 ); // shouldn't this be 1 instead of 0? - static const char *pCubeFaceName[7] = { "rt", "lf", "bk", "ft", "up", "dn", "sph" }; - Q_strcat( pTempNameBuf, pCubeFaceName[iCubeFace], iTGANameLen + 13 ); - } - - if ( nFrameCount > 1 ) - { - char pTemp[4]; - Q_snprintf( pTemp, 4, "%03d", iFrame ); - Q_strcat( pTempNameBuf, pTemp, iTGANameLen + 13 ); - } - - if ( iLastMipLevel != 0 ) - { - char pTemp[8]; - Q_snprintf( pTemp, 8, "_mip%d", iMipLevel ); - Q_strcat( pTempNameBuf, pTemp, iTGANameLen + 13 ); - } - - if ( pTex->Depth() > 1 ) - { - char pTemp[6]; - Q_snprintf( pTemp, 6, "_z%03d", z ); - Q_strcat( pTempNameBuf, pTemp, iTGANameLen + 13 ); - } - - if( srcFormat == IMAGE_FORMAT_RGBA16161616F ) - { - Q_strcat( pTempNameBuf, ".pfm", iTGANameLen + 13 ); - } - else - { - Q_strcat( pTempNameBuf, ".tga", iTGANameLen + 13 ); - } - - unsigned char *pSrcImage = pTex->ImageData( iFrame, iCubeFace, iMipLevel, 0, 0, z ); - - ImageFormat dstFormat; - if( srcFormat == IMAGE_FORMAT_RGBA16161616F ) - { - dstFormat = IMAGE_FORMAT_RGB323232F; - } - else - { - if( ImageLoader::IsTransparent( srcFormat ) || (srcFormat == IMAGE_FORMAT_ATI1N ) || (srcFormat == IMAGE_FORMAT_ATI2N )) - { - dstFormat = IMAGE_FORMAT_BGRA8888; - } - else - { - dstFormat = IMAGE_FORMAT_BGR888; - } - } - // dstFormat = IMAGE_FORMAT_RGBA8888; - // dstFormat = IMAGE_FORMAT_RGB888; - // dstFormat = IMAGE_FORMAT_BGRA8888; - // dstFormat = IMAGE_FORMAT_BGR888; - // dstFormat = IMAGE_FORMAT_BGRA5551; - // dstFormat = IMAGE_FORMAT_BGR565; - // dstFormat = IMAGE_FORMAT_BGRA4444; - // printf( "dstFormat: %s\n", ImageLoader::GetName( dstFormat ) ); - unsigned char *pDstImage = new unsigned char[ImageLoader::GetMemRequired( iWidth, iHeight, 1, dstFormat, false )]; - if( !ImageLoader::ConvertImageFormat( pSrcImage, srcFormat, - pDstImage, dstFormat, iWidth, iHeight, 0, 0 ) ) - { - Error( "Error converting from %s to %s\n", - ImageLoader::GetName( srcFormat ), ImageLoader::GetName( dstFormat ) ); - exit( -1 ); - } - - if( dstFormat != IMAGE_FORMAT_RGB323232F ) - { - if( ImageLoader::IsTransparent( dstFormat ) && ( dstFormat != IMAGE_FORMAT_RGBA8888 ) ) - { - unsigned char *tmpImage = pDstImage; - pDstImage = new unsigned char[ImageLoader::GetMemRequired( iWidth, iHeight, 1, IMAGE_FORMAT_RGBA8888, false )]; - if( !ImageLoader::ConvertImageFormat( tmpImage, dstFormat, pDstImage, IMAGE_FORMAT_RGBA8888, - iWidth, iHeight, 0, 0 ) ) - { - Error( "Error converting from %s to %s\n", - ImageLoader::GetName( dstFormat ), ImageLoader::GetName( IMAGE_FORMAT_RGBA8888 ) ); - } - dstFormat = IMAGE_FORMAT_RGBA8888; - } - else if( !ImageLoader::IsTransparent( dstFormat ) && ( dstFormat != IMAGE_FORMAT_RGB888 ) ) - { - unsigned char *tmpImage = pDstImage; - pDstImage = new unsigned char[ImageLoader::GetMemRequired( iWidth, iHeight, 1, IMAGE_FORMAT_RGB888, false )]; - if( !ImageLoader::ConvertImageFormat( tmpImage, dstFormat, pDstImage, IMAGE_FORMAT_RGB888, - iWidth, iHeight, 0, 0 ) ) - { - Error( "Error converting from %s to %s\n", - ImageLoader::GetName( dstFormat ), ImageLoader::GetName( IMAGE_FORMAT_RGB888 ) ); - } - dstFormat = IMAGE_FORMAT_RGB888; - } - - CUtlBuffer outBuffer; - TGAWriter::WriteToBuffer( pDstImage, outBuffer, iWidth, iHeight, - dstFormat, dstFormat ); - if ( !g_pFullFileSystem->WriteFile( pTempNameBuf, NULL, outBuffer ) ) - { - fprintf( stderr, "unable to write %s\n", pTempNameBuf ); - } - } - else - { - PFMWrite( ( float * )pDstImage, pTempNameBuf, iWidth, iHeight ); - } - } - } - } - } - - // leak leak leak leak leak, leak leak, leak leak (Blue Danube) - return 0; -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include +#include "mathlib/mathlib.h" +#include "bitmap/tgawriter.h" +#include "tier1/strtools.h" +#include "vtf/vtf.h" +#include "tier1/UtlBuffer.h" +#include "tier0/dbg.h" +#include "tier0/icommandline.h" +#include "tier1/utlbuffer.h" +#include "tier2/tier2.h" +#include "filesystem.h" + + +//----------------------------------------------------------------------------- +// HDRFIXME: move this somewhere else. +//----------------------------------------------------------------------------- +static void PFMWrite( float *pFloatImage, const char *pFilename, int width, int height ) +{ + FILE *fp; + fp = fopen( pFilename, "wb" ); + fprintf( fp, "PF\n%d %d\n-1.000000\n", width, height ); + int i; + for( i = height-1; i >= 0; i-- ) + { + float *pRow = &pFloatImage[3 * width * i]; + fwrite( pRow, width * sizeof( float ) * 3, 1, fp ); + } + fclose( fp ); +} + +SpewRetval_t VTF2TGAOutputFunc( SpewType_t spewType, char const *pMsg ) +{ + printf( pMsg ); + fflush( stdout ); + + if (spewType == SPEW_ERROR) + return SPEW_ABORT; + return (spewType == SPEW_ASSERT) ? SPEW_DEBUGGER : SPEW_CONTINUE; +} + +static void Usage( void ) +{ + Error( "Usage: vtf2tga -i [-o ] [-mip]\n" ); + exit( -1 ); +} + +int main( int argc, char **argv ) +{ + SpewOutputFunc( VTF2TGAOutputFunc ); + CommandLine()->CreateCmdLine( argc, argv ); + MathLib_Init( 2.2f, 2.2f, 0.0f, 1.0f, false, false, false, false ); + InitDefaultFileSystem(); + + const char *pVTFFileName = CommandLine()->ParmValue( "-i" ); + const char *pTGAFileName = CommandLine()->ParmValue( "-o" ); + bool bGenerateMipLevels = CommandLine()->CheckParm( "-mip" ) != NULL; + if ( !pVTFFileName ) + { + Usage(); + } + + if ( !pTGAFileName ) + { + pTGAFileName = pVTFFileName; + } + + char pCurrentDirectory[MAX_PATH]; + if ( _getcwd( pCurrentDirectory, sizeof(pCurrentDirectory) ) == NULL ) + { + fprintf( stderr, "Unable to get the current directory\n" ); + return -1; + } + Q_StripTrailingSlash( pCurrentDirectory ); + + char pBuf[MAX_PATH]; + if ( !Q_IsAbsolutePath( pTGAFileName ) ) + { + Q_snprintf( pBuf, sizeof(pBuf), "%s\\%s", pCurrentDirectory, pTGAFileName ); + } + else + { + Q_strncpy( pBuf, pTGAFileName, sizeof(pBuf) ); + } + Q_FixSlashes( pBuf ); + + char pOutFileNameBase[MAX_PATH]; + Q_StripExtension( pBuf, pOutFileNameBase, MAX_PATH ); + + char pActualVTFFileName[MAX_PATH]; + Q_strncpy( pActualVTFFileName, pVTFFileName, MAX_PATH ); + if ( !Q_strstr( pActualVTFFileName, ".vtf" ) ) + { + Q_strcat( pActualVTFFileName, ".vtf", MAX_PATH ); + } + + FILE *vtfFp = fopen( pActualVTFFileName, "rb" ); + if( !vtfFp ) + { + Error( "Can't open %s\n", pActualVTFFileName ); + exit( -1 ); + } + + fseek( vtfFp, 0, SEEK_END ); + int srcVTFLength = ftell( vtfFp ); + fseek( vtfFp, 0, SEEK_SET ); + + CUtlBuffer buf; + buf.EnsureCapacity( srcVTFLength ); + int nBytesRead = fread( buf.Base(), 1, srcVTFLength, vtfFp ); + fclose( vtfFp ); + buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead ); + + IVTFTexture *pTex = CreateVTFTexture(); + if (!pTex->Unserialize( buf )) + { + Error( "*** Error reading in .VTF file %s\n", pActualVTFFileName ); + exit(-1); + } + + Msg( "vtf width: %d\n", pTex->Width() ); + Msg( "vtf height: %d\n", pTex->Height() ); + Msg( "vtf numFrames: %d\n", pTex->FrameCount() ); + + Msg( "TEXTUREFLAGS_POINTSAMPLE=%s\n", ( pTex->Flags() & TEXTUREFLAGS_POINTSAMPLE ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_TRILINEAR=%s\n", ( pTex->Flags() & TEXTUREFLAGS_TRILINEAR ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_CLAMPS=%s\n", ( pTex->Flags() & TEXTUREFLAGS_CLAMPS ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_CLAMPT=%s\n", ( pTex->Flags() & TEXTUREFLAGS_CLAMPT ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_CLAMPU=%s\n", ( pTex->Flags() & TEXTUREFLAGS_CLAMPU ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_BORDER=%s\n", ( pTex->Flags() & TEXTUREFLAGS_BORDER ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_ANISOTROPIC=%s\n", ( pTex->Flags() & TEXTUREFLAGS_ANISOTROPIC ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_HINT_DXT5=%s\n", ( pTex->Flags() & TEXTUREFLAGS_HINT_DXT5 ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_SRGB=%s\n", ( pTex->Flags() & TEXTUREFLAGS_SRGB ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_NORMAL=%s\n", ( pTex->Flags() & TEXTUREFLAGS_NORMAL ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_NOMIP=%s\n", ( pTex->Flags() & TEXTUREFLAGS_NOMIP ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_NOLOD=%s\n", ( pTex->Flags() & TEXTUREFLAGS_NOLOD ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_ALL_MIPS=%s\n", ( pTex->Flags() & TEXTUREFLAGS_ALL_MIPS ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_PROCEDURAL=%s\n", ( pTex->Flags() & TEXTUREFLAGS_PROCEDURAL ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_ONEBITALPHA=%s\n", ( pTex->Flags() & TEXTUREFLAGS_ONEBITALPHA ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_EIGHTBITALPHA=%s\n", ( pTex->Flags() & TEXTUREFLAGS_EIGHTBITALPHA ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_ENVMAP=%s\n", ( pTex->Flags() & TEXTUREFLAGS_ENVMAP ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_RENDERTARGET=%s\n", ( pTex->Flags() & TEXTUREFLAGS_RENDERTARGET ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_DEPTHRENDERTARGET=%s\n", ( pTex->Flags() & TEXTUREFLAGS_DEPTHRENDERTARGET ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_NODEBUGOVERRIDE=%s\n", ( pTex->Flags() & TEXTUREFLAGS_NODEBUGOVERRIDE ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_SINGLECOPY=%s\n", ( pTex->Flags() & TEXTUREFLAGS_SINGLECOPY ) ? "true" : "false" ); + + Vector vecReflectivity = pTex->Reflectivity(); + Msg( "vtf reflectivity: %f %f %f\n", vecReflectivity[0], vecReflectivity[1], vecReflectivity[2] ); + Msg( "transparency: " ); + if( pTex->Flags() & TEXTUREFLAGS_EIGHTBITALPHA ) + { + Msg( "eightbitalpha\n" ); + } + else if( pTex->Flags() & TEXTUREFLAGS_ONEBITALPHA ) + { + Msg( "onebitalpha\n" ); + } + else + { + Msg( "noalpha\n" ); + } + ImageFormat srcFormat = pTex->Format(); + Msg( "vtf format: %s\n", ImageLoader::GetName( srcFormat ) ); + + int iTGANameLen = Q_strlen( pOutFileNameBase ); + + int iFaceCount = pTex->FaceCount(); + int nFrameCount = pTex->FrameCount(); + bool bIsCubeMap = pTex->IsCubeMap(); + + int iLastMipLevel = bGenerateMipLevels ? pTex->MipCount() - 1 : 0; + for( int iFrame = 0; iFrame < nFrameCount; ++iFrame ) + { + for ( int iMipLevel = 0; iMipLevel <= iLastMipLevel; ++iMipLevel ) + { + int iWidth, iHeight, iDepth; + pTex->ComputeMipLevelDimensions( iMipLevel, &iWidth, &iHeight, &iDepth ); + + for (int iCubeFace = 0; iCubeFace < iFaceCount; ++iCubeFace) + { + for ( int z = 0; z < iDepth; ++z ) + { + // Construct output filename + char *pTempNameBuf = (char *)stackalloc( iTGANameLen + 13 ); + Q_strncpy( pTempNameBuf, pOutFileNameBase, iTGANameLen + 1 ); + char *pExt = Q_strrchr( pTempNameBuf, '.' ); + if ( pExt ) + { + pExt = 0; + } + + if ( bIsCubeMap ) + { + Assert( pTex->Depth() == 1 ); // shouldn't this be 1 instead of 0? + static const char *pCubeFaceName[7] = { "rt", "lf", "bk", "ft", "up", "dn", "sph" }; + Q_strcat( pTempNameBuf, pCubeFaceName[iCubeFace], iTGANameLen + 13 ); + } + + if ( nFrameCount > 1 ) + { + char pTemp[4]; + Q_snprintf( pTemp, 4, "%03d", iFrame ); + Q_strcat( pTempNameBuf, pTemp, iTGANameLen + 13 ); + } + + if ( iLastMipLevel != 0 ) + { + char pTemp[8]; + Q_snprintf( pTemp, 8, "_mip%d", iMipLevel ); + Q_strcat( pTempNameBuf, pTemp, iTGANameLen + 13 ); + } + + if ( pTex->Depth() > 1 ) + { + char pTemp[6]; + Q_snprintf( pTemp, 6, "_z%03d", z ); + Q_strcat( pTempNameBuf, pTemp, iTGANameLen + 13 ); + } + + if( srcFormat == IMAGE_FORMAT_RGBA16161616F ) + { + Q_strcat( pTempNameBuf, ".pfm", iTGANameLen + 13 ); + } + else + { + Q_strcat( pTempNameBuf, ".tga", iTGANameLen + 13 ); + } + + unsigned char *pSrcImage = pTex->ImageData( iFrame, iCubeFace, iMipLevel, 0, 0, z ); + + ImageFormat dstFormat; + if( srcFormat == IMAGE_FORMAT_RGBA16161616F ) + { + dstFormat = IMAGE_FORMAT_RGB323232F; + } + else + { + if( ImageLoader::IsTransparent( srcFormat ) || (srcFormat == IMAGE_FORMAT_ATI1N ) || (srcFormat == IMAGE_FORMAT_ATI2N )) + { + dstFormat = IMAGE_FORMAT_BGRA8888; + } + else + { + dstFormat = IMAGE_FORMAT_BGR888; + } + } + // dstFormat = IMAGE_FORMAT_RGBA8888; + // dstFormat = IMAGE_FORMAT_RGB888; + // dstFormat = IMAGE_FORMAT_BGRA8888; + // dstFormat = IMAGE_FORMAT_BGR888; + // dstFormat = IMAGE_FORMAT_BGRA5551; + // dstFormat = IMAGE_FORMAT_BGR565; + // dstFormat = IMAGE_FORMAT_BGRA4444; + // printf( "dstFormat: %s\n", ImageLoader::GetName( dstFormat ) ); + unsigned char *pDstImage = new unsigned char[ImageLoader::GetMemRequired( iWidth, iHeight, 1, dstFormat, false )]; + if( !ImageLoader::ConvertImageFormat( pSrcImage, srcFormat, + pDstImage, dstFormat, iWidth, iHeight, 0, 0 ) ) + { + Error( "Error converting from %s to %s\n", + ImageLoader::GetName( srcFormat ), ImageLoader::GetName( dstFormat ) ); + exit( -1 ); + } + + if( dstFormat != IMAGE_FORMAT_RGB323232F ) + { + if( ImageLoader::IsTransparent( dstFormat ) && ( dstFormat != IMAGE_FORMAT_RGBA8888 ) ) + { + unsigned char *tmpImage = pDstImage; + pDstImage = new unsigned char[ImageLoader::GetMemRequired( iWidth, iHeight, 1, IMAGE_FORMAT_RGBA8888, false )]; + if( !ImageLoader::ConvertImageFormat( tmpImage, dstFormat, pDstImage, IMAGE_FORMAT_RGBA8888, + iWidth, iHeight, 0, 0 ) ) + { + Error( "Error converting from %s to %s\n", + ImageLoader::GetName( dstFormat ), ImageLoader::GetName( IMAGE_FORMAT_RGBA8888 ) ); + } + dstFormat = IMAGE_FORMAT_RGBA8888; + } + else if( !ImageLoader::IsTransparent( dstFormat ) && ( dstFormat != IMAGE_FORMAT_RGB888 ) ) + { + unsigned char *tmpImage = pDstImage; + pDstImage = new unsigned char[ImageLoader::GetMemRequired( iWidth, iHeight, 1, IMAGE_FORMAT_RGB888, false )]; + if( !ImageLoader::ConvertImageFormat( tmpImage, dstFormat, pDstImage, IMAGE_FORMAT_RGB888, + iWidth, iHeight, 0, 0 ) ) + { + Error( "Error converting from %s to %s\n", + ImageLoader::GetName( dstFormat ), ImageLoader::GetName( IMAGE_FORMAT_RGB888 ) ); + } + dstFormat = IMAGE_FORMAT_RGB888; + } + + CUtlBuffer outBuffer; + TGAWriter::WriteToBuffer( pDstImage, outBuffer, iWidth, iHeight, + dstFormat, dstFormat ); + if ( !g_pFullFileSystem->WriteFile( pTempNameBuf, NULL, outBuffer ) ) + { + fprintf( stderr, "unable to write %s\n", pTempNameBuf ); + } + } + else + { + PFMWrite( ( float * )pDstImage, pTempNameBuf, iWidth, iHeight ); + } + } + } + } + } + + // leak leak leak leak leak, leak leak, leak leak (Blue Danube) + return 0; +} diff --git a/mp/src/utils/vtf2tga/vtf2tga.vpc b/mp/src/utils/vtf2tga/vtf2tga.vpc index b51ed3e1..adaf21d3 100644 --- a/mp/src/utils/vtf2tga/vtf2tga.vpc +++ b/mp/src/utils/vtf2tga/vtf2tga.vpc @@ -1,47 +1,47 @@ -//----------------------------------------------------------------------------- -// VTF2TGA.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" - -$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" - -$Project "Vtf2tga" -{ - $Folder "Source Files" - { - $File "vtf2tga.cpp" - } - - $Folder "Header Files" - { - $File "$SRCDIR\public\mathlib\amd3dx.h" - $File "$SRCDIR\public\tier0\basetypes.h" - $File "$SRCDIR\public\tier0\commonmacros.h" - $File "$SRCDIR\public\tier0\dbg.h" - $File "$SRCDIR\public\tier0\fasttimer.h" - $File "$SRCDIR\public\mathlib\mathlib.h" - $File "$SRCDIR\public\tier0\memdbgon.h" - $File "$SRCDIR\public\tier0\platform.h" - $File "$SRCDIR\public\tier0\protected_things.h" - $File "$SRCDIR\public\string_t.h" - $File "$SRCDIR\public\tier1\strtools.h" - $File "$SRCDIR\public\tier1\utlbuffer.h" - $File "$SRCDIR\public\tier1\utlmemory.h" - $File "$SRCDIR\public\mathlib\vector.h" - $File "$SRCDIR\public\mathlib\vector2d.h" - $File "$SRCDIR\public\vstdlib\vstdlib.h" - $File "$SRCDIR\public\vtf\vtf.h" - } - - $Folder "Link Libraries" - { - $Lib bitmap - $Lib mathlib - $Lib tier2 - $Lib vtf - } -} +//----------------------------------------------------------------------------- +// VTF2TGA.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Project "Vtf2tga" +{ + $Folder "Source Files" + { + $File "vtf2tga.cpp" + } + + $Folder "Header Files" + { + $File "$SRCDIR\public\mathlib\amd3dx.h" + $File "$SRCDIR\public\tier0\basetypes.h" + $File "$SRCDIR\public\tier0\commonmacros.h" + $File "$SRCDIR\public\tier0\dbg.h" + $File "$SRCDIR\public\tier0\fasttimer.h" + $File "$SRCDIR\public\mathlib\mathlib.h" + $File "$SRCDIR\public\tier0\memdbgon.h" + $File "$SRCDIR\public\tier0\platform.h" + $File "$SRCDIR\public\tier0\protected_things.h" + $File "$SRCDIR\public\string_t.h" + $File "$SRCDIR\public\tier1\strtools.h" + $File "$SRCDIR\public\tier1\utlbuffer.h" + $File "$SRCDIR\public\tier1\utlmemory.h" + $File "$SRCDIR\public\mathlib\vector.h" + $File "$SRCDIR\public\mathlib\vector2d.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + $File "$SRCDIR\public\vtf\vtf.h" + } + + $Folder "Link Libraries" + { + $Lib bitmap + $Lib mathlib + $Lib tier2 + $Lib vtf + } +} diff --git a/mp/src/utils/vtfdiff/vtfdiff.cpp b/mp/src/utils/vtfdiff/vtfdiff.cpp index bee0d777..068cb319 100644 --- a/mp/src/utils/vtfdiff/vtfdiff.cpp +++ b/mp/src/utils/vtfdiff/vtfdiff.cpp @@ -1,438 +1,438 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#include -#include -#include -#include -#include "vtf/vtf.h" -#include "tier1/UtlBuffer.h" -#include "tier1/utlmap.h" -#include "bitmap/imageformat.h" -#include "mathlib/vector.h" -#include - -void Usage( void ) -{ - printf( "Usage: vtfdiff file1.vtf file2.vtf\n" ); -} - -bool LoadFileIntoBuffer( const char *pFileName, CUtlBuffer &buf ) -{ - struct _stat statBuf; - if( _stat( pFileName, &statBuf ) != 0 ) - { - goto error; - } - - buf.EnsureCapacity( statBuf.st_size ); - FILE *fp; - fp = fopen( pFileName, "rb" ); - if( !fp ) - { - goto error; - } - - int nBytesRead = fread( buf.Base(), 1, statBuf.st_size, fp ); - fclose( fp ); - - buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead ); - return true; - -error: - printf( "Can't find file %s\n", pFileName ); - return false; -} - -char const * ResourceToString( uint32 uiResType ) -{ - static char chBuffer[256]; - - switch ( uiResType ) - { - case VTF_LEGACY_RSRC_LOW_RES_IMAGE: - return "VTF_LEGACY_RSRC_LOW_RES_IMAGE"; - case VTF_LEGACY_RSRC_IMAGE: - return "VTF_LEGACY_RSRC_IMAGE"; - case VTF_RSRC_SHEET: - return "VTF_RSRC_SHEET"; - case MK_VTF_RSRC_ID( 'C','R','C' ): - return "CRC"; - case VTF_RSRC_TEXTURE_LOD_SETTINGS: - return "VTF_RSRC_TEXTURE_LOD_SETTINGS"; - - default: - sprintf( chBuffer, "0x%08X", uiResType ); - return chBuffer; - } - - return chBuffer; -} - -void PrintFlags( int flags ) -{ -#define PRNFLAG( flagname ) \ - if ( ( flags & (flagname) ) == (flagname) ) \ - { \ - flags &=~ (flagname); \ - printf( "%s%s", #flagname + strlen("TEXTUREFLAGS_"), flags ? "|" : "" ); \ - } \ - - - PRNFLAG( TEXTUREFLAGS_POINTSAMPLE ) - PRNFLAG( TEXTUREFLAGS_TRILINEAR ) - PRNFLAG( TEXTUREFLAGS_CLAMPS ) - PRNFLAG( TEXTUREFLAGS_CLAMPT ) - PRNFLAG( TEXTUREFLAGS_ANISOTROPIC ) - PRNFLAG( TEXTUREFLAGS_HINT_DXT5 ) - PRNFLAG( TEXTUREFLAGS_SRGB ) - PRNFLAG( TEXTUREFLAGS_NORMAL ) - PRNFLAG( TEXTUREFLAGS_NOMIP ) - PRNFLAG( TEXTUREFLAGS_NOLOD ) - PRNFLAG( TEXTUREFLAGS_ALL_MIPS ) - PRNFLAG( TEXTUREFLAGS_PROCEDURAL ) - PRNFLAG( TEXTUREFLAGS_ONEBITALPHA ) - PRNFLAG( TEXTUREFLAGS_EIGHTBITALPHA ) - PRNFLAG( TEXTUREFLAGS_ENVMAP ) - PRNFLAG( TEXTUREFLAGS_RENDERTARGET ) - PRNFLAG( TEXTUREFLAGS_DEPTHRENDERTARGET ) - PRNFLAG( TEXTUREFLAGS_NODEBUGOVERRIDE ) - PRNFLAG( TEXTUREFLAGS_SINGLECOPY ) - PRNFLAG( TEXTUREFLAGS_UNUSED_00080000 ) - PRNFLAG( TEXTUREFLAGS_UNUSED_00100000 ) - PRNFLAG( TEXTUREFLAGS_UNUSED_00200000 ) - PRNFLAG( TEXTUREFLAGS_UNUSED_00400000 ) - PRNFLAG( TEXTUREFLAGS_NODEPTHBUFFER ) - PRNFLAG( TEXTUREFLAGS_UNUSED_01000000 ) - PRNFLAG( TEXTUREFLAGS_CLAMPU ) - PRNFLAG( TEXTUREFLAGS_VERTEXTEXTURE ) - PRNFLAG( TEXTUREFLAGS_SSBUMP ) - PRNFLAG( TEXTUREFLAGS_UNUSED_10000000 ) - PRNFLAG( TEXTUREFLAGS_BORDER ) - PRNFLAG( TEXTUREFLAGS_UNUSED_40000000 ) - PRNFLAG( TEXTUREFLAGS_UNUSED_80000000 ) - -#undef PRNFLAG - - if ( flags ) - { - printf( "0x%08X", flags ); - } -} - -int main( int argc, char **argv ) -{ - if( argc != 3 ) - { - Usage(); - return 10; - } - - CUtlBuffer file1; - CUtlBuffer file2; - - if ( !LoadFileIntoBuffer( argv[1], file1 ) ) - return 21; - if ( !LoadFileIntoBuffer( argv[2], file2 ) ) - return 22; - - IVTFTexture *pTexture1 = CreateVTFTexture(); - IVTFTexture *pTexture2 = CreateVTFTexture(); - - IVTFTexture *arrTextures[2] = { pTexture1, pTexture2 }; - - bool bMatch = true; - - if( !pTexture1->Unserialize( file1 ) ) - { - printf( "error loading %s\n", argv[1] ); - return 31; - } - if( !pTexture2->Unserialize( file2 ) ) - { - printf( "error loading %s\n", argv[2] ); - return 32; - } - - if( pTexture1->Width() != pTexture2->Width() || - pTexture1->Height() != pTexture2->Height() || - pTexture1->Depth() != pTexture2->Depth() ) - { - printf( "%s dimensions differ: %dx%dx%d != %dx%dx%d\n", - argv[1], - ( int )pTexture1->Width(), ( int )pTexture1->Height(), ( int )pTexture1->Depth(), - ( int )pTexture2->Width(), ( int )pTexture2->Height(), ( int )pTexture2->Depth() ); - bMatch = false; - } - - if( pTexture1->LowResWidth() != pTexture2->LowResWidth() || - pTexture1->LowResHeight() != pTexture2->LowResHeight() ) - { - printf( "%s lowres dimensions differ: %dx%d != %dx%d\n", - argv[1], - ( int )pTexture1->LowResWidth(), ( int )pTexture1->LowResHeight(), - ( int )pTexture2->LowResWidth(), ( int )pTexture2->LowResHeight() ); - bMatch = false; - } - - if( pTexture1->MipCount() != pTexture2->MipCount() ) - { - printf( "%s differing mipcounts: %d != %d\n", - argv[1], - ( int )pTexture1->MipCount(), ( int )pTexture2->MipCount() ); - bMatch = false; - } - - if( pTexture1->FaceCount() != pTexture2->FaceCount() ) - { - printf( "%s differing facecount: %d != %d\n", - argv[1], - ( int )pTexture1->FaceCount(), ( int )pTexture2->FaceCount() ); - bMatch = false; - } - - if( pTexture1->FrameCount() != pTexture2->FrameCount() ) - { - printf( "%s differing framecount: %d != %d\n", - argv[1], - ( int )pTexture1->FrameCount(), ( int )pTexture2->FrameCount() ); - bMatch = false; - } - - if( pTexture1->Flags() != pTexture2->Flags() ) - { - printf( "%s differing flags: \"", - argv[1] ); - PrintFlags( pTexture1->Flags() ); - printf( "\" != \"" ); - PrintFlags( pTexture2->Flags() ); - printf( "\"\n" ); - bMatch = false; - } - - if( pTexture1->BumpScale() != pTexture2->BumpScale() ) - { - printf( "%s differing bumpscale: %f != %f\n", - argv[1], - ( float )pTexture1->BumpScale(), ( float )pTexture2->BumpScale() ); - bMatch = false; - } - - if( pTexture1->Format() != pTexture2->Format() ) - { - printf( "%s differing image format: %s != %s\n", - argv[1], - ImageLoader::GetName( pTexture1->Format() ), - ImageLoader::GetName( pTexture2->Format() ) ); - bMatch = false; - } - - if( pTexture1->LowResFormat() != pTexture2->LowResFormat() ) - { - Assert(0); - printf( "%s differing lowres image format: %s != %s\n", - argv[1], - ImageLoader::GetName( pTexture1->LowResFormat() ), - ImageLoader::GetName( pTexture2->LowResFormat() ) ); - bMatch = false; - } - - const Vector &vReflectivity1 = pTexture1->Reflectivity(); - const Vector &vReflectivity2 = pTexture2->Reflectivity(); - if( !VectorsAreEqual( vReflectivity1, vReflectivity2, 0.0001f ) ) - { - printf( "%s differing reflectivity: [%f,%f,%f] != [%f,%f,%f]\n", - argv[1], - ( float )pTexture1->Reflectivity()[0], - ( float )pTexture1->Reflectivity()[1], - ( float )pTexture1->Reflectivity()[2], - ( float )pTexture2->Reflectivity()[0], - ( float )pTexture2->Reflectivity()[1], - ( float )pTexture2->Reflectivity()[2] ); - bMatch = false; - } - - if ( pTexture1->ComputeTotalSize() != pTexture2->ComputeTotalSize() ) - { - printf( "%s differing image data size: %d != %d\n", - argv[1], - ( int )pTexture1->ComputeTotalSize(), ( int )pTexture2->ComputeTotalSize() ); - bMatch = false; - } - - if ( bMatch ) - { - unsigned char const *pData1 = pTexture1->ImageData(); - unsigned char const *pData2 = pTexture2->ImageData(); - - int const iSize = pTexture1->ComputeTotalSize(); - - if( memcmp( pData1, pData2, iSize) != 0 ) - { - printf( "%s image data different\n", argv[1] ); - - if (( pTexture1->Format() == IMAGE_FORMAT_DXT1 ) || ( pTexture1->Format() == IMAGE_FORMAT_DXT3 ) || - ( pTexture1->Format() == IMAGE_FORMAT_DXT5 ) || ( pTexture1->Format() == IMAGE_FORMAT_ATI2N ) || - ( pTexture1->Format() == IMAGE_FORMAT_ATI1N ) ) - { - int i, numOffsetsComplained = 0; - for( i = 0; i < iSize; i++ ) - { - if( pData1[i] != pData2[i] ) - { - printf( "image data at offset %d different\n", i ); - if ( numOffsetsComplained ++ > 10 ) - { - printf( "image data significantly differs!\n" ); - break; - } - } - } - } - else - { - for( int iFrame = 0; iFrame < pTexture1->FrameCount(); ++iFrame ) - { - for ( int iMipLevel = 0; iMipLevel < pTexture1->MipCount(); ++iMipLevel ) - { - int nMipWidth, nMipHeight, nMipDepth; - pTexture1->ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth ); - - for (int iCubeFace = 0; iCubeFace < pTexture1->FrameCount(); ++iCubeFace) - { - for ( int z = 0; z < nMipDepth; ++z ) - { - pData1 = pTexture1->ImageData( iFrame, iCubeFace, iMipLevel, 0, 0, z ); - pData2 = pTexture2->ImageData( iFrame, iCubeFace, iMipLevel, 0, 0, z ); - - int nMipSize = pTexture1->ComputeMipSize( iMipLevel ); - if ( memcmp( pData1, pData2, nMipSize ) ) - { - bool bBreak = false; - - for ( int y = 0; y < nMipHeight; ++y ) - { - for ( int x = 0; x < nMipWidth; ++x ) - { - unsigned char const *pData1a = pTexture1->ImageData( iFrame, iCubeFace, iMipLevel, x, y, z ); - unsigned char const *pData2a = pTexture2->ImageData( iFrame, iCubeFace, iMipLevel, x, y, z ); - - if ( memcmp( pData1a, pData2a, ImageLoader::SizeInBytes( pTexture1->Format() ) ) ) - { - printf( "Frame %d Mip level %d Face %d Z-slice %d texel (%d,%d) different!\n", - iFrame, iMipLevel, iCubeFace, z, x, y ); - bBreak = true; - break; - } - } - - if ( bBreak ) - break; - } - } - } - } - } - } - } - - bMatch = false; - } - } - - // Lowres data - { - int iDummy, iSize1, iSize2; - pTexture1->LowResFileInfo( &iDummy, &iSize1 ); - pTexture2->LowResFileInfo( &iDummy, &iSize2 ); - - if ( iSize1 != iSize2 ) - { - printf( "%s differing low res image data size: %d != %d\n", argv[1], iSize1, iSize2 ); - bMatch = false; - } - - if ( bMatch ) - { - if ( memcmp( pTexture1->LowResImageData(), pTexture2->LowResImageData(), iSize1 ) != 0 ) - { - printf( "%s differing low res image data\n", - argv[1] ); - bMatch = false; - } - } - } - - // Check other resources - { - int numRes1 = pTexture1->GetResourceTypes( NULL, 0 ); - int numRes2 = pTexture2->GetResourceTypes( NULL, 0 ); - - // List of resource types checked or insignificant diffs - typedef CUtlMap< int, bool > MapResTypes; - MapResTypes mapTypes( DefLessFunc( int ) ); - mapTypes.Insert( VTF_LEGACY_RSRC_LOW_RES_IMAGE, true ); - mapTypes.Insert( VTF_LEGACY_RSRC_IMAGE, true ); - mapTypes.Insert( MK_VTF_RSRC_ID( 'C','R','C' ), true ); - - uint32 *puiresbuffer = ( uint32 * ) stackalloc( ( numRes1 + numRes2 ) * sizeof( uint32 ) ); - - int arrNums[2] = { numRes1, numRes2 }; - - for ( int itx = 0; itx < 2; ++ itx ) - { - arrTextures[itx]->GetResourceTypes( puiresbuffer, arrNums[itx] ); - while ( arrNums[itx] --> 0 ) - { - uint32 uiResType = puiresbuffer[ arrNums[itx] ]; - if ( mapTypes.Find( uiResType ) != mapTypes.InvalidIndex() ) - continue; - - mapTypes.Insert( uiResType, true ); - - size_t numBytes1, numBytes2; - void const *pvResData1 = pTexture1->GetResourceData( uiResType, &numBytes1 ); - void const *pvResData2 = pTexture2->GetResourceData( uiResType, &numBytes2 ); - - if ( !pvResData1 != !pvResData2 ) - { - printf( "%s different resource %s %s\n", - argv[1], - ResourceToString( uiResType ), - pvResData1 ? "present" : "missing" ); - bMatch = false; - } - else if ( numBytes1 != numBytes2 ) - { - printf( "%s different resource %s size %lld != %lld\n", - argv[1], - ResourceToString( uiResType ), - (long long)numBytes1, (long long)numBytes2 ); - bMatch = false; - } - else if ( memcmp( pvResData1, pvResData2, numBytes1 ) != 0 ) - { - printf( "%s different resource %s data\n", - argv[1], - ResourceToString( uiResType ) ); - bMatch = false; - } - } - } - } - - - - if( bMatch ) - { - return 0; - } - else - { - return 1; - } -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include +#include +#include +#include +#include "vtf/vtf.h" +#include "tier1/UtlBuffer.h" +#include "tier1/utlmap.h" +#include "bitmap/imageformat.h" +#include "mathlib/vector.h" +#include + +void Usage( void ) +{ + printf( "Usage: vtfdiff file1.vtf file2.vtf\n" ); +} + +bool LoadFileIntoBuffer( const char *pFileName, CUtlBuffer &buf ) +{ + struct _stat statBuf; + if( _stat( pFileName, &statBuf ) != 0 ) + { + goto error; + } + + buf.EnsureCapacity( statBuf.st_size ); + FILE *fp; + fp = fopen( pFileName, "rb" ); + if( !fp ) + { + goto error; + } + + int nBytesRead = fread( buf.Base(), 1, statBuf.st_size, fp ); + fclose( fp ); + + buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead ); + return true; + +error: + printf( "Can't find file %s\n", pFileName ); + return false; +} + +char const * ResourceToString( uint32 uiResType ) +{ + static char chBuffer[256]; + + switch ( uiResType ) + { + case VTF_LEGACY_RSRC_LOW_RES_IMAGE: + return "VTF_LEGACY_RSRC_LOW_RES_IMAGE"; + case VTF_LEGACY_RSRC_IMAGE: + return "VTF_LEGACY_RSRC_IMAGE"; + case VTF_RSRC_SHEET: + return "VTF_RSRC_SHEET"; + case MK_VTF_RSRC_ID( 'C','R','C' ): + return "CRC"; + case VTF_RSRC_TEXTURE_LOD_SETTINGS: + return "VTF_RSRC_TEXTURE_LOD_SETTINGS"; + + default: + sprintf( chBuffer, "0x%08X", uiResType ); + return chBuffer; + } + + return chBuffer; +} + +void PrintFlags( int flags ) +{ +#define PRNFLAG( flagname ) \ + if ( ( flags & (flagname) ) == (flagname) ) \ + { \ + flags &=~ (flagname); \ + printf( "%s%s", #flagname + strlen("TEXTUREFLAGS_"), flags ? "|" : "" ); \ + } \ + + + PRNFLAG( TEXTUREFLAGS_POINTSAMPLE ) + PRNFLAG( TEXTUREFLAGS_TRILINEAR ) + PRNFLAG( TEXTUREFLAGS_CLAMPS ) + PRNFLAG( TEXTUREFLAGS_CLAMPT ) + PRNFLAG( TEXTUREFLAGS_ANISOTROPIC ) + PRNFLAG( TEXTUREFLAGS_HINT_DXT5 ) + PRNFLAG( TEXTUREFLAGS_SRGB ) + PRNFLAG( TEXTUREFLAGS_NORMAL ) + PRNFLAG( TEXTUREFLAGS_NOMIP ) + PRNFLAG( TEXTUREFLAGS_NOLOD ) + PRNFLAG( TEXTUREFLAGS_ALL_MIPS ) + PRNFLAG( TEXTUREFLAGS_PROCEDURAL ) + PRNFLAG( TEXTUREFLAGS_ONEBITALPHA ) + PRNFLAG( TEXTUREFLAGS_EIGHTBITALPHA ) + PRNFLAG( TEXTUREFLAGS_ENVMAP ) + PRNFLAG( TEXTUREFLAGS_RENDERTARGET ) + PRNFLAG( TEXTUREFLAGS_DEPTHRENDERTARGET ) + PRNFLAG( TEXTUREFLAGS_NODEBUGOVERRIDE ) + PRNFLAG( TEXTUREFLAGS_SINGLECOPY ) + PRNFLAG( TEXTUREFLAGS_UNUSED_00080000 ) + PRNFLAG( TEXTUREFLAGS_UNUSED_00100000 ) + PRNFLAG( TEXTUREFLAGS_UNUSED_00200000 ) + PRNFLAG( TEXTUREFLAGS_UNUSED_00400000 ) + PRNFLAG( TEXTUREFLAGS_NODEPTHBUFFER ) + PRNFLAG( TEXTUREFLAGS_UNUSED_01000000 ) + PRNFLAG( TEXTUREFLAGS_CLAMPU ) + PRNFLAG( TEXTUREFLAGS_VERTEXTEXTURE ) + PRNFLAG( TEXTUREFLAGS_SSBUMP ) + PRNFLAG( TEXTUREFLAGS_UNUSED_10000000 ) + PRNFLAG( TEXTUREFLAGS_BORDER ) + PRNFLAG( TEXTUREFLAGS_UNUSED_40000000 ) + PRNFLAG( TEXTUREFLAGS_UNUSED_80000000 ) + +#undef PRNFLAG + + if ( flags ) + { + printf( "0x%08X", flags ); + } +} + +int main( int argc, char **argv ) +{ + if( argc != 3 ) + { + Usage(); + return 10; + } + + CUtlBuffer file1; + CUtlBuffer file2; + + if ( !LoadFileIntoBuffer( argv[1], file1 ) ) + return 21; + if ( !LoadFileIntoBuffer( argv[2], file2 ) ) + return 22; + + IVTFTexture *pTexture1 = CreateVTFTexture(); + IVTFTexture *pTexture2 = CreateVTFTexture(); + + IVTFTexture *arrTextures[2] = { pTexture1, pTexture2 }; + + bool bMatch = true; + + if( !pTexture1->Unserialize( file1 ) ) + { + printf( "error loading %s\n", argv[1] ); + return 31; + } + if( !pTexture2->Unserialize( file2 ) ) + { + printf( "error loading %s\n", argv[2] ); + return 32; + } + + if( pTexture1->Width() != pTexture2->Width() || + pTexture1->Height() != pTexture2->Height() || + pTexture1->Depth() != pTexture2->Depth() ) + { + printf( "%s dimensions differ: %dx%dx%d != %dx%dx%d\n", + argv[1], + ( int )pTexture1->Width(), ( int )pTexture1->Height(), ( int )pTexture1->Depth(), + ( int )pTexture2->Width(), ( int )pTexture2->Height(), ( int )pTexture2->Depth() ); + bMatch = false; + } + + if( pTexture1->LowResWidth() != pTexture2->LowResWidth() || + pTexture1->LowResHeight() != pTexture2->LowResHeight() ) + { + printf( "%s lowres dimensions differ: %dx%d != %dx%d\n", + argv[1], + ( int )pTexture1->LowResWidth(), ( int )pTexture1->LowResHeight(), + ( int )pTexture2->LowResWidth(), ( int )pTexture2->LowResHeight() ); + bMatch = false; + } + + if( pTexture1->MipCount() != pTexture2->MipCount() ) + { + printf( "%s differing mipcounts: %d != %d\n", + argv[1], + ( int )pTexture1->MipCount(), ( int )pTexture2->MipCount() ); + bMatch = false; + } + + if( pTexture1->FaceCount() != pTexture2->FaceCount() ) + { + printf( "%s differing facecount: %d != %d\n", + argv[1], + ( int )pTexture1->FaceCount(), ( int )pTexture2->FaceCount() ); + bMatch = false; + } + + if( pTexture1->FrameCount() != pTexture2->FrameCount() ) + { + printf( "%s differing framecount: %d != %d\n", + argv[1], + ( int )pTexture1->FrameCount(), ( int )pTexture2->FrameCount() ); + bMatch = false; + } + + if( pTexture1->Flags() != pTexture2->Flags() ) + { + printf( "%s differing flags: \"", + argv[1] ); + PrintFlags( pTexture1->Flags() ); + printf( "\" != \"" ); + PrintFlags( pTexture2->Flags() ); + printf( "\"\n" ); + bMatch = false; + } + + if( pTexture1->BumpScale() != pTexture2->BumpScale() ) + { + printf( "%s differing bumpscale: %f != %f\n", + argv[1], + ( float )pTexture1->BumpScale(), ( float )pTexture2->BumpScale() ); + bMatch = false; + } + + if( pTexture1->Format() != pTexture2->Format() ) + { + printf( "%s differing image format: %s != %s\n", + argv[1], + ImageLoader::GetName( pTexture1->Format() ), + ImageLoader::GetName( pTexture2->Format() ) ); + bMatch = false; + } + + if( pTexture1->LowResFormat() != pTexture2->LowResFormat() ) + { + Assert(0); + printf( "%s differing lowres image format: %s != %s\n", + argv[1], + ImageLoader::GetName( pTexture1->LowResFormat() ), + ImageLoader::GetName( pTexture2->LowResFormat() ) ); + bMatch = false; + } + + const Vector &vReflectivity1 = pTexture1->Reflectivity(); + const Vector &vReflectivity2 = pTexture2->Reflectivity(); + if( !VectorsAreEqual( vReflectivity1, vReflectivity2, 0.0001f ) ) + { + printf( "%s differing reflectivity: [%f,%f,%f] != [%f,%f,%f]\n", + argv[1], + ( float )pTexture1->Reflectivity()[0], + ( float )pTexture1->Reflectivity()[1], + ( float )pTexture1->Reflectivity()[2], + ( float )pTexture2->Reflectivity()[0], + ( float )pTexture2->Reflectivity()[1], + ( float )pTexture2->Reflectivity()[2] ); + bMatch = false; + } + + if ( pTexture1->ComputeTotalSize() != pTexture2->ComputeTotalSize() ) + { + printf( "%s differing image data size: %d != %d\n", + argv[1], + ( int )pTexture1->ComputeTotalSize(), ( int )pTexture2->ComputeTotalSize() ); + bMatch = false; + } + + if ( bMatch ) + { + unsigned char const *pData1 = pTexture1->ImageData(); + unsigned char const *pData2 = pTexture2->ImageData(); + + int const iSize = pTexture1->ComputeTotalSize(); + + if( memcmp( pData1, pData2, iSize) != 0 ) + { + printf( "%s image data different\n", argv[1] ); + + if (( pTexture1->Format() == IMAGE_FORMAT_DXT1 ) || ( pTexture1->Format() == IMAGE_FORMAT_DXT3 ) || + ( pTexture1->Format() == IMAGE_FORMAT_DXT5 ) || ( pTexture1->Format() == IMAGE_FORMAT_ATI2N ) || + ( pTexture1->Format() == IMAGE_FORMAT_ATI1N ) ) + { + int i, numOffsetsComplained = 0; + for( i = 0; i < iSize; i++ ) + { + if( pData1[i] != pData2[i] ) + { + printf( "image data at offset %d different\n", i ); + if ( numOffsetsComplained ++ > 10 ) + { + printf( "image data significantly differs!\n" ); + break; + } + } + } + } + else + { + for( int iFrame = 0; iFrame < pTexture1->FrameCount(); ++iFrame ) + { + for ( int iMipLevel = 0; iMipLevel < pTexture1->MipCount(); ++iMipLevel ) + { + int nMipWidth, nMipHeight, nMipDepth; + pTexture1->ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth ); + + for (int iCubeFace = 0; iCubeFace < pTexture1->FrameCount(); ++iCubeFace) + { + for ( int z = 0; z < nMipDepth; ++z ) + { + pData1 = pTexture1->ImageData( iFrame, iCubeFace, iMipLevel, 0, 0, z ); + pData2 = pTexture2->ImageData( iFrame, iCubeFace, iMipLevel, 0, 0, z ); + + int nMipSize = pTexture1->ComputeMipSize( iMipLevel ); + if ( memcmp( pData1, pData2, nMipSize ) ) + { + bool bBreak = false; + + for ( int y = 0; y < nMipHeight; ++y ) + { + for ( int x = 0; x < nMipWidth; ++x ) + { + unsigned char const *pData1a = pTexture1->ImageData( iFrame, iCubeFace, iMipLevel, x, y, z ); + unsigned char const *pData2a = pTexture2->ImageData( iFrame, iCubeFace, iMipLevel, x, y, z ); + + if ( memcmp( pData1a, pData2a, ImageLoader::SizeInBytes( pTexture1->Format() ) ) ) + { + printf( "Frame %d Mip level %d Face %d Z-slice %d texel (%d,%d) different!\n", + iFrame, iMipLevel, iCubeFace, z, x, y ); + bBreak = true; + break; + } + } + + if ( bBreak ) + break; + } + } + } + } + } + } + } + + bMatch = false; + } + } + + // Lowres data + { + int iDummy, iSize1, iSize2; + pTexture1->LowResFileInfo( &iDummy, &iSize1 ); + pTexture2->LowResFileInfo( &iDummy, &iSize2 ); + + if ( iSize1 != iSize2 ) + { + printf( "%s differing low res image data size: %d != %d\n", argv[1], iSize1, iSize2 ); + bMatch = false; + } + + if ( bMatch ) + { + if ( memcmp( pTexture1->LowResImageData(), pTexture2->LowResImageData(), iSize1 ) != 0 ) + { + printf( "%s differing low res image data\n", + argv[1] ); + bMatch = false; + } + } + } + + // Check other resources + { + int numRes1 = pTexture1->GetResourceTypes( NULL, 0 ); + int numRes2 = pTexture2->GetResourceTypes( NULL, 0 ); + + // List of resource types checked or insignificant diffs + typedef CUtlMap< int, bool > MapResTypes; + MapResTypes mapTypes( DefLessFunc( int ) ); + mapTypes.Insert( VTF_LEGACY_RSRC_LOW_RES_IMAGE, true ); + mapTypes.Insert( VTF_LEGACY_RSRC_IMAGE, true ); + mapTypes.Insert( MK_VTF_RSRC_ID( 'C','R','C' ), true ); + + uint32 *puiresbuffer = ( uint32 * ) stackalloc( ( numRes1 + numRes2 ) * sizeof( uint32 ) ); + + int arrNums[2] = { numRes1, numRes2 }; + + for ( int itx = 0; itx < 2; ++ itx ) + { + arrTextures[itx]->GetResourceTypes( puiresbuffer, arrNums[itx] ); + while ( arrNums[itx] --> 0 ) + { + uint32 uiResType = puiresbuffer[ arrNums[itx] ]; + if ( mapTypes.Find( uiResType ) != mapTypes.InvalidIndex() ) + continue; + + mapTypes.Insert( uiResType, true ); + + size_t numBytes1, numBytes2; + void const *pvResData1 = pTexture1->GetResourceData( uiResType, &numBytes1 ); + void const *pvResData2 = pTexture2->GetResourceData( uiResType, &numBytes2 ); + + if ( !pvResData1 != !pvResData2 ) + { + printf( "%s different resource %s %s\n", + argv[1], + ResourceToString( uiResType ), + pvResData1 ? "present" : "missing" ); + bMatch = false; + } + else if ( numBytes1 != numBytes2 ) + { + printf( "%s different resource %s size %lld != %lld\n", + argv[1], + ResourceToString( uiResType ), + (long long)numBytes1, (long long)numBytes2 ); + bMatch = false; + } + else if ( memcmp( pvResData1, pvResData2, numBytes1 ) != 0 ) + { + printf( "%s different resource %s data\n", + argv[1], + ResourceToString( uiResType ) ); + bMatch = false; + } + } + } + } + + + + if( bMatch ) + { + return 0; + } + else + { + return 1; + } +} diff --git a/mp/src/utils/vtfdiff/vtfdiff.vpc b/mp/src/utils/vtfdiff/vtfdiff.vpc index 7ed8be34..da8f680d 100644 --- a/mp/src/utils/vtfdiff/vtfdiff.vpc +++ b/mp/src/utils/vtfdiff/vtfdiff.vpc @@ -1,26 +1,26 @@ -//----------------------------------------------------------------------------- -// VTFDIFF.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" - -$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" - -$Project "Vtfdiff" -{ - $Folder "Source Files" - { - $File "vtfdiff.cpp" - } - - $Folder "Link Libraries" - { - $Lib bitmap - $Lib mathlib - $Lib tier2 - $Lib vtf - } -} +//----------------------------------------------------------------------------- +// VTFDIFF.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Project "Vtfdiff" +{ + $Folder "Source Files" + { + $File "vtfdiff.cpp" + } + + $Folder "Link Libraries" + { + $Lib bitmap + $Lib mathlib + $Lib tier2 + $Lib vtf + } +} diff --git a/mp/src/utils/vvis/WaterDist.cpp b/mp/src/utils/vvis/WaterDist.cpp index 5ed380c8..933e178f 100644 --- a/mp/src/utils/vvis/WaterDist.cpp +++ b/mp/src/utils/vvis/WaterDist.cpp @@ -1,30 +1,30 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#include "bsplib.h" - -// input: -// from bsplib.h: -// numleafs -// dleafs - -void EmitDistanceToWaterInfo( void ) -{ - int leafID; - for( leafID = 0; leafID < numleafs; leafID++ ) - { - dleaf_t *pLeaf = &dleafs[leafID]; - if( pLeaf->leafWaterDataID == -1 ) - { - // FIXME: set the distance to water to infinity here just in case. - continue; - } - - // Get the vis set for this leaf. - - } -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "bsplib.h" + +// input: +// from bsplib.h: +// numleafs +// dleafs + +void EmitDistanceToWaterInfo( void ) +{ + int leafID; + for( leafID = 0; leafID < numleafs; leafID++ ) + { + dleaf_t *pLeaf = &dleafs[leafID]; + if( pLeaf->leafWaterDataID == -1 ) + { + // FIXME: set the distance to water to infinity here just in case. + continue; + } + + // Get the vis set for this leaf. + + } +} + diff --git a/mp/src/utils/vvis/flow.cpp b/mp/src/utils/vvis/flow.cpp index 7234e68a..716fc1ae 100644 --- a/mp/src/utils/vvis/flow.cpp +++ b/mp/src/utils/vvis/flow.cpp @@ -1,881 +1,881 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -#include "vis.h" -#include "vmpi.h" - -int g_TraceClusterStart = -1; -int g_TraceClusterStop = -1; -/* - - each portal will have a list of all possible to see from first portal - - if (!thread->portalmightsee[portalnum]) - - portal mightsee - - for p2 = all other portals in leaf - get sperating planes - for all portals that might be seen by p2 - mark as unseen if not present in seperating plane - flood fill a new mightsee - save as passagemightsee - - - void CalcMightSee (leaf_t *leaf, -*/ - -int CountBits (byte *bits, int numbits) -{ - int i; - int c; - - c = 0; - for (i=0 ; ipstack_head.next ; p ; p=p->next) - { -// Msg ("="); - if (p->leaf == leaf) - Error ("CheckStack: leaf recursion"); - for (p2=thread->pstack_head.next ; p2 != p ; p2=p2->next) - if (p2->leaf == p->leaf) - Error ("CheckStack: late leaf recursion"); - } -// Msg ("\n"); -} - - -winding_t *AllocStackWinding (pstack_t *stack) -{ - int i; - - for (i=0 ; i<3 ; i++) - { - if (stack->freewindings[i]) - { - stack->freewindings[i] = 0; - return &stack->windings[i]; - } - } - - Error ("Out of memory. AllocStackWinding: failed"); - - return NULL; -} - -void FreeStackWinding (winding_t *w, pstack_t *stack) -{ - int i; - - i = w - stack->windings; - - if (i<0 || i>2) - return; // not from local - - if (stack->freewindings[i]) - Error ("FreeStackWinding: allready free"); - stack->freewindings[i] = 1; -} - -/* -============== -ChopWinding - -============== -*/ - -#ifdef _WIN32 -#pragma warning (disable:4701) -#endif - -winding_t *ChopWinding (winding_t *in, pstack_t *stack, plane_t *split) -{ - vec_t dists[128]; - int sides[128]; - int counts[3]; - vec_t dot; - int i, j; - Vector mid; - winding_t *neww; - - counts[0] = counts[1] = counts[2] = 0; - -// determine sides for each point - for (i=0 ; inumpoints ; i++) - { - dot = DotProduct (in->points[i], split->normal); - dot -= split->dist; - dists[i] = dot; - if (dot > ON_VIS_EPSILON) - sides[i] = SIDE_FRONT; - else if (dot < -ON_VIS_EPSILON) - sides[i] = SIDE_BACK; - else - { - sides[i] = SIDE_ON; - } - counts[sides[i]]++; - } - - if (!counts[1]) - return in; // completely on front side - - if (!counts[0]) - { - FreeStackWinding (in, stack); - return NULL; - } - - sides[i] = sides[0]; - dists[i] = dists[0]; - - neww = AllocStackWinding (stack); - - neww->numpoints = 0; - - for (i=0 ; inumpoints ; i++) - { - Vector& p1 = in->points[i]; - - if (neww->numpoints == MAX_POINTS_ON_FIXED_WINDING) - { - FreeStackWinding (neww, stack); - return in; // can't chop -- fall back to original - } - - if (sides[i] == SIDE_ON) - { - VectorCopy (p1, neww->points[neww->numpoints]); - neww->numpoints++; - continue; - } - - if (sides[i] == SIDE_FRONT) - { - VectorCopy (p1, neww->points[neww->numpoints]); - neww->numpoints++; - } - - if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) - continue; - - if (neww->numpoints == MAX_POINTS_ON_FIXED_WINDING) - { - FreeStackWinding (neww, stack); - return in; // can't chop -- fall back to original - } - - // generate a split point - Vector& p2 = in->points[(i+1)%in->numpoints]; - - dot = dists[i] / (dists[i]-dists[i+1]); - for (j=0 ; j<3 ; j++) - { // avoid round off error when possible - if (split->normal[j] == 1) - mid[j] = split->dist; - else if (split->normal[j] == -1) - mid[j] = -split->dist; - else - mid[j] = p1[j] + dot*(p2[j]-p1[j]); - } - - VectorCopy (mid, neww->points[neww->numpoints]); - neww->numpoints++; - } - -// free the original winding - FreeStackWinding (in, stack); - - return neww; -} - -#ifdef _WIN32 -#pragma warning (default:4701) -#endif - -/* -============== -ClipToSeperators - -Source, pass, and target are an ordering of portals. - -Generates seperating planes canidates by taking two points from source and one -point from pass, and clips target by them. - -If target is totally clipped away, that portal can not be seen through. - -Normal clip keeps target on the same side as pass, which is correct if the -order goes source, pass, target. If the order goes pass, source, target then -flipclip should be set. -============== -*/ -winding_t *ClipToSeperators (winding_t *source, winding_t *pass, winding_t *target, bool flipclip, pstack_t *stack) -{ - int i, j, k, l; - plane_t plane; - Vector v1, v2; - float d; - vec_t length; - int counts[3]; - bool fliptest; - -// check all combinations - for (i=0 ; inumpoints ; i++) - { - l = (i+1)%source->numpoints; - VectorSubtract (source->points[l] , source->points[i], v1); - - // fing a vertex of pass that makes a plane that puts all of the - // vertexes of pass on the front side and all of the vertexes of - // source on the back side - for (j=0 ; jnumpoints ; j++) - { - VectorSubtract (pass->points[j], source->points[i], v2); - - plane.normal[0] = v1[1]*v2[2] - v1[2]*v2[1]; - plane.normal[1] = v1[2]*v2[0] - v1[0]*v2[2]; - plane.normal[2] = v1[0]*v2[1] - v1[1]*v2[0]; - - // if points don't make a valid plane, skip it - - length = plane.normal[0] * plane.normal[0] - + plane.normal[1] * plane.normal[1] - + plane.normal[2] * plane.normal[2]; - - if (length < ON_VIS_EPSILON) - continue; - - length = 1/sqrt(length); - - plane.normal[0] *= length; - plane.normal[1] *= length; - plane.normal[2] *= length; - - plane.dist = DotProduct (pass->points[j], plane.normal); - - // - // find out which side of the generated seperating plane has the - // source portal - // -#if 1 - fliptest = false; - for (k=0 ; knumpoints ; k++) - { - if (k == i || k == l) - continue; - d = DotProduct (source->points[k], plane.normal) - plane.dist; - if (d < -ON_VIS_EPSILON) - { // source is on the negative side, so we want all - // pass and target on the positive side - fliptest = false; - break; - } - else if (d > ON_VIS_EPSILON) - { // source is on the positive side, so we want all - // pass and target on the negative side - fliptest = true; - break; - } - } - if (k == source->numpoints) - continue; // planar with source portal -#else - fliptest = flipclip; -#endif - // - // flip the normal if the source portal is backwards - // - if (fliptest) - { - VectorSubtract (vec3_origin, plane.normal, plane.normal); - plane.dist = -plane.dist; - } -#if 1 - // - // if all of the pass portal points are now on the positive side, - // this is the seperating plane - // - counts[0] = counts[1] = counts[2] = 0; - for (k=0 ; knumpoints ; k++) - { - if (k==j) - continue; - d = DotProduct (pass->points[k], plane.normal) - plane.dist; - if (d < -ON_VIS_EPSILON) - break; - else if (d > ON_VIS_EPSILON) - counts[0]++; - else - counts[2]++; - } - if (k != pass->numpoints) - continue; // points on negative side, not a seperating plane - - if (!counts[0]) - continue; // planar with seperating plane -#else - k = (j+1)%pass->numpoints; - d = DotProduct (pass->points[k], plane.normal) - plane.dist; - if (d < -ON_VIS_EPSILON) - continue; - k = (j+pass->numpoints-1)%pass->numpoints; - d = DotProduct (pass->points[k], plane.normal) - plane.dist; - if (d < -ON_VIS_EPSILON) - continue; -#endif - // - // flip the normal if we want the back side - // - if (flipclip) - { - VectorSubtract (vec3_origin, plane.normal, plane.normal); - plane.dist = -plane.dist; - } - - // - // clip target by the seperating plane - // - target = ChopWinding (target, stack, &plane); - if (!target) - return NULL; // target is not visible - - // JAY: End the loop, no need to find additional separators on this edge ? -// j = pass->numpoints; - } - } - - return target; -} - - -class CPortalTrace -{ -public: - CUtlVector m_list; - CThreadFastMutex m_mutex; -} g_PortalTrace; - -void WindingCenter (winding_t *w, Vector ¢er) -{ - int i; - float scale; - - VectorCopy (vec3_origin, center); - for (i=0 ; inumpoints ; i++) - VectorAdd (w->points[i], center, center); - - scale = 1.0/w->numpoints; - VectorScale (center, scale, center); -} - -Vector ClusterCenter( int cluster ) -{ - Vector mins, maxs; - ClearBounds(mins, maxs); - int count = leafs[cluster].portals.Count(); - for ( int i = 0; i < count; i++ ) - { - winding_t *w = leafs[cluster].portals[i]->winding; - for ( int j = 0; j < w->numpoints; j++ ) - { - AddPointToBounds( w->points[j], mins, maxs ); - } - } - return (mins + maxs) * 0.5f; -} - - -void DumpPortalTrace( pstack_t *pStack ) -{ - AUTO_LOCK_FM(g_PortalTrace.m_mutex); - if ( g_PortalTrace.m_list.Count() ) - return; - - Warning("Dumped cluster trace!!!\n"); - Vector mid; - mid = ClusterCenter( g_TraceClusterStart ); - g_PortalTrace.m_list.AddToTail(mid); - for ( ; pStack != NULL; pStack = pStack->next ) - { - winding_t *w = pStack->pass ? pStack->pass : pStack->portal->winding; - WindingCenter (w, mid); - g_PortalTrace.m_list.AddToTail(mid); - for ( int i = 0; i < w->numpoints; i++ ) - { - g_PortalTrace.m_list.AddToTail(w->points[i]); - g_PortalTrace.m_list.AddToTail(mid); - } - for ( int i = 0; i < w->numpoints; i++ ) - { - g_PortalTrace.m_list.AddToTail(w->points[i]); - } - g_PortalTrace.m_list.AddToTail(w->points[0]); - g_PortalTrace.m_list.AddToTail(mid); - } - mid = ClusterCenter( g_TraceClusterStop ); - g_PortalTrace.m_list.AddToTail(mid); -} - -void WritePortalTrace( const char *source ) -{ - Vector mid; - FILE *linefile; - char filename[1024]; - - if ( !g_PortalTrace.m_list.Count() ) - { - Warning("No trace generated from %d to %d\n", g_TraceClusterStart, g_TraceClusterStop ); - return; - } - - sprintf (filename, "%s.lin", source); - linefile = fopen (filename, "w"); - if (!linefile) - Error ("Couldn't open %s\n", filename); - - for ( int i = 0; i < g_PortalTrace.m_list.Count(); i++ ) - { - Vector p = g_PortalTrace.m_list[i]; - fprintf (linefile, "%f %f %f\n", p[0], p[1], p[2]); - } - fclose (linefile); - Warning("Wrote %s!!!\n", filename); -} - -/* -================== -RecursiveLeafFlow - -Flood fill through the leafs -If src_portal is NULL, this is the originating leaf -================== -*/ -void RecursiveLeafFlow (int leafnum, threaddata_t *thread, pstack_t *prevstack) -{ - pstack_t stack; - portal_t *p; - plane_t backplane; - leaf_t *leaf; - int i, j; - long *test, *might, *vis, more; - int pnum; - - // Early-out if we're a VMPI worker that's told to exit. If we don't do this here, then the - // worker might spin its wheels for a while on an expensive work unit and not be available to the pool. - // This is pretty common in vis. - if ( g_bVMPIEarlyExit ) - return; - - if ( leafnum == g_TraceClusterStop ) - { - DumpPortalTrace(&thread->pstack_head); - return; - } - thread->c_chains++; - - leaf = &leafs[leafnum]; - - prevstack->next = &stack; - - stack.next = NULL; - stack.leaf = leaf; - stack.portal = NULL; - - might = (long *)stack.mightsee; - vis = (long *)thread->base->portalvis; - - // check all portals for flowing into other leafs - for (i=0 ; iportals.Count() ; i++) - { - - p = leaf->portals[i]; - pnum = p - portals; - - if ( ! (prevstack->mightsee[pnum >> 3] & (1<<(pnum&7)) ) ) - { - continue; // can't possibly see it - } - - // if the portal can't see anything we haven't allready seen, skip it - if (p->status == stat_done) - { - test = (long *)p->portalvis; - } - else - { - test = (long *)p->portalflood; - } - - more = 0; - for (j=0 ; jmightsee)[j] & test[j]; - more |= (might[j] & ~vis[j]); - } - - if ( !more && CheckBit( thread->base->portalvis, pnum ) ) - { // can't see anything new - continue; - } - - // get plane of portal, point normal into the neighbor leaf - stack.portalplane = p->plane; - VectorSubtract (vec3_origin, p->plane.normal, backplane.normal); - backplane.dist = -p->plane.dist; - - stack.portal = p; - stack.next = NULL; - stack.freewindings[0] = 1; - stack.freewindings[1] = 1; - stack.freewindings[2] = 1; - - float d = DotProduct (p->origin, thread->pstack_head.portalplane.normal); - d -= thread->pstack_head.portalplane.dist; - if (d < -p->radius) - { - continue; - } - else if (d > p->radius) - { - stack.pass = p->winding; - } - else - { - stack.pass = ChopWinding (p->winding, &stack, &thread->pstack_head.portalplane); - if (!stack.pass) - continue; - } - - - d = DotProduct (thread->base->origin, p->plane.normal); - d -= p->plane.dist; - if (d > thread->base->radius) - { - continue; - } - else if (d < -thread->base->radius) - { - stack.source = prevstack->source; - } - else - { - stack.source = ChopWinding (prevstack->source, &stack, &backplane); - if (!stack.source) - continue; - } - - - if (!prevstack->pass) - { // the second leaf can only be blocked if coplanar - - // mark the portal as visible - SetBit( thread->base->portalvis, pnum ); - - RecursiveLeafFlow (p->leaf, thread, &stack); - continue; - } - - stack.pass = ClipToSeperators (stack.source, prevstack->pass, stack.pass, false, &stack); - if (!stack.pass) - continue; - - stack.pass = ClipToSeperators (prevstack->pass, stack.source, stack.pass, true, &stack); - if (!stack.pass) - continue; - - // mark the portal as visible - SetBit( thread->base->portalvis, pnum ); - - // flow through it for real - RecursiveLeafFlow (p->leaf, thread, &stack); - } -} - - -/* -=============== -PortalFlow - -generates the portalvis bit vector -=============== -*/ -void PortalFlow (int iThread, int portalnum) -{ - threaddata_t data; - int i; - portal_t *p; - int c_might, c_can; - - p = sorted_portals[portalnum]; - p->status = stat_working; - - c_might = CountBits (p->portalflood, g_numportals*2); - - memset (&data, 0, sizeof(data)); - data.base = p; - - data.pstack_head.portal = p; - data.pstack_head.source = p->winding; - data.pstack_head.portalplane = p->plane; - for (i=0 ; iportalflood)[i]; - - RecursiveLeafFlow (p->leaf, &data, &data.pstack_head); - - - p->status = stat_done; - - c_can = CountBits (p->portalvis, g_numportals*2); - - qprintf ("portal:%4i mightsee:%4i cansee:%4i (%i chains)\n", - (int)(p - portals), c_might, c_can, data.c_chains); -} - - -/* -=============================================================================== - -This is a rough first-order aproximation that is used to trivially reject some -of the final calculations. - - -Calculates portalfront and portalflood bit vectors - -=============================================================================== -*/ - -int c_flood, c_vis; - -/* -================== -SimpleFlood - -================== -*/ -void SimpleFlood (portal_t *srcportal, int leafnum) -{ - int i; - leaf_t *leaf; - portal_t *p; - int pnum; - - leaf = &leafs[leafnum]; - - for (i=0 ; iportals.Count(); i++) - { - p = leaf->portals[i]; - pnum = p - portals; - if ( !CheckBit( srcportal->portalfront, pnum ) ) - continue; - - if ( CheckBit( srcportal->portalflood, pnum ) ) - continue; - - SetBit( srcportal->portalflood, pnum ); - - SimpleFlood (srcportal, p->leaf); - } -} - -/* -============== -BasePortalVis -============== -*/ -void BasePortalVis (int iThread, int portalnum) -{ - int j, k; - portal_t *tp, *p; - float d; - winding_t *w; - Vector segment; - double dist2, minDist2; - - // get the portal - p = portals+portalnum; - - // - // allocate memory for bitwise vis solutions for this portal - // - p->portalfront = (byte*)malloc (portalbytes); - memset (p->portalfront, 0, portalbytes); - - p->portalflood = (byte*)malloc (portalbytes); - memset (p->portalflood, 0, portalbytes); - - p->portalvis = (byte*)malloc (portalbytes); - memset (p->portalvis, 0, portalbytes); - - // - // test the given portal against all of the portals in the map - // - for (j=0, tp = portals ; jwinding; - for (k=0 ; knumpoints ; k++) - { - d = DotProduct (w->points[k], p->plane.normal) - p->plane.dist; - if (d > ON_VIS_EPSILON) - break; - } - if (k == w->numpoints) - continue; // no points on front - - // - // - // - w = p->winding; - for (k=0 ; knumpoints ; k++) - { - d = DotProduct (w->points[k], tp->plane.normal) - tp->plane.dist; - if (d < -ON_VIS_EPSILON) - break; - } - if (k == w->numpoints) - continue; // no points on front - - // - // if using radius visibility -- check to see if any portal points lie inside of the - // radius given - // - if( g_bUseRadius ) - { - w = tp->winding; - minDist2 = 1024000000.0; // 32000^2 - for( k = 0; k < w->numpoints; k++ ) - { - VectorSubtract( w->points[k], p->origin, segment ); - dist2 = ( segment[0] * segment[0] ) + ( segment[1] * segment[1] ) + ( segment[2] * segment[2] ); - if( dist2 < minDist2 ) - { - minDist2 = dist2; - } - } - - if( minDist2 > g_VisRadius ) - continue; - } - - // add current portal to given portal's list of visible portals - SetBit( p->portalfront, j ); - } - - SimpleFlood (p, p->leaf); - - p->nummightsee = CountBits (p->portalflood, g_numportals*2); -// Msg ("portal %i: %i mightsee\n", portalnum, p->nummightsee); - c_flood += p->nummightsee; -} - - - - - -/* -=============================================================================== - -This is a second order aproximation - -Calculates portalvis bit vector - -WAAAAAAY too slow. - -=============================================================================== -*/ - -/* -================== -RecursiveLeafBitFlow - -================== -*/ -void RecursiveLeafBitFlow (int leafnum, byte *mightsee, byte *cansee) -{ - portal_t *p; - leaf_t *leaf; - int i, j; - long more; - int pnum; - byte newmight[MAX_PORTALS/8]; - - leaf = &leafs[leafnum]; - -// check all portals for flowing into other leafs - for (i=0 ; iportals.Count(); i++) - { - p = leaf->portals[i]; - pnum = p - portals; - - // if some previous portal can't see it, skip - if ( !CheckBit( mightsee, pnum ) ) - continue; - - // if this portal can see some portals we mightsee, recurse - more = 0; - for (j=0 ; jportalflood)[j]; - more |= ((long *)newmight)[j] & ~((long *)cansee)[j]; - } - - if (!more) - continue; // can't see anything new - - SetBit( cansee, pnum ); - - RecursiveLeafBitFlow (p->leaf, newmight, cansee); - } -} - -/* -============== -BetterPortalVis -============== -*/ -void BetterPortalVis (int portalnum) -{ - portal_t *p; - - p = portals+portalnum; - - RecursiveLeafBitFlow (p->leaf, p->portalflood, p->portalvis); - - // build leaf vis information - p->nummightsee = CountBits (p->portalvis, g_numportals*2); - c_vis += p->nummightsee; -} - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "vis.h" +#include "vmpi.h" + +int g_TraceClusterStart = -1; +int g_TraceClusterStop = -1; +/* + + each portal will have a list of all possible to see from first portal + + if (!thread->portalmightsee[portalnum]) + + portal mightsee + + for p2 = all other portals in leaf + get sperating planes + for all portals that might be seen by p2 + mark as unseen if not present in seperating plane + flood fill a new mightsee + save as passagemightsee + + + void CalcMightSee (leaf_t *leaf, +*/ + +int CountBits (byte *bits, int numbits) +{ + int i; + int c; + + c = 0; + for (i=0 ; ipstack_head.next ; p ; p=p->next) + { +// Msg ("="); + if (p->leaf == leaf) + Error ("CheckStack: leaf recursion"); + for (p2=thread->pstack_head.next ; p2 != p ; p2=p2->next) + if (p2->leaf == p->leaf) + Error ("CheckStack: late leaf recursion"); + } +// Msg ("\n"); +} + + +winding_t *AllocStackWinding (pstack_t *stack) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if (stack->freewindings[i]) + { + stack->freewindings[i] = 0; + return &stack->windings[i]; + } + } + + Error ("Out of memory. AllocStackWinding: failed"); + + return NULL; +} + +void FreeStackWinding (winding_t *w, pstack_t *stack) +{ + int i; + + i = w - stack->windings; + + if (i<0 || i>2) + return; // not from local + + if (stack->freewindings[i]) + Error ("FreeStackWinding: allready free"); + stack->freewindings[i] = 1; +} + +/* +============== +ChopWinding + +============== +*/ + +#ifdef _WIN32 +#pragma warning (disable:4701) +#endif + +winding_t *ChopWinding (winding_t *in, pstack_t *stack, plane_t *split) +{ + vec_t dists[128]; + int sides[128]; + int counts[3]; + vec_t dot; + int i, j; + Vector mid; + winding_t *neww; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->points[i], split->normal); + dot -= split->dist; + dists[i] = dot; + if (dot > ON_VIS_EPSILON) + sides[i] = SIDE_FRONT; + else if (dot < -ON_VIS_EPSILON) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + + if (!counts[1]) + return in; // completely on front side + + if (!counts[0]) + { + FreeStackWinding (in, stack); + return NULL; + } + + sides[i] = sides[0]; + dists[i] = dists[0]; + + neww = AllocStackWinding (stack); + + neww->numpoints = 0; + + for (i=0 ; inumpoints ; i++) + { + Vector& p1 = in->points[i]; + + if (neww->numpoints == MAX_POINTS_ON_FIXED_WINDING) + { + FreeStackWinding (neww, stack); + return in; // can't chop -- fall back to original + } + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, neww->points[neww->numpoints]); + neww->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, neww->points[neww->numpoints]); + neww->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + if (neww->numpoints == MAX_POINTS_ON_FIXED_WINDING) + { + FreeStackWinding (neww, stack); + return in; // can't chop -- fall back to original + } + + // generate a split point + Vector& p2 = in->points[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (split->normal[j] == 1) + mid[j] = split->dist; + else if (split->normal[j] == -1) + mid[j] = -split->dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, neww->points[neww->numpoints]); + neww->numpoints++; + } + +// free the original winding + FreeStackWinding (in, stack); + + return neww; +} + +#ifdef _WIN32 +#pragma warning (default:4701) +#endif + +/* +============== +ClipToSeperators + +Source, pass, and target are an ordering of portals. + +Generates seperating planes canidates by taking two points from source and one +point from pass, and clips target by them. + +If target is totally clipped away, that portal can not be seen through. + +Normal clip keeps target on the same side as pass, which is correct if the +order goes source, pass, target. If the order goes pass, source, target then +flipclip should be set. +============== +*/ +winding_t *ClipToSeperators (winding_t *source, winding_t *pass, winding_t *target, bool flipclip, pstack_t *stack) +{ + int i, j, k, l; + plane_t plane; + Vector v1, v2; + float d; + vec_t length; + int counts[3]; + bool fliptest; + +// check all combinations + for (i=0 ; inumpoints ; i++) + { + l = (i+1)%source->numpoints; + VectorSubtract (source->points[l] , source->points[i], v1); + + // fing a vertex of pass that makes a plane that puts all of the + // vertexes of pass on the front side and all of the vertexes of + // source on the back side + for (j=0 ; jnumpoints ; j++) + { + VectorSubtract (pass->points[j], source->points[i], v2); + + plane.normal[0] = v1[1]*v2[2] - v1[2]*v2[1]; + plane.normal[1] = v1[2]*v2[0] - v1[0]*v2[2]; + plane.normal[2] = v1[0]*v2[1] - v1[1]*v2[0]; + + // if points don't make a valid plane, skip it + + length = plane.normal[0] * plane.normal[0] + + plane.normal[1] * plane.normal[1] + + plane.normal[2] * plane.normal[2]; + + if (length < ON_VIS_EPSILON) + continue; + + length = 1/sqrt(length); + + plane.normal[0] *= length; + plane.normal[1] *= length; + plane.normal[2] *= length; + + plane.dist = DotProduct (pass->points[j], plane.normal); + + // + // find out which side of the generated seperating plane has the + // source portal + // +#if 1 + fliptest = false; + for (k=0 ; knumpoints ; k++) + { + if (k == i || k == l) + continue; + d = DotProduct (source->points[k], plane.normal) - plane.dist; + if (d < -ON_VIS_EPSILON) + { // source is on the negative side, so we want all + // pass and target on the positive side + fliptest = false; + break; + } + else if (d > ON_VIS_EPSILON) + { // source is on the positive side, so we want all + // pass and target on the negative side + fliptest = true; + break; + } + } + if (k == source->numpoints) + continue; // planar with source portal +#else + fliptest = flipclip; +#endif + // + // flip the normal if the source portal is backwards + // + if (fliptest) + { + VectorSubtract (vec3_origin, plane.normal, plane.normal); + plane.dist = -plane.dist; + } +#if 1 + // + // if all of the pass portal points are now on the positive side, + // this is the seperating plane + // + counts[0] = counts[1] = counts[2] = 0; + for (k=0 ; knumpoints ; k++) + { + if (k==j) + continue; + d = DotProduct (pass->points[k], plane.normal) - plane.dist; + if (d < -ON_VIS_EPSILON) + break; + else if (d > ON_VIS_EPSILON) + counts[0]++; + else + counts[2]++; + } + if (k != pass->numpoints) + continue; // points on negative side, not a seperating plane + + if (!counts[0]) + continue; // planar with seperating plane +#else + k = (j+1)%pass->numpoints; + d = DotProduct (pass->points[k], plane.normal) - plane.dist; + if (d < -ON_VIS_EPSILON) + continue; + k = (j+pass->numpoints-1)%pass->numpoints; + d = DotProduct (pass->points[k], plane.normal) - plane.dist; + if (d < -ON_VIS_EPSILON) + continue; +#endif + // + // flip the normal if we want the back side + // + if (flipclip) + { + VectorSubtract (vec3_origin, plane.normal, plane.normal); + plane.dist = -plane.dist; + } + + // + // clip target by the seperating plane + // + target = ChopWinding (target, stack, &plane); + if (!target) + return NULL; // target is not visible + + // JAY: End the loop, no need to find additional separators on this edge ? +// j = pass->numpoints; + } + } + + return target; +} + + +class CPortalTrace +{ +public: + CUtlVector m_list; + CThreadFastMutex m_mutex; +} g_PortalTrace; + +void WindingCenter (winding_t *w, Vector ¢er) +{ + int i; + float scale; + + VectorCopy (vec3_origin, center); + for (i=0 ; inumpoints ; i++) + VectorAdd (w->points[i], center, center); + + scale = 1.0/w->numpoints; + VectorScale (center, scale, center); +} + +Vector ClusterCenter( int cluster ) +{ + Vector mins, maxs; + ClearBounds(mins, maxs); + int count = leafs[cluster].portals.Count(); + for ( int i = 0; i < count; i++ ) + { + winding_t *w = leafs[cluster].portals[i]->winding; + for ( int j = 0; j < w->numpoints; j++ ) + { + AddPointToBounds( w->points[j], mins, maxs ); + } + } + return (mins + maxs) * 0.5f; +} + + +void DumpPortalTrace( pstack_t *pStack ) +{ + AUTO_LOCK_FM(g_PortalTrace.m_mutex); + if ( g_PortalTrace.m_list.Count() ) + return; + + Warning("Dumped cluster trace!!!\n"); + Vector mid; + mid = ClusterCenter( g_TraceClusterStart ); + g_PortalTrace.m_list.AddToTail(mid); + for ( ; pStack != NULL; pStack = pStack->next ) + { + winding_t *w = pStack->pass ? pStack->pass : pStack->portal->winding; + WindingCenter (w, mid); + g_PortalTrace.m_list.AddToTail(mid); + for ( int i = 0; i < w->numpoints; i++ ) + { + g_PortalTrace.m_list.AddToTail(w->points[i]); + g_PortalTrace.m_list.AddToTail(mid); + } + for ( int i = 0; i < w->numpoints; i++ ) + { + g_PortalTrace.m_list.AddToTail(w->points[i]); + } + g_PortalTrace.m_list.AddToTail(w->points[0]); + g_PortalTrace.m_list.AddToTail(mid); + } + mid = ClusterCenter( g_TraceClusterStop ); + g_PortalTrace.m_list.AddToTail(mid); +} + +void WritePortalTrace( const char *source ) +{ + Vector mid; + FILE *linefile; + char filename[1024]; + + if ( !g_PortalTrace.m_list.Count() ) + { + Warning("No trace generated from %d to %d\n", g_TraceClusterStart, g_TraceClusterStop ); + return; + } + + sprintf (filename, "%s.lin", source); + linefile = fopen (filename, "w"); + if (!linefile) + Error ("Couldn't open %s\n", filename); + + for ( int i = 0; i < g_PortalTrace.m_list.Count(); i++ ) + { + Vector p = g_PortalTrace.m_list[i]; + fprintf (linefile, "%f %f %f\n", p[0], p[1], p[2]); + } + fclose (linefile); + Warning("Wrote %s!!!\n", filename); +} + +/* +================== +RecursiveLeafFlow + +Flood fill through the leafs +If src_portal is NULL, this is the originating leaf +================== +*/ +void RecursiveLeafFlow (int leafnum, threaddata_t *thread, pstack_t *prevstack) +{ + pstack_t stack; + portal_t *p; + plane_t backplane; + leaf_t *leaf; + int i, j; + long *test, *might, *vis, more; + int pnum; + + // Early-out if we're a VMPI worker that's told to exit. If we don't do this here, then the + // worker might spin its wheels for a while on an expensive work unit and not be available to the pool. + // This is pretty common in vis. + if ( g_bVMPIEarlyExit ) + return; + + if ( leafnum == g_TraceClusterStop ) + { + DumpPortalTrace(&thread->pstack_head); + return; + } + thread->c_chains++; + + leaf = &leafs[leafnum]; + + prevstack->next = &stack; + + stack.next = NULL; + stack.leaf = leaf; + stack.portal = NULL; + + might = (long *)stack.mightsee; + vis = (long *)thread->base->portalvis; + + // check all portals for flowing into other leafs + for (i=0 ; iportals.Count() ; i++) + { + + p = leaf->portals[i]; + pnum = p - portals; + + if ( ! (prevstack->mightsee[pnum >> 3] & (1<<(pnum&7)) ) ) + { + continue; // can't possibly see it + } + + // if the portal can't see anything we haven't allready seen, skip it + if (p->status == stat_done) + { + test = (long *)p->portalvis; + } + else + { + test = (long *)p->portalflood; + } + + more = 0; + for (j=0 ; jmightsee)[j] & test[j]; + more |= (might[j] & ~vis[j]); + } + + if ( !more && CheckBit( thread->base->portalvis, pnum ) ) + { // can't see anything new + continue; + } + + // get plane of portal, point normal into the neighbor leaf + stack.portalplane = p->plane; + VectorSubtract (vec3_origin, p->plane.normal, backplane.normal); + backplane.dist = -p->plane.dist; + + stack.portal = p; + stack.next = NULL; + stack.freewindings[0] = 1; + stack.freewindings[1] = 1; + stack.freewindings[2] = 1; + + float d = DotProduct (p->origin, thread->pstack_head.portalplane.normal); + d -= thread->pstack_head.portalplane.dist; + if (d < -p->radius) + { + continue; + } + else if (d > p->radius) + { + stack.pass = p->winding; + } + else + { + stack.pass = ChopWinding (p->winding, &stack, &thread->pstack_head.portalplane); + if (!stack.pass) + continue; + } + + + d = DotProduct (thread->base->origin, p->plane.normal); + d -= p->plane.dist; + if (d > thread->base->radius) + { + continue; + } + else if (d < -thread->base->radius) + { + stack.source = prevstack->source; + } + else + { + stack.source = ChopWinding (prevstack->source, &stack, &backplane); + if (!stack.source) + continue; + } + + + if (!prevstack->pass) + { // the second leaf can only be blocked if coplanar + + // mark the portal as visible + SetBit( thread->base->portalvis, pnum ); + + RecursiveLeafFlow (p->leaf, thread, &stack); + continue; + } + + stack.pass = ClipToSeperators (stack.source, prevstack->pass, stack.pass, false, &stack); + if (!stack.pass) + continue; + + stack.pass = ClipToSeperators (prevstack->pass, stack.source, stack.pass, true, &stack); + if (!stack.pass) + continue; + + // mark the portal as visible + SetBit( thread->base->portalvis, pnum ); + + // flow through it for real + RecursiveLeafFlow (p->leaf, thread, &stack); + } +} + + +/* +=============== +PortalFlow + +generates the portalvis bit vector +=============== +*/ +void PortalFlow (int iThread, int portalnum) +{ + threaddata_t data; + int i; + portal_t *p; + int c_might, c_can; + + p = sorted_portals[portalnum]; + p->status = stat_working; + + c_might = CountBits (p->portalflood, g_numportals*2); + + memset (&data, 0, sizeof(data)); + data.base = p; + + data.pstack_head.portal = p; + data.pstack_head.source = p->winding; + data.pstack_head.portalplane = p->plane; + for (i=0 ; iportalflood)[i]; + + RecursiveLeafFlow (p->leaf, &data, &data.pstack_head); + + + p->status = stat_done; + + c_can = CountBits (p->portalvis, g_numportals*2); + + qprintf ("portal:%4i mightsee:%4i cansee:%4i (%i chains)\n", + (int)(p - portals), c_might, c_can, data.c_chains); +} + + +/* +=============================================================================== + +This is a rough first-order aproximation that is used to trivially reject some +of the final calculations. + + +Calculates portalfront and portalflood bit vectors + +=============================================================================== +*/ + +int c_flood, c_vis; + +/* +================== +SimpleFlood + +================== +*/ +void SimpleFlood (portal_t *srcportal, int leafnum) +{ + int i; + leaf_t *leaf; + portal_t *p; + int pnum; + + leaf = &leafs[leafnum]; + + for (i=0 ; iportals.Count(); i++) + { + p = leaf->portals[i]; + pnum = p - portals; + if ( !CheckBit( srcportal->portalfront, pnum ) ) + continue; + + if ( CheckBit( srcportal->portalflood, pnum ) ) + continue; + + SetBit( srcportal->portalflood, pnum ); + + SimpleFlood (srcportal, p->leaf); + } +} + +/* +============== +BasePortalVis +============== +*/ +void BasePortalVis (int iThread, int portalnum) +{ + int j, k; + portal_t *tp, *p; + float d; + winding_t *w; + Vector segment; + double dist2, minDist2; + + // get the portal + p = portals+portalnum; + + // + // allocate memory for bitwise vis solutions for this portal + // + p->portalfront = (byte*)malloc (portalbytes); + memset (p->portalfront, 0, portalbytes); + + p->portalflood = (byte*)malloc (portalbytes); + memset (p->portalflood, 0, portalbytes); + + p->portalvis = (byte*)malloc (portalbytes); + memset (p->portalvis, 0, portalbytes); + + // + // test the given portal against all of the portals in the map + // + for (j=0, tp = portals ; jwinding; + for (k=0 ; knumpoints ; k++) + { + d = DotProduct (w->points[k], p->plane.normal) - p->plane.dist; + if (d > ON_VIS_EPSILON) + break; + } + if (k == w->numpoints) + continue; // no points on front + + // + // + // + w = p->winding; + for (k=0 ; knumpoints ; k++) + { + d = DotProduct (w->points[k], tp->plane.normal) - tp->plane.dist; + if (d < -ON_VIS_EPSILON) + break; + } + if (k == w->numpoints) + continue; // no points on front + + // + // if using radius visibility -- check to see if any portal points lie inside of the + // radius given + // + if( g_bUseRadius ) + { + w = tp->winding; + minDist2 = 1024000000.0; // 32000^2 + for( k = 0; k < w->numpoints; k++ ) + { + VectorSubtract( w->points[k], p->origin, segment ); + dist2 = ( segment[0] * segment[0] ) + ( segment[1] * segment[1] ) + ( segment[2] * segment[2] ); + if( dist2 < minDist2 ) + { + minDist2 = dist2; + } + } + + if( minDist2 > g_VisRadius ) + continue; + } + + // add current portal to given portal's list of visible portals + SetBit( p->portalfront, j ); + } + + SimpleFlood (p, p->leaf); + + p->nummightsee = CountBits (p->portalflood, g_numportals*2); +// Msg ("portal %i: %i mightsee\n", portalnum, p->nummightsee); + c_flood += p->nummightsee; +} + + + + + +/* +=============================================================================== + +This is a second order aproximation + +Calculates portalvis bit vector + +WAAAAAAY too slow. + +=============================================================================== +*/ + +/* +================== +RecursiveLeafBitFlow + +================== +*/ +void RecursiveLeafBitFlow (int leafnum, byte *mightsee, byte *cansee) +{ + portal_t *p; + leaf_t *leaf; + int i, j; + long more; + int pnum; + byte newmight[MAX_PORTALS/8]; + + leaf = &leafs[leafnum]; + +// check all portals for flowing into other leafs + for (i=0 ; iportals.Count(); i++) + { + p = leaf->portals[i]; + pnum = p - portals; + + // if some previous portal can't see it, skip + if ( !CheckBit( mightsee, pnum ) ) + continue; + + // if this portal can see some portals we mightsee, recurse + more = 0; + for (j=0 ; jportalflood)[j]; + more |= ((long *)newmight)[j] & ~((long *)cansee)[j]; + } + + if (!more) + continue; // can't see anything new + + SetBit( cansee, pnum ); + + RecursiveLeafBitFlow (p->leaf, newmight, cansee); + } +} + +/* +============== +BetterPortalVis +============== +*/ +void BetterPortalVis (int portalnum) +{ + portal_t *p; + + p = portals+portalnum; + + RecursiveLeafBitFlow (p->leaf, p->portalflood, p->portalvis); + + // build leaf vis information + p->nummightsee = CountBits (p->portalvis, g_numportals*2); + c_vis += p->nummightsee; +} + + diff --git a/mp/src/utils/vvis/mpivis.cpp b/mp/src/utils/vvis/mpivis.cpp index 6da76ebc..58b76331 100644 --- a/mp/src/utils/vvis/mpivis.cpp +++ b/mp/src/utils/vvis/mpivis.cpp @@ -1,640 +1,640 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// - -#include -#include "vis.h" -#include "threads.h" -#include "stdlib.h" -#include "pacifier.h" -#include "mpi_stats.h" -#include "vmpi.h" -#include "vmpi_dispatch.h" -#include "vmpi_filesystem.h" -#include "vmpi_distribute_work.h" -#include "iphelpers.h" -#include "threadhelpers.h" -#include "vstdlib/random.h" -#include "vmpi_tools_shared.h" -#include -#include "scratchpad_helpers.h" - - -#define VMPI_VVIS_PACKET_ID 1 - // Sub packet IDs. - #define VMPI_SUBPACKETID_DISCONNECT_NOTIFY 3 // We send ourselves this when there is a disconnect. - #define VMPI_SUBPACKETID_BASEPORTALVIS 5 - #define VMPI_SUBPACKETID_PORTALFLOW 6 - #define VMPI_BASEPORTALVIS_RESULTS 7 - #define VMPI_BASEPORTALVIS_WORKER_DONE 8 - #define VMPI_PORTALFLOW_RESULTS 9 - #define VMPI_SUBPACKETID_BASEPORTALVIS_SYNC 11 - #define VMPI_SUBPACKETID_PORTALFLOW_SYNC 12 - #define VMPI_SUBPACKETID_MC_ADDR 13 - -// DistributeWork owns this packet ID. -#define VMPI_DISTRIBUTEWORK_PACKETID 2 - - -extern bool fastvis; - -// The worker waits until these are true. -bool g_bBasePortalVisSync = false; -bool g_bPortalFlowSync = false; - -CUtlVector g_BasePortalVisResultsFilename; - -CCycleCount g_CPUTime; - - -// This stuff is all for the multicast channel the master uses to send out the portal results. -ISocket *g_pPortalMCSocket = NULL; -CIPAddr g_PortalMCAddr; -bool g_bGotMCAddr = false; -HANDLE g_hMCThread = NULL; -CEvent g_MCThreadExitEvent; -unsigned long g_PortalMCThreadUniqueID = 0; -int g_nMulticastPortalsReceived = 0; - - -// Handle VVIS packets. -bool VVIS_DispatchFn( MessageBuffer *pBuf, int iSource, int iPacketID ) -{ - switch ( pBuf->data[1] ) - { - case VMPI_SUBPACKETID_MC_ADDR: - { - pBuf->setOffset( 2 ); - pBuf->read( &g_PortalMCAddr, sizeof( g_PortalMCAddr ) ); - g_bGotMCAddr = true; - return true; - } - - case VMPI_SUBPACKETID_DISCONNECT_NOTIFY: - { - // This is just used to cause nonblocking dispatches to jump out so loops like the one - // in AppBarrier can handle the fact that there are disconnects. - return true; - } - - case VMPI_SUBPACKETID_BASEPORTALVIS_SYNC: - { - g_bBasePortalVisSync = true; - return true; - } - - case VMPI_SUBPACKETID_PORTALFLOW_SYNC: - { - g_bPortalFlowSync = true; - return true; - } - - case VMPI_BASEPORTALVIS_RESULTS: - { - const char *pFilename = &pBuf->data[2]; - g_BasePortalVisResultsFilename.CopyArray( pFilename, strlen( pFilename ) + 1 ); - return true; - } - - default: - { - return false; - } - } -} -CDispatchReg g_VVISDispatchReg( VMPI_VVIS_PACKET_ID, VVIS_DispatchFn ); // register to handle the messages we want -CDispatchReg g_DistributeWorkReg( VMPI_DISTRIBUTEWORK_PACKETID, DistributeWorkDispatch ); - - - -void VMPI_DeletePortalMCSocket() -{ - // Stop the thread if it exists. - if ( g_hMCThread ) - { - g_MCThreadExitEvent.SetEvent(); - WaitForSingleObject( g_hMCThread, INFINITE ); - CloseHandle( g_hMCThread ); - g_hMCThread = NULL; - } - - if ( g_pPortalMCSocket ) - { - g_pPortalMCSocket->Release(); - g_pPortalMCSocket = NULL; - } -} - - -void VVIS_SetupMPI( int &argc, char **&argv ) -{ - if ( !VMPI_FindArg( argc, argv, "-mpi", "" ) && !VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Worker ), "" ) ) - return; - - CmdLib_AtCleanup( VMPI_Stats_Term ); - CmdLib_AtCleanup( VMPI_DeletePortalMCSocket ); - - VMPI_Stats_InstallSpewHook(); - - // Force local mode? - VMPIRunMode mode; - if ( VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Local ), "" ) ) - mode = VMPI_RUN_LOCAL; - else - mode = VMPI_RUN_NETWORKED; - - // - // Extract mpi specific arguments - // - Msg( "Initializing VMPI...\n" ); - if ( !VMPI_Init( argc, argv, "dependency_info_vvis.txt", HandleMPIDisconnect, mode ) ) - { - Error( "MPI_Init failed." ); - } - - StatsDB_InitStatsDatabase( argc, argv, "dbinfo_vvis.txt" ); -} - - -void ProcessBasePortalVis( int iThread, uint64 iPortal, MessageBuffer *pBuf ) -{ - CTimeAdder adder( &g_CPUTime ); - - BasePortalVis( iThread, iPortal ); - - // Send my result to the master - if ( pBuf ) - { - portal_t * p = &portals[iPortal]; - pBuf->write( p->portalfront, portalbytes ); - pBuf->write( p->portalflood, portalbytes ); - } -} - - -void ReceiveBasePortalVis( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ) -{ - portal_t * p = &portals[iWorkUnit]; - if ( p->portalflood != 0 || p->portalfront != 0 || p->portalvis != 0) - { - Msg("Duplicate portal %llu\n", iWorkUnit); - } - - if ( pBuf->getLen() - pBuf->getOffset() != portalbytes*2 ) - Error( "Invalid packet in ReceiveBasePortalVis." ); - - // - // allocate memory for bitwise vis solutions for this portal - // - p->portalfront = (byte*)malloc (portalbytes); - pBuf->read( p->portalfront, portalbytes ); - - p->portalflood = (byte*)malloc (portalbytes); - pBuf->read( p->portalflood, portalbytes ); - - p->portalvis = (byte*)malloc (portalbytes); - memset (p->portalvis, 0, portalbytes); - - p->nummightsee = CountBits( p->portalflood, g_numportals*2 ); -} - - -//----------------------------------------- -// -// Run BasePortalVis across all available processing nodes -// Then collect and redistribute the results. -// -void RunMPIBasePortalVis() -{ - int i; - - Msg( "\n\nportalbytes: %d\nNum Work Units: %d\nTotal data size: %d\n", portalbytes, g_numportals*2, portalbytes*g_numportals*2 ); - Msg("%-20s ", "BasePortalVis:"); - if ( g_bMPIMaster ) - StartPacifier(""); - - - VMPI_SetCurrentStage( "RunMPIBasePortalVis" ); - - // Note: we're aiming for about 1500 portals in a map, so about 3000 work units. - g_CPUTime.Init(); - double elapsed = DistributeWork( - g_numportals * 2, // # work units - VMPI_DISTRIBUTEWORK_PACKETID, // packet ID - ProcessBasePortalVis, // Worker function to process work units - ReceiveBasePortalVis // Master function to receive work results - ); - - if ( g_bMPIMaster ) - { - EndPacifier( false ); - Msg( " (%d)\n", (int)elapsed ); - } - - // - // Distribute the results to all the workers. - // - if ( g_bMPIMaster ) - { - if ( !fastvis ) - { - VMPI_SetCurrentStage( "SendPortalResults" ); - - // Store all the portal results in a temp file and multicast that to the workers. - CUtlVector allPortalData; - allPortalData.SetSize( g_numportals * 2 * portalbytes * 2 ); - - char *pOut = allPortalData.Base(); - for ( i=0; i < g_numportals * 2; i++) - { - portal_t *p = &portals[i]; - - memcpy( pOut, p->portalfront, portalbytes ); - pOut += portalbytes; - - memcpy( pOut, p->portalflood, portalbytes ); - pOut += portalbytes; - } - - const char *pVirtualFilename = "--portal-results--"; - VMPI_FileSystem_CreateVirtualFile( pVirtualFilename, allPortalData.Base(), allPortalData.Count() ); - - char cPacketID[2] = { VMPI_VVIS_PACKET_ID, VMPI_BASEPORTALVIS_RESULTS }; - VMPI_Send2Chunks( cPacketID, sizeof( cPacketID ), pVirtualFilename, strlen( pVirtualFilename ) + 1, VMPI_PERSISTENT ); - } - } - else - { - VMPI_SetCurrentStage( "RecvPortalResults" ); - - // Wait until we've received the filename from the master. - while ( g_BasePortalVisResultsFilename.Count() == 0 ) - { - VMPI_DispatchNextMessage(); - } - - // Open - FileHandle_t fp = g_pFileSystem->Open( g_BasePortalVisResultsFilename.Base(), "rb", VMPI_VIRTUAL_FILES_PATH_ID ); - if ( !fp ) - Error( "Can't open '%s' to read portal info.", g_BasePortalVisResultsFilename.Base() ); - - for ( i=0; i < g_numportals * 2; i++) - { - portal_t *p = &portals[i]; - - p->portalfront = (byte*)malloc (portalbytes); - g_pFileSystem->Read( p->portalfront, portalbytes, fp ); - - p->portalflood = (byte*)malloc (portalbytes); - g_pFileSystem->Read( p->portalflood, portalbytes, fp ); - - p->portalvis = (byte*)malloc (portalbytes); - memset (p->portalvis, 0, portalbytes); - - p->nummightsee = CountBits (p->portalflood, g_numportals*2); - } - - g_pFileSystem->Close( fp ); - } - - - if ( !g_bMPIMaster ) - { - if ( g_iVMPIVerboseLevel >= 1 ) - Msg( "\n%% worker CPU utilization during BasePortalVis: %.1f\n", (g_CPUTime.GetSeconds() * 100.0f / elapsed) / numthreads ); - } -} - - - -void ProcessPortalFlow( int iThread, uint64 iPortal, MessageBuffer *pBuf ) -{ - // Process Portal and distribute results - CTimeAdder adder( &g_CPUTime ); - - PortalFlow( iThread, iPortal ); - - // Send my result to root and potentially the other slaves - // The slave results are read in RecursiveLeafFlow - // - if ( pBuf ) - { - portal_t * p = sorted_portals[iPortal]; - pBuf->write( p->portalvis, portalbytes ); - } -} - - -void ReceivePortalFlow( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ) -{ - portal_t *p = sorted_portals[iWorkUnit]; - - if ( p->status != stat_done ) - { - pBuf->read( p->portalvis, portalbytes ); - p->status = stat_done; - - - // Multicast the status of this portal out. - if ( g_pPortalMCSocket ) - { - char cPacketID[2] = { VMPI_VVIS_PACKET_ID, VMPI_PORTALFLOW_RESULTS }; - void *chunks[4] = { cPacketID, &g_PortalMCThreadUniqueID, &iWorkUnit, p->portalvis }; - int chunkLengths[4] = { sizeof( cPacketID ), sizeof( g_PortalMCThreadUniqueID ), sizeof( iWorkUnit ), portalbytes }; - - g_pPortalMCSocket->SendChunksTo( &g_PortalMCAddr, chunks, chunkLengths, ARRAYSIZE( chunks ) ); - } - } -} - - -DWORD WINAPI PortalMCThreadFn( LPVOID p ) -{ - CUtlVector data; - data.SetSize( portalbytes + 128 ); - - DWORD waitTime = 0; - while ( WaitForSingleObject( g_MCThreadExitEvent.GetEventHandle(), waitTime ) != WAIT_OBJECT_0 ) - { - CIPAddr ipFrom; - int len = g_pPortalMCSocket->RecvFrom( data.Base(), data.Count(), &ipFrom ); - if ( len == -1 ) - { - waitTime = 20; - } - else - { - // These lengths must match exactly what is sent in ReceivePortalFlow. - if ( len == 2 + sizeof( g_PortalMCThreadUniqueID ) + sizeof( int ) + portalbytes ) - { - // Perform more validation... - if ( data[0] == VMPI_VVIS_PACKET_ID && data[1] == VMPI_PORTALFLOW_RESULTS ) - { - if ( *((unsigned long*)&data[2]) == g_PortalMCThreadUniqueID ) - { - int iWorkUnit = *((int*)&data[6]); - if ( iWorkUnit >= 0 && iWorkUnit < g_numportals*2 ) - { - portal_t *p = sorted_portals[iWorkUnit]; - if ( p ) - { - ++g_nMulticastPortalsReceived; - memcpy( p->portalvis, &data[10], portalbytes ); - p->status = stat_done; - waitTime = 0; - } - } - } - } - } - } - } - - return 0; -} - - -void MCThreadCleanupFn() -{ - g_MCThreadExitEvent.SetEvent(); -} - - -// --------------------------------------------------------------------------------- // -// Cheesy hack to let them stop the job early and keep the results of what has -// been done so far. -// --------------------------------------------------------------------------------- // - -class CVisDistributeWorkCallbacks : public IWorkUnitDistributorCallbacks -{ -public: - CVisDistributeWorkCallbacks() - { - m_bExitedEarly = false; - m_iState = STATE_NONE; - } - - virtual bool Update() - { - if ( kbhit() ) - { - int key = toupper( getch() ); - if ( m_iState == STATE_NONE ) - { - if ( key == 'M' ) - { - m_iState = STATE_AT_MENU; - Warning("\n\n" - "----------------------\n" - "1. Write scratchpad file.\n" - "2. Exit early and use fast vis for remaining portals.\n" - "\n" - "0. Exit menu.\n" - "----------------------\n" - "\n" - ); - } - } - else if ( m_iState == STATE_AT_MENU ) - { - if ( key == '1' ) - { - Warning( - "\n" - "\nWriting scratchpad file." - "\nCommand line: scratchpad3dviewer -file scratch.pad\n" - "\nRed portals are the portals that are fast vis'd." - "\n" - ); - m_iState = STATE_NONE; - IScratchPad3D *pPad = ScratchPad3D_Create( "scratch.pad" ); - if ( pPad ) - { - ScratchPad_DrawWorld( pPad, false ); - - // Draw the portals that haven't been vis'd. - for ( int i=0; i < g_numportals*2; i++ ) - { - portal_t *p = sorted_portals[i]; - ScratchPad_DrawWinding( pPad, p->winding->numpoints, p->winding->points, Vector( 1, 0, 0 ), Vector( .3, .3, .3 ) ); - } - - pPad->Release(); - } - } - else if ( key == '2' ) - { - // Exit the process early. - m_bExitedEarly = true; - return true; - } - else if ( key == '0' ) - { - m_iState = STATE_NONE; - Warning( "\n\nExited menu.\n\n" ); - } - } - } - - return false; - } - -public: - enum - { - STATE_NONE, - STATE_AT_MENU - }; - - bool m_bExitedEarly; - int m_iState; // STATE_ enum. -}; - - -CVisDistributeWorkCallbacks g_VisDistributeWorkCallbacks; - - -void CheckExitedEarly() -{ - if ( g_VisDistributeWorkCallbacks.m_bExitedEarly ) - { - Warning( "\nExited early, using fastvis results...\n" ); - Warning( "Exited early, using fastvis results...\n" ); - - // Use the fastvis results for portals that we didn't get results for. - for ( int i=0; i < g_numportals*2; i++ ) - { - if ( sorted_portals[i]->status != stat_done ) - { - sorted_portals[i]->portalvis = sorted_portals[i]->portalflood; - sorted_portals[i]->status = stat_done; - } - } - } -} - - -//----------------------------------------- -// -// Run PortalFlow across all available processing nodes -// -void RunMPIPortalFlow() -{ - Msg( "%-20s ", "MPIPortalFlow:" ); - if ( g_bMPIMaster ) - StartPacifier(""); - - // Workers wait until we get the MC socket address. - g_PortalMCThreadUniqueID = StatsDB_GetUniqueJobID(); - if ( g_bMPIMaster ) - { - CCycleCount cnt; - cnt.Sample(); - CUniformRandomStream randomStream; - randomStream.SetSeed( cnt.GetMicroseconds() ); - - g_PortalMCAddr.port = randomStream.RandomInt( 22000, 25000 ); // Pulled out of something else. - g_PortalMCAddr.ip[0] = (unsigned char)RandomInt( 225, 238 ); - g_PortalMCAddr.ip[1] = (unsigned char)RandomInt( 0, 255 ); - g_PortalMCAddr.ip[2] = (unsigned char)RandomInt( 0, 255 ); - g_PortalMCAddr.ip[3] = (unsigned char)RandomInt( 3, 255 ); - - g_pPortalMCSocket = CreateIPSocket(); - int i=0; - for ( i; i < 5; i++ ) - { - if ( g_pPortalMCSocket->BindToAny( randomStream.RandomInt( 20000, 30000 ) ) ) - break; - } - if ( i == 5 ) - { - Error( "RunMPIPortalFlow: can't open a socket to multicast on." ); - } - - char cPacketID[2] = { VMPI_VVIS_PACKET_ID, VMPI_SUBPACKETID_MC_ADDR }; - VMPI_Send2Chunks( cPacketID, sizeof( cPacketID ), &g_PortalMCAddr, sizeof( g_PortalMCAddr ), VMPI_PERSISTENT ); - } - else - { - VMPI_SetCurrentStage( "wait for MC address" ); - - while ( !g_bGotMCAddr ) - { - VMPI_DispatchNextMessage(); - } - - // Open our multicast receive socket. - g_pPortalMCSocket = CreateMulticastListenSocket( g_PortalMCAddr ); - if ( !g_pPortalMCSocket ) - { - char err[512]; - IP_GetLastErrorString( err, sizeof( err ) ); - Error( "RunMPIPortalFlow: CreateMulticastListenSocket failed. (%s).", err ); - } - - // Make a thread to listen for the data on the multicast socket. - DWORD dwDummy = 0; - g_MCThreadExitEvent.Init( false, false ); - - // Make sure we kill the MC thread if the app exits ungracefully. - CmdLib_AtCleanup( MCThreadCleanupFn ); - - g_hMCThread = CreateThread( - NULL, - 0, - PortalMCThreadFn, - NULL, - 0, - &dwDummy ); - - if ( !g_hMCThread ) - { - Error( "RunMPIPortalFlow: CreateThread failed for multicast receive thread." ); - } - } - - VMPI_SetCurrentStage( "RunMPIBasePortalFlow" ); - - - g_pDistributeWorkCallbacks = &g_VisDistributeWorkCallbacks; - - g_CPUTime.Init(); - double elapsed = DistributeWork( - g_numportals * 2, // # work units - VMPI_DISTRIBUTEWORK_PACKETID, // packet ID - ProcessPortalFlow, // Worker function to process work units - ReceivePortalFlow // Master function to receive work results - ); - - g_pDistributeWorkCallbacks = NULL; - - CheckExitedEarly(); - - // Stop the multicast stuff. - VMPI_DeletePortalMCSocket(); - - if( !g_bMPIMaster ) - { - if ( g_iVMPIVerboseLevel >= 1 ) - { - Msg( "Received %d (out of %d) portals from multicast.\n", g_nMulticastPortalsReceived, g_numportals * 2 ); - Msg( "%.1f%% CPU utilization during PortalFlow\n", (g_CPUTime.GetSeconds() * 100.0f / elapsed) / numthreads ); - } - - Msg( "VVIS worker finished. Over and out.\n" ); - VMPI_SetCurrentStage( "worker done" ); - - CmdLib_Exit( 0 ); - } - - if ( g_bMPIMaster ) - { - EndPacifier( false ); - Msg( " (%d)\n", (int)elapsed ); - } -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include +#include "vis.h" +#include "threads.h" +#include "stdlib.h" +#include "pacifier.h" +#include "mpi_stats.h" +#include "vmpi.h" +#include "vmpi_dispatch.h" +#include "vmpi_filesystem.h" +#include "vmpi_distribute_work.h" +#include "iphelpers.h" +#include "threadhelpers.h" +#include "vstdlib/random.h" +#include "vmpi_tools_shared.h" +#include +#include "scratchpad_helpers.h" + + +#define VMPI_VVIS_PACKET_ID 1 + // Sub packet IDs. + #define VMPI_SUBPACKETID_DISCONNECT_NOTIFY 3 // We send ourselves this when there is a disconnect. + #define VMPI_SUBPACKETID_BASEPORTALVIS 5 + #define VMPI_SUBPACKETID_PORTALFLOW 6 + #define VMPI_BASEPORTALVIS_RESULTS 7 + #define VMPI_BASEPORTALVIS_WORKER_DONE 8 + #define VMPI_PORTALFLOW_RESULTS 9 + #define VMPI_SUBPACKETID_BASEPORTALVIS_SYNC 11 + #define VMPI_SUBPACKETID_PORTALFLOW_SYNC 12 + #define VMPI_SUBPACKETID_MC_ADDR 13 + +// DistributeWork owns this packet ID. +#define VMPI_DISTRIBUTEWORK_PACKETID 2 + + +extern bool fastvis; + +// The worker waits until these are true. +bool g_bBasePortalVisSync = false; +bool g_bPortalFlowSync = false; + +CUtlVector g_BasePortalVisResultsFilename; + +CCycleCount g_CPUTime; + + +// This stuff is all for the multicast channel the master uses to send out the portal results. +ISocket *g_pPortalMCSocket = NULL; +CIPAddr g_PortalMCAddr; +bool g_bGotMCAddr = false; +HANDLE g_hMCThread = NULL; +CEvent g_MCThreadExitEvent; +unsigned long g_PortalMCThreadUniqueID = 0; +int g_nMulticastPortalsReceived = 0; + + +// Handle VVIS packets. +bool VVIS_DispatchFn( MessageBuffer *pBuf, int iSource, int iPacketID ) +{ + switch ( pBuf->data[1] ) + { + case VMPI_SUBPACKETID_MC_ADDR: + { + pBuf->setOffset( 2 ); + pBuf->read( &g_PortalMCAddr, sizeof( g_PortalMCAddr ) ); + g_bGotMCAddr = true; + return true; + } + + case VMPI_SUBPACKETID_DISCONNECT_NOTIFY: + { + // This is just used to cause nonblocking dispatches to jump out so loops like the one + // in AppBarrier can handle the fact that there are disconnects. + return true; + } + + case VMPI_SUBPACKETID_BASEPORTALVIS_SYNC: + { + g_bBasePortalVisSync = true; + return true; + } + + case VMPI_SUBPACKETID_PORTALFLOW_SYNC: + { + g_bPortalFlowSync = true; + return true; + } + + case VMPI_BASEPORTALVIS_RESULTS: + { + const char *pFilename = &pBuf->data[2]; + g_BasePortalVisResultsFilename.CopyArray( pFilename, strlen( pFilename ) + 1 ); + return true; + } + + default: + { + return false; + } + } +} +CDispatchReg g_VVISDispatchReg( VMPI_VVIS_PACKET_ID, VVIS_DispatchFn ); // register to handle the messages we want +CDispatchReg g_DistributeWorkReg( VMPI_DISTRIBUTEWORK_PACKETID, DistributeWorkDispatch ); + + + +void VMPI_DeletePortalMCSocket() +{ + // Stop the thread if it exists. + if ( g_hMCThread ) + { + g_MCThreadExitEvent.SetEvent(); + WaitForSingleObject( g_hMCThread, INFINITE ); + CloseHandle( g_hMCThread ); + g_hMCThread = NULL; + } + + if ( g_pPortalMCSocket ) + { + g_pPortalMCSocket->Release(); + g_pPortalMCSocket = NULL; + } +} + + +void VVIS_SetupMPI( int &argc, char **&argv ) +{ + if ( !VMPI_FindArg( argc, argv, "-mpi", "" ) && !VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Worker ), "" ) ) + return; + + CmdLib_AtCleanup( VMPI_Stats_Term ); + CmdLib_AtCleanup( VMPI_DeletePortalMCSocket ); + + VMPI_Stats_InstallSpewHook(); + + // Force local mode? + VMPIRunMode mode; + if ( VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Local ), "" ) ) + mode = VMPI_RUN_LOCAL; + else + mode = VMPI_RUN_NETWORKED; + + // + // Extract mpi specific arguments + // + Msg( "Initializing VMPI...\n" ); + if ( !VMPI_Init( argc, argv, "dependency_info_vvis.txt", HandleMPIDisconnect, mode ) ) + { + Error( "MPI_Init failed." ); + } + + StatsDB_InitStatsDatabase( argc, argv, "dbinfo_vvis.txt" ); +} + + +void ProcessBasePortalVis( int iThread, uint64 iPortal, MessageBuffer *pBuf ) +{ + CTimeAdder adder( &g_CPUTime ); + + BasePortalVis( iThread, iPortal ); + + // Send my result to the master + if ( pBuf ) + { + portal_t * p = &portals[iPortal]; + pBuf->write( p->portalfront, portalbytes ); + pBuf->write( p->portalflood, portalbytes ); + } +} + + +void ReceiveBasePortalVis( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ) +{ + portal_t * p = &portals[iWorkUnit]; + if ( p->portalflood != 0 || p->portalfront != 0 || p->portalvis != 0) + { + Msg("Duplicate portal %llu\n", iWorkUnit); + } + + if ( pBuf->getLen() - pBuf->getOffset() != portalbytes*2 ) + Error( "Invalid packet in ReceiveBasePortalVis." ); + + // + // allocate memory for bitwise vis solutions for this portal + // + p->portalfront = (byte*)malloc (portalbytes); + pBuf->read( p->portalfront, portalbytes ); + + p->portalflood = (byte*)malloc (portalbytes); + pBuf->read( p->portalflood, portalbytes ); + + p->portalvis = (byte*)malloc (portalbytes); + memset (p->portalvis, 0, portalbytes); + + p->nummightsee = CountBits( p->portalflood, g_numportals*2 ); +} + + +//----------------------------------------- +// +// Run BasePortalVis across all available processing nodes +// Then collect and redistribute the results. +// +void RunMPIBasePortalVis() +{ + int i; + + Msg( "\n\nportalbytes: %d\nNum Work Units: %d\nTotal data size: %d\n", portalbytes, g_numportals*2, portalbytes*g_numportals*2 ); + Msg("%-20s ", "BasePortalVis:"); + if ( g_bMPIMaster ) + StartPacifier(""); + + + VMPI_SetCurrentStage( "RunMPIBasePortalVis" ); + + // Note: we're aiming for about 1500 portals in a map, so about 3000 work units. + g_CPUTime.Init(); + double elapsed = DistributeWork( + g_numportals * 2, // # work units + VMPI_DISTRIBUTEWORK_PACKETID, // packet ID + ProcessBasePortalVis, // Worker function to process work units + ReceiveBasePortalVis // Master function to receive work results + ); + + if ( g_bMPIMaster ) + { + EndPacifier( false ); + Msg( " (%d)\n", (int)elapsed ); + } + + // + // Distribute the results to all the workers. + // + if ( g_bMPIMaster ) + { + if ( !fastvis ) + { + VMPI_SetCurrentStage( "SendPortalResults" ); + + // Store all the portal results in a temp file and multicast that to the workers. + CUtlVector allPortalData; + allPortalData.SetSize( g_numportals * 2 * portalbytes * 2 ); + + char *pOut = allPortalData.Base(); + for ( i=0; i < g_numportals * 2; i++) + { + portal_t *p = &portals[i]; + + memcpy( pOut, p->portalfront, portalbytes ); + pOut += portalbytes; + + memcpy( pOut, p->portalflood, portalbytes ); + pOut += portalbytes; + } + + const char *pVirtualFilename = "--portal-results--"; + VMPI_FileSystem_CreateVirtualFile( pVirtualFilename, allPortalData.Base(), allPortalData.Count() ); + + char cPacketID[2] = { VMPI_VVIS_PACKET_ID, VMPI_BASEPORTALVIS_RESULTS }; + VMPI_Send2Chunks( cPacketID, sizeof( cPacketID ), pVirtualFilename, strlen( pVirtualFilename ) + 1, VMPI_PERSISTENT ); + } + } + else + { + VMPI_SetCurrentStage( "RecvPortalResults" ); + + // Wait until we've received the filename from the master. + while ( g_BasePortalVisResultsFilename.Count() == 0 ) + { + VMPI_DispatchNextMessage(); + } + + // Open + FileHandle_t fp = g_pFileSystem->Open( g_BasePortalVisResultsFilename.Base(), "rb", VMPI_VIRTUAL_FILES_PATH_ID ); + if ( !fp ) + Error( "Can't open '%s' to read portal info.", g_BasePortalVisResultsFilename.Base() ); + + for ( i=0; i < g_numportals * 2; i++) + { + portal_t *p = &portals[i]; + + p->portalfront = (byte*)malloc (portalbytes); + g_pFileSystem->Read( p->portalfront, portalbytes, fp ); + + p->portalflood = (byte*)malloc (portalbytes); + g_pFileSystem->Read( p->portalflood, portalbytes, fp ); + + p->portalvis = (byte*)malloc (portalbytes); + memset (p->portalvis, 0, portalbytes); + + p->nummightsee = CountBits (p->portalflood, g_numportals*2); + } + + g_pFileSystem->Close( fp ); + } + + + if ( !g_bMPIMaster ) + { + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "\n%% worker CPU utilization during BasePortalVis: %.1f\n", (g_CPUTime.GetSeconds() * 100.0f / elapsed) / numthreads ); + } +} + + + +void ProcessPortalFlow( int iThread, uint64 iPortal, MessageBuffer *pBuf ) +{ + // Process Portal and distribute results + CTimeAdder adder( &g_CPUTime ); + + PortalFlow( iThread, iPortal ); + + // Send my result to root and potentially the other slaves + // The slave results are read in RecursiveLeafFlow + // + if ( pBuf ) + { + portal_t * p = sorted_portals[iPortal]; + pBuf->write( p->portalvis, portalbytes ); + } +} + + +void ReceivePortalFlow( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ) +{ + portal_t *p = sorted_portals[iWorkUnit]; + + if ( p->status != stat_done ) + { + pBuf->read( p->portalvis, portalbytes ); + p->status = stat_done; + + + // Multicast the status of this portal out. + if ( g_pPortalMCSocket ) + { + char cPacketID[2] = { VMPI_VVIS_PACKET_ID, VMPI_PORTALFLOW_RESULTS }; + void *chunks[4] = { cPacketID, &g_PortalMCThreadUniqueID, &iWorkUnit, p->portalvis }; + int chunkLengths[4] = { sizeof( cPacketID ), sizeof( g_PortalMCThreadUniqueID ), sizeof( iWorkUnit ), portalbytes }; + + g_pPortalMCSocket->SendChunksTo( &g_PortalMCAddr, chunks, chunkLengths, ARRAYSIZE( chunks ) ); + } + } +} + + +DWORD WINAPI PortalMCThreadFn( LPVOID p ) +{ + CUtlVector data; + data.SetSize( portalbytes + 128 ); + + DWORD waitTime = 0; + while ( WaitForSingleObject( g_MCThreadExitEvent.GetEventHandle(), waitTime ) != WAIT_OBJECT_0 ) + { + CIPAddr ipFrom; + int len = g_pPortalMCSocket->RecvFrom( data.Base(), data.Count(), &ipFrom ); + if ( len == -1 ) + { + waitTime = 20; + } + else + { + // These lengths must match exactly what is sent in ReceivePortalFlow. + if ( len == 2 + sizeof( g_PortalMCThreadUniqueID ) + sizeof( int ) + portalbytes ) + { + // Perform more validation... + if ( data[0] == VMPI_VVIS_PACKET_ID && data[1] == VMPI_PORTALFLOW_RESULTS ) + { + if ( *((unsigned long*)&data[2]) == g_PortalMCThreadUniqueID ) + { + int iWorkUnit = *((int*)&data[6]); + if ( iWorkUnit >= 0 && iWorkUnit < g_numportals*2 ) + { + portal_t *p = sorted_portals[iWorkUnit]; + if ( p ) + { + ++g_nMulticastPortalsReceived; + memcpy( p->portalvis, &data[10], portalbytes ); + p->status = stat_done; + waitTime = 0; + } + } + } + } + } + } + } + + return 0; +} + + +void MCThreadCleanupFn() +{ + g_MCThreadExitEvent.SetEvent(); +} + + +// --------------------------------------------------------------------------------- // +// Cheesy hack to let them stop the job early and keep the results of what has +// been done so far. +// --------------------------------------------------------------------------------- // + +class CVisDistributeWorkCallbacks : public IWorkUnitDistributorCallbacks +{ +public: + CVisDistributeWorkCallbacks() + { + m_bExitedEarly = false; + m_iState = STATE_NONE; + } + + virtual bool Update() + { + if ( kbhit() ) + { + int key = toupper( getch() ); + if ( m_iState == STATE_NONE ) + { + if ( key == 'M' ) + { + m_iState = STATE_AT_MENU; + Warning("\n\n" + "----------------------\n" + "1. Write scratchpad file.\n" + "2. Exit early and use fast vis for remaining portals.\n" + "\n" + "0. Exit menu.\n" + "----------------------\n" + "\n" + ); + } + } + else if ( m_iState == STATE_AT_MENU ) + { + if ( key == '1' ) + { + Warning( + "\n" + "\nWriting scratchpad file." + "\nCommand line: scratchpad3dviewer -file scratch.pad\n" + "\nRed portals are the portals that are fast vis'd." + "\n" + ); + m_iState = STATE_NONE; + IScratchPad3D *pPad = ScratchPad3D_Create( "scratch.pad" ); + if ( pPad ) + { + ScratchPad_DrawWorld( pPad, false ); + + // Draw the portals that haven't been vis'd. + for ( int i=0; i < g_numportals*2; i++ ) + { + portal_t *p = sorted_portals[i]; + ScratchPad_DrawWinding( pPad, p->winding->numpoints, p->winding->points, Vector( 1, 0, 0 ), Vector( .3, .3, .3 ) ); + } + + pPad->Release(); + } + } + else if ( key == '2' ) + { + // Exit the process early. + m_bExitedEarly = true; + return true; + } + else if ( key == '0' ) + { + m_iState = STATE_NONE; + Warning( "\n\nExited menu.\n\n" ); + } + } + } + + return false; + } + +public: + enum + { + STATE_NONE, + STATE_AT_MENU + }; + + bool m_bExitedEarly; + int m_iState; // STATE_ enum. +}; + + +CVisDistributeWorkCallbacks g_VisDistributeWorkCallbacks; + + +void CheckExitedEarly() +{ + if ( g_VisDistributeWorkCallbacks.m_bExitedEarly ) + { + Warning( "\nExited early, using fastvis results...\n" ); + Warning( "Exited early, using fastvis results...\n" ); + + // Use the fastvis results for portals that we didn't get results for. + for ( int i=0; i < g_numportals*2; i++ ) + { + if ( sorted_portals[i]->status != stat_done ) + { + sorted_portals[i]->portalvis = sorted_portals[i]->portalflood; + sorted_portals[i]->status = stat_done; + } + } + } +} + + +//----------------------------------------- +// +// Run PortalFlow across all available processing nodes +// +void RunMPIPortalFlow() +{ + Msg( "%-20s ", "MPIPortalFlow:" ); + if ( g_bMPIMaster ) + StartPacifier(""); + + // Workers wait until we get the MC socket address. + g_PortalMCThreadUniqueID = StatsDB_GetUniqueJobID(); + if ( g_bMPIMaster ) + { + CCycleCount cnt; + cnt.Sample(); + CUniformRandomStream randomStream; + randomStream.SetSeed( cnt.GetMicroseconds() ); + + g_PortalMCAddr.port = randomStream.RandomInt( 22000, 25000 ); // Pulled out of something else. + g_PortalMCAddr.ip[0] = (unsigned char)RandomInt( 225, 238 ); + g_PortalMCAddr.ip[1] = (unsigned char)RandomInt( 0, 255 ); + g_PortalMCAddr.ip[2] = (unsigned char)RandomInt( 0, 255 ); + g_PortalMCAddr.ip[3] = (unsigned char)RandomInt( 3, 255 ); + + g_pPortalMCSocket = CreateIPSocket(); + int i=0; + for ( i; i < 5; i++ ) + { + if ( g_pPortalMCSocket->BindToAny( randomStream.RandomInt( 20000, 30000 ) ) ) + break; + } + if ( i == 5 ) + { + Error( "RunMPIPortalFlow: can't open a socket to multicast on." ); + } + + char cPacketID[2] = { VMPI_VVIS_PACKET_ID, VMPI_SUBPACKETID_MC_ADDR }; + VMPI_Send2Chunks( cPacketID, sizeof( cPacketID ), &g_PortalMCAddr, sizeof( g_PortalMCAddr ), VMPI_PERSISTENT ); + } + else + { + VMPI_SetCurrentStage( "wait for MC address" ); + + while ( !g_bGotMCAddr ) + { + VMPI_DispatchNextMessage(); + } + + // Open our multicast receive socket. + g_pPortalMCSocket = CreateMulticastListenSocket( g_PortalMCAddr ); + if ( !g_pPortalMCSocket ) + { + char err[512]; + IP_GetLastErrorString( err, sizeof( err ) ); + Error( "RunMPIPortalFlow: CreateMulticastListenSocket failed. (%s).", err ); + } + + // Make a thread to listen for the data on the multicast socket. + DWORD dwDummy = 0; + g_MCThreadExitEvent.Init( false, false ); + + // Make sure we kill the MC thread if the app exits ungracefully. + CmdLib_AtCleanup( MCThreadCleanupFn ); + + g_hMCThread = CreateThread( + NULL, + 0, + PortalMCThreadFn, + NULL, + 0, + &dwDummy ); + + if ( !g_hMCThread ) + { + Error( "RunMPIPortalFlow: CreateThread failed for multicast receive thread." ); + } + } + + VMPI_SetCurrentStage( "RunMPIBasePortalFlow" ); + + + g_pDistributeWorkCallbacks = &g_VisDistributeWorkCallbacks; + + g_CPUTime.Init(); + double elapsed = DistributeWork( + g_numportals * 2, // # work units + VMPI_DISTRIBUTEWORK_PACKETID, // packet ID + ProcessPortalFlow, // Worker function to process work units + ReceivePortalFlow // Master function to receive work results + ); + + g_pDistributeWorkCallbacks = NULL; + + CheckExitedEarly(); + + // Stop the multicast stuff. + VMPI_DeletePortalMCSocket(); + + if( !g_bMPIMaster ) + { + if ( g_iVMPIVerboseLevel >= 1 ) + { + Msg( "Received %d (out of %d) portals from multicast.\n", g_nMulticastPortalsReceived, g_numportals * 2 ); + Msg( "%.1f%% CPU utilization during PortalFlow\n", (g_CPUTime.GetSeconds() * 100.0f / elapsed) / numthreads ); + } + + Msg( "VVIS worker finished. Over and out.\n" ); + VMPI_SetCurrentStage( "worker done" ); + + CmdLib_Exit( 0 ); + } + + if ( g_bMPIMaster ) + { + EndPacifier( false ); + Msg( " (%d)\n", (int)elapsed ); + } +} + diff --git a/mp/src/utils/vvis/mpivis.h b/mp/src/utils/vvis/mpivis.h index a6ee349e..ed44aa25 100644 --- a/mp/src/utils/vvis/mpivis.h +++ b/mp/src/utils/vvis/mpivis.h @@ -1,21 +1,21 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - -#ifndef MPIVIS_H -#define MPIVIS_H -#ifdef _WIN32 -#pragma once -#endif - - -void VVIS_SetupMPI( int &argc, char **&argv ); - - -void RunMPIBasePortalVis(); -void RunMPIPortalFlow(); - - -#endif // MPIVIS_H +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MPIVIS_H +#define MPIVIS_H +#ifdef _WIN32 +#pragma once +#endif + + +void VVIS_SetupMPI( int &argc, char **&argv ); + + +void RunMPIBasePortalVis(); +void RunMPIPortalFlow(); + + +#endif // MPIVIS_H diff --git a/mp/src/utils/vvis/vis.h b/mp/src/utils/vvis/vis.h index c4b58414..0a4c314e 100644 --- a/mp/src/utils/vvis/vis.h +++ b/mp/src/utils/vvis/vis.h @@ -1,125 +1,125 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// vis.h - -#include "cmdlib.h" -#include "mathlib/mathlib.h" -#include "bsplib.h" - - -#define MAX_PORTALS 65536 - -#define PORTALFILE "PRT1" - -extern bool g_bUseRadius; // prototyping TF2, "radius vis" solution -extern double g_VisRadius; // the radius for the TF2 "radius vis" - -struct plane_t -{ - Vector normal; - float dist; -}; - -#define MAX_POINTS_ON_WINDING 64 -#define MAX_POINTS_ON_FIXED_WINDING 12 - -struct winding_t -{ - qboolean original; // don't free, it's part of the portal - int numpoints; - Vector points[MAX_POINTS_ON_FIXED_WINDING]; // variable sized -}; - -winding_t *NewWinding (int points); -void FreeWinding (winding_t *w); -winding_t *CopyWinding (winding_t *w); - - -typedef enum {stat_none, stat_working, stat_done} vstatus_t; -struct portal_t -{ - plane_t plane; // normal pointing into neighbor - int leaf; // neighbor - - Vector origin; // for fast clip testing - float radius; - - winding_t *winding; - vstatus_t status; - byte *portalfront; // [portals], preliminary - byte *portalflood; // [portals], intermediate - byte *portalvis; // [portals], final - - int nummightsee; // bit count on portalflood for sort -}; - -struct leaf_t -{ - CUtlVector portals; -}; - - -struct pstack_t -{ - byte mightsee[MAX_PORTALS/8]; // bit string - pstack_t *next; - leaf_t *leaf; - portal_t *portal; // portal exiting - winding_t *source; - winding_t *pass; - - winding_t windings[3]; // source, pass, temp in any order - int freewindings[3]; - - plane_t portalplane; -}; - -struct threaddata_t -{ - portal_t *base; - int c_chains; - pstack_t pstack_head; -}; - -extern int g_numportals; -extern int portalclusters; - -extern portal_t *portals; -extern leaf_t *leafs; - -extern int c_portaltest, c_portalpass, c_portalcheck; -extern int c_portalskip, c_leafskip; -extern int c_vistest, c_mighttest; -extern int c_chains; - -extern byte *vismap, *vismap_p, *vismap_end; // past visfile - -extern int testlevel; - -extern byte *uncompressed; - -extern int leafbytes, leaflongs; -extern int portalbytes, portallongs; - - -void LeafFlow (int leafnum); - - -void BasePortalVis (int iThread, int portalnum); -void BetterPortalVis (int portalnum); -void PortalFlow (int iThread, int portalnum); -void WritePortalTrace( const char *source ); - -extern portal_t *sorted_portals[MAX_MAP_PORTALS*2]; -extern int g_TraceClusterStart, g_TraceClusterStop; - -int CountBits (byte *bits, int numbits); - -#define CheckBit( bitstring, bitNumber ) ( (bitstring)[ ((bitNumber) >> 3) ] & ( 1 << ( (bitNumber) & 7 ) ) ) -#define SetBit( bitstring, bitNumber ) ( (bitstring)[ ((bitNumber) >> 3) ] |= ( 1 << ( (bitNumber) & 7 ) ) ) -#define ClearBit( bitstring, bitNumber ) ( (bitstring)[ ((bitNumber) >> 3) ] &= ~( 1 << ( (bitNumber) & 7 ) ) ) +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vis.h + +#include "cmdlib.h" +#include "mathlib/mathlib.h" +#include "bsplib.h" + + +#define MAX_PORTALS 65536 + +#define PORTALFILE "PRT1" + +extern bool g_bUseRadius; // prototyping TF2, "radius vis" solution +extern double g_VisRadius; // the radius for the TF2 "radius vis" + +struct plane_t +{ + Vector normal; + float dist; +}; + +#define MAX_POINTS_ON_WINDING 64 +#define MAX_POINTS_ON_FIXED_WINDING 12 + +struct winding_t +{ + qboolean original; // don't free, it's part of the portal + int numpoints; + Vector points[MAX_POINTS_ON_FIXED_WINDING]; // variable sized +}; + +winding_t *NewWinding (int points); +void FreeWinding (winding_t *w); +winding_t *CopyWinding (winding_t *w); + + +typedef enum {stat_none, stat_working, stat_done} vstatus_t; +struct portal_t +{ + plane_t plane; // normal pointing into neighbor + int leaf; // neighbor + + Vector origin; // for fast clip testing + float radius; + + winding_t *winding; + vstatus_t status; + byte *portalfront; // [portals], preliminary + byte *portalflood; // [portals], intermediate + byte *portalvis; // [portals], final + + int nummightsee; // bit count on portalflood for sort +}; + +struct leaf_t +{ + CUtlVector portals; +}; + + +struct pstack_t +{ + byte mightsee[MAX_PORTALS/8]; // bit string + pstack_t *next; + leaf_t *leaf; + portal_t *portal; // portal exiting + winding_t *source; + winding_t *pass; + + winding_t windings[3]; // source, pass, temp in any order + int freewindings[3]; + + plane_t portalplane; +}; + +struct threaddata_t +{ + portal_t *base; + int c_chains; + pstack_t pstack_head; +}; + +extern int g_numportals; +extern int portalclusters; + +extern portal_t *portals; +extern leaf_t *leafs; + +extern int c_portaltest, c_portalpass, c_portalcheck; +extern int c_portalskip, c_leafskip; +extern int c_vistest, c_mighttest; +extern int c_chains; + +extern byte *vismap, *vismap_p, *vismap_end; // past visfile + +extern int testlevel; + +extern byte *uncompressed; + +extern int leafbytes, leaflongs; +extern int portalbytes, portallongs; + + +void LeafFlow (int leafnum); + + +void BasePortalVis (int iThread, int portalnum); +void BetterPortalVis (int portalnum); +void PortalFlow (int iThread, int portalnum); +void WritePortalTrace( const char *source ); + +extern portal_t *sorted_portals[MAX_MAP_PORTALS*2]; +extern int g_TraceClusterStart, g_TraceClusterStop; + +int CountBits (byte *bits, int numbits); + +#define CheckBit( bitstring, bitNumber ) ( (bitstring)[ ((bitNumber) >> 3) ] & ( 1 << ( (bitNumber) & 7 ) ) ) +#define SetBit( bitstring, bitNumber ) ( (bitstring)[ ((bitNumber) >> 3) ] |= ( 1 << ( (bitNumber) & 7 ) ) ) +#define ClearBit( bitstring, bitNumber ) ( (bitstring)[ ((bitNumber) >> 3) ] &= ~( 1 << ( (bitNumber) & 7 ) ) ) diff --git a/mp/src/utils/vvis/vvis.cpp b/mp/src/utils/vvis/vvis.cpp index 577c1cc3..0fb61388 100644 --- a/mp/src/utils/vvis/vvis.cpp +++ b/mp/src/utils/vvis/vvis.cpp @@ -1,1240 +1,1240 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// vis.c - -#include -#include "vis.h" -#include "threads.h" -#include "stdlib.h" -#include "pacifier.h" -#include "vmpi.h" -#include "mpivis.h" -#include "tier1/strtools.h" -#include "collisionutils.h" -#include "tier0/icommandline.h" -#include "vmpi_tools_shared.h" -#include "ilaunchabledll.h" -#include "tools_minidump.h" -#include "loadcmdline.h" -#include "byteswap.h" - - -int g_numportals; -int portalclusters; - -char inbase[32]; - -portal_t *portals; -leaf_t *leafs; - -int c_portaltest, c_portalpass, c_portalcheck; - -byte *uncompressedvis; - -byte *vismap, *vismap_p, *vismap_end; // past visfile -int originalvismapsize; - -int leafbytes; // (portalclusters+63)>>3 -int leaflongs; - -int portalbytes, portallongs; - -bool fastvis; -bool nosort; - -int totalvis; - -portal_t *sorted_portals[MAX_MAP_PORTALS*2]; - -bool g_bUseRadius = false; -double g_VisRadius = 4096.0f * 4096.0f; - -bool g_bLowPriority = false; - -//============================================================================= - -void PlaneFromWinding (winding_t *w, plane_t *plane) -{ - Vector v1, v2; - -// calc plane - VectorSubtract (w->points[2], w->points[1], v1); - VectorSubtract (w->points[0], w->points[1], v2); - CrossProduct (v2, v1, plane->normal); - VectorNormalize (plane->normal); - plane->dist = DotProduct (w->points[0], plane->normal); -} - - -/* -================== -NewWinding -================== -*/ -winding_t *NewWinding (int points) -{ - winding_t *w; - int size; - - if (points > MAX_POINTS_ON_WINDING) - Error ("NewWinding: %i points, max %d", points, MAX_POINTS_ON_WINDING); - - size = (int)(&((winding_t *)0)->points[points]); - w = (winding_t*)malloc (size); - memset (w, 0, size); - - return w; -} - -void pw(winding_t *w) -{ - int i; - for (i=0 ; inumpoints ; i++) - Msg ("(%5.1f, %5.1f, %5.1f)\n",w->points[i][0], w->points[i][1],w->points[i][2]); -} - -void prl(leaf_t *l) -{ - int i; - portal_t *p; - plane_t pl; - - int count = l->portals.Count(); - for (i=0 ; iportals[i]; - pl = p->plane; - Msg ("portal %4i to leaf %4i : %7.1f : (%4.1f, %4.1f, %4.1f)\n",(int)(p-portals),p->leaf,pl.dist, pl.normal[0], pl.normal[1], pl.normal[2]); - } -} - - -//============================================================================= - -/* -============= -SortPortals - -Sorts the portals from the least complex, so the later ones can reuse -the earlier information. -============= -*/ -int PComp (const void *a, const void *b) -{ - if ( (*(portal_t **)a)->nummightsee == (*(portal_t **)b)->nummightsee) - return 0; - if ( (*(portal_t **)a)->nummightsee < (*(portal_t **)b)->nummightsee) - return -1; - - return 1; -} - -void BuildTracePortals( int clusterStart ) -{ - leaf_t *leaf = &leafs[g_TraceClusterStart]; - g_numportals = leaf->portals.Count(); - for ( int i = 0; i < g_numportals; i++ ) - { - sorted_portals[i] = leaf->portals[i]; - } -} - -void SortPortals (void) -{ - int i; - - for (i=0 ; ileaf ); - } - } - - c_leafs = CountBits (leafbits, portalclusters); - - return c_leafs; -} - - -/* -=============== -ClusterMerge - -Merges the portal visibility for a leaf -=============== -*/ -void ClusterMerge (int clusternum) -{ - leaf_t *leaf; -// byte portalvector[MAX_PORTALS/8]; - byte portalvector[MAX_PORTALS/4]; // 4 because portal bytes is * 2 - byte uncompressed[MAX_MAP_LEAFS/8]; - int i, j; - int numvis; - portal_t *p; - int pnum; - - // OR together all the portalvis bits - - memset (portalvector, 0, portalbytes); - leaf = &leafs[clusternum]; - for (i=0 ; i < leaf->portals.Count(); i++) - { - p = leaf->portals[i]; - if (p->status != stat_done) - Error ("portal not done %d %p %p\n", i, p, portals); - for (j=0 ; jportalvis)[j]; - pnum = p - portals; - SetBit( portalvector, pnum ); - } - - // convert portal bits to leaf bits - numvis = LeafVectorFromPortalVector (portalvector, uncompressed); - -#if 0 - // func_viscluster makes this happen all the time because it allows a non-convex set of portals - // My analysis says this is ok, but it does make this check for errors in vis kind of useless - if ( CheckBit( uncompressed, clusternum ) ) - Warning("WARNING: Cluster portals saw into cluster\n"); -#endif - - SetBit( uncompressed, clusternum ); - numvis++; // count the leaf itself - - // save uncompressed for PHS calculation - memcpy (uncompressedvis + clusternum*leafbytes, uncompressed, leafbytes); - - qprintf ("cluster %4i : %4i visible\n", clusternum, numvis); - totalvis += numvis; -} - -static int CompressAndCrosscheckClusterVis( int clusternum ) -{ - int optimized = 0; - byte compressed[MAX_MAP_LEAFS/8]; -// -// compress the bit string -// - byte *uncompressed = uncompressedvis + clusternum*leafbytes; - for ( int i = 0; i < portalclusters; i++ ) - { - if ( i == clusternum ) - continue; - - if ( CheckBit( uncompressed, i ) ) - { - byte *other = uncompressedvis + i*leafbytes; - if ( !CheckBit( other, clusternum ) ) - { - ClearBit( uncompressed, i ); - optimized++; - } - } - } - int numbytes = CompressVis( uncompressed, compressed ); - - byte *dest = vismap_p; - vismap_p += numbytes; - - if (vismap_p > vismap_end) - Error ("Vismap expansion overflow"); - - dvis->bitofs[clusternum][DVIS_PVS] = dest-vismap; - - memcpy( dest, compressed, numbytes ); - - // check vis data - DecompressVis( vismap + dvis->bitofs[clusternum][DVIS_PVS], compressed ); - - return optimized; -} - - -/* -================== -CalcPortalVis -================== -*/ -void CalcPortalVis (void) -{ - int i; - - // fastvis just uses mightsee for a very loose bound - if( fastvis ) - { - for (i=0 ; iwinding; - VectorCopy (vec3_origin, total); - for (i=0 ; inumpoints ; i++) - { - VectorAdd (total, w->points[i], total); - } - - for (i=0 ; i<3 ; i++) - total[i] /= w->numpoints; - - bestr = 0; - for (i=0 ; inumpoints ; i++) - { - VectorSubtract (w->points[i], total, dist); - r = VectorLength (dist); - if (r > bestr) - bestr = r; - } - VectorCopy (total, p->origin); - p->radius = bestr; -} - -/* -============ -LoadPortals -============ -*/ -void LoadPortals (char *name) -{ - int i, j; - portal_t *p; - leaf_t *l; - char magic[80]; - int numpoints; - winding_t *w; - int leafnums[2]; - plane_t plane; - - FILE *f; - - // Open the portal file. - if ( g_bUseMPI ) - { - // If we're using MPI, copy off the file to a temporary first. This will download the file - // from the MPI master, then we get to use nice functions like fscanf on it. - char tempPath[MAX_PATH], tempFile[MAX_PATH]; - if ( GetTempPath( sizeof( tempPath ), tempPath ) == 0 ) - { - Error( "LoadPortals: GetTempPath failed.\n" ); - } - - if ( GetTempFileName( tempPath, "vvis_portal_", 0, tempFile ) == 0 ) - { - Error( "LoadPortals: GetTempFileName failed.\n" ); - } - - // Read all the data from the network file into memory. - FileHandle_t hFile = g_pFileSystem->Open(name, "r"); - if ( hFile == FILESYSTEM_INVALID_HANDLE ) - Error( "LoadPortals( %s ): couldn't get file from master.\n", name ); - - CUtlVector data; - data.SetSize( g_pFileSystem->Size( hFile ) ); - g_pFileSystem->Read( data.Base(), data.Count(), hFile ); - g_pFileSystem->Close( hFile ); - - // Dump it into a temp file. - f = fopen( tempFile, "wt" ); - fwrite( data.Base(), 1, data.Count(), f ); - fclose( f ); - - // Open the temp file up. - f = fopen( tempFile, "rSTD" ); // read only, sequential, temporary, delete on close - } - else - { - f = fopen( name, "r" ); - } - - if ( !f ) - Error ("LoadPortals: couldn't read %s\n",name); - - if (fscanf (f,"%79s\n%i\n%i\n",magic, &portalclusters, &g_numportals) != 3) - Error ("LoadPortals %s: failed to read header", name); - if (stricmp(magic,PORTALFILE)) - Error ("LoadPortals %s: not a portal file", name); - - Msg ("%4i portalclusters\n", portalclusters); - Msg ("%4i numportals\n", g_numportals); - - if (g_numportals * 2 >= MAX_PORTALS) - { - Error("The map overflows the max portal count (%d of max %d)!\n", g_numportals, MAX_PORTALS / 2 ); - } - - // these counts should take advantage of 64 bit systems automatically - leafbytes = ((portalclusters+63)&~63)>>3; - leaflongs = leafbytes/sizeof(long); - - portalbytes = ((g_numportals*2+63)&~63)>>3; - portallongs = portalbytes/sizeof(long); - -// each file portal is split into two memory portals - portals = (portal_t*)malloc(2*g_numportals*sizeof(portal_t)); - memset (portals, 0, 2*g_numportals*sizeof(portal_t)); - - leafs = (leaf_t*)malloc(portalclusters*sizeof(leaf_t)); - memset (leafs, 0, portalclusters*sizeof(leaf_t)); - - originalvismapsize = portalclusters*leafbytes; - uncompressedvis = (byte*)malloc(originalvismapsize); - - vismap = vismap_p = dvisdata; - dvis->numclusters = portalclusters; - vismap_p = (byte *)&dvis->bitofs[portalclusters]; - - vismap_end = vismap + MAX_MAP_VISIBILITY; - - for (i=0, p=portals ; i MAX_POINTS_ON_WINDING) - Error ("LoadPortals: portal %i has too many points", i); - if ( (unsigned)leafnums[0] > portalclusters - || (unsigned)leafnums[1] > portalclusters) - Error ("LoadPortals: reading portal %i", i); - - w = p->winding = NewWinding (numpoints); - w->original = true; - w->numpoints = numpoints; - - for (j=0 ; jpoints[j][k] = v[k]; - } - fscanf (f, "\n"); - - // calc plane - PlaneFromWinding (w, &plane); - - // create forward portal - l = &leafs[leafnums[0]]; - l->portals.AddToTail(p); - - p->winding = w; - VectorSubtract (vec3_origin, plane.normal, p->plane.normal); - p->plane.dist = -plane.dist; - p->leaf = leafnums[1]; - SetPortalSphere (p); - p++; - - // create backwards portal - l = &leafs[leafnums[1]]; - l->portals.AddToTail(p); - - p->winding = NewWinding(w->numpoints); - p->winding->numpoints = w->numpoints; - for (j=0 ; jnumpoints ; j++) - { - VectorCopy (w->points[w->numpoints-1-j], p->winding->points[j]); - } - - p->plane = plane; - p->leaf = leafnums[0]; - SetPortalSphere (p); - p++; - - } - - fclose (f); -} - - -/* -================ -CalcPAS - -Calculate the PAS (Potentially Audible Set) -by ORing together all the PVS visible from a leaf -================ -*/ -void CalcPAS (void) -{ - int i, j, k, l, index; - int bitbyte; - long *dest, *src; - byte *scan; - int count; - byte uncompressed[MAX_MAP_LEAFS/8]; - byte compressed[MAX_MAP_LEAFS/8]; - - Msg ("Building PAS...\n"); - - count = 0; - for (i=0 ; i= portalclusters) - Error ("Bad bit in PVS"); // pad bits should be 0 - src = (long *)(uncompressedvis + index*leafbytes); - dest = (long *)uncompressed; - for (l=0 ; l vismap_end) - Error ("Vismap expansion overflow"); - - dvis->bitofs[i][DVIS_PAS] = (byte *)dest-vismap; - - memcpy (dest, compressed, j); - } - - Msg ("Average clusters audible: %i\n", count/portalclusters); -} - - - -static void GetBoundsForFace( int faceID, Vector &faceMin, Vector &faceMax ) -{ - ClearBounds( faceMin, faceMax ); - dface_t *pFace = &dfaces[faceID]; - int i; - for( i = pFace->firstedge; i < pFace->firstedge + pFace->numedges; i++ ) - { - int edgeID = dsurfedges[i]; - if( edgeID < 0 ) - { - edgeID = -edgeID; - } - dedge_t *pEdge = &dedges[edgeID]; - dvertex_t *pVert0 = &dvertexes[pEdge->v[0]]; - dvertex_t *pVert1 = &dvertexes[pEdge->v[1]]; - AddPointToBounds( pVert0->point, faceMin, faceMax ); - AddPointToBounds( pVert1->point, faceMin, faceMax ); - } -} - -// FIXME: should stick this in mathlib -static float GetMinDistanceBetweenBoundingBoxes( const Vector &min1, const Vector &max1, - const Vector &min2, const Vector &max2 ) -{ - if( IsBoxIntersectingBox( min1, max1, min2, max2 ) ) - { - return 0.0f; - } - - Vector axisDist; - int i; - for( i = 0; i < 3; i++ ) - { - if( min1[i] <= max2[i] && max1[i] >= min2[i] ) - { - // the intersection in this dimension. - axisDist[i] = 0.0f; - } - else - { - float dist1, dist2; - dist1 = min1[i] - max2[i]; - dist2 = min2[i] - max1[i]; - axisDist[i] = dist1 > dist2 ? dist1 : dist2; - Assert( axisDist[i] > 0.0f ); - } - } - - float mag = axisDist.Length(); - Assert( mag > 0.0f ); - return mag; -} - -static float CalcDistanceFromLeafToWater( int leafNum ) -{ - byte uncompressed[MAX_MAP_LEAFS/8]; - - int j, k; - - // If we know that this one doesn't see a water surface then don't bother doing anything. - if( ((dleafs[leafNum].contents & CONTENTS_TESTFOGVOLUME) == 0) && ( dleafs[leafNum].leafWaterDataID == -1 ) ) - return 65535; // FIXME: make a define for this. - - // First get the vis data.. - int cluster = dleafs[leafNum].cluster; - if (cluster < 0) - return 65535; // FIXME: make a define for this. - - DecompressVis( &dvisdata[dvis->bitofs[cluster][DVIS_PVS]], uncompressed ); - - float minDist = 65535.0f; // FIXME: make a define for this. - - Vector leafMin, leafMax; - - leafMin[0] = ( float )dleafs[leafNum].mins[0]; - leafMin[1] = ( float )dleafs[leafNum].mins[1]; - leafMin[2] = ( float )dleafs[leafNum].mins[2]; - leafMax[0] = ( float )dleafs[leafNum].maxs[0]; - leafMax[1] = ( float )dleafs[leafNum].maxs[1]; - leafMax[2] = ( float )dleafs[leafNum].maxs[2]; - -/* - CUtlVector temp; - - // build a convex solid out of the planes so that we can get at the triangles. - for( j = dleafs[i].firstleafbrush; j < dleafs[i].firstleafbrush + dleafs[i].numleafbrushes; j++ ) - { - dbrush_t *pBrush = &dbrushes[j]; - for( k = pBrush->firstside; k < pBrush->firstside + pBrush->numsides; k++ ) - { - dbrushside_t *pside = dbrushsides + k; - dplane_t *pplane = dplanes + pside->planenum; - AddListPlane( &temp, pplane->normal[0], pplane->normal[1], pplane->normal[2], pplane->dist ); - } - CPhysConvex *pConvex = physcollision->ConvexFromPlanes( (float *)temp.Base(), temp.Count(), VPHYSICS_MERGE ); - ConvertConvexToCollide( &pConvex, - temp.RemoveAll(); - } -*/ - - // Iterate over all potentially visible clusters from this leaf - for (j = 0; j < dvis->numclusters; ++j) - { - // Don't need to bother if this is the same as the current cluster - if (j == cluster) - continue; - - // If the cluster isn't in our current pvs, then get out of here. - if ( !CheckBit( uncompressed, j ) ) - continue; - - // Found a visible cluster, now iterate over all leaves - // inside that cluster - for (k = 0; k < g_ClusterLeaves[j].leafCount; ++k) - { - int nClusterLeaf = g_ClusterLeaves[j].leafs[k]; - - // Don't bother testing the ones that don't see a water boundary. - if( ((dleafs[nClusterLeaf].contents & CONTENTS_TESTFOGVOLUME) == 0) && ( dleafs[nClusterLeaf].leafWaterDataID == -1 ) ) - continue; - - // Find the minimum distance between each surface on the boundary of the leaf - // that we have the pvs for and each water surface in the leaf that we are testing. - int nFirstFaceID = dleafs[nClusterLeaf].firstleafface; - for( int leafFaceID = 0; leafFaceID < dleafs[nClusterLeaf].numleaffaces; ++leafFaceID ) - { - int faceID = dleaffaces[nFirstFaceID + leafFaceID]; - dface_t *pFace = &dfaces[faceID]; - if( pFace->texinfo == -1 ) - continue; - - texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; - if( pTexInfo->flags & SURF_WARP ) - { - // Woo hoo!!! We found a water face. - // compare the bounding box of the face with the bounding - // box of the leaf that we are looking from and see - // what the closest distance is. - // FIXME: this could be a face/face distance between the water - // face and the bounding volume of the leaf. - - // Get the bounding box of the face - Vector faceMin, faceMax; - GetBoundsForFace( faceID, faceMin, faceMax ); - float dist = GetMinDistanceBetweenBoundingBoxes( leafMin, leafMax, faceMin, faceMax ); - if( dist < minDist ) - { - minDist = dist; - } - } - } - } - } - return minDist; -} - -static void CalcDistanceFromLeavesToWater( void ) -{ - int i; - for( i = 0; i < numleafs; i++ ) - { - g_LeafMinDistToWater[i] = ( unsigned short )CalcDistanceFromLeafToWater( i ); - } -} - -//----------------------------------------------------------------------------- -// Using the PVS, compute the visible fog volumes from each leaf -//----------------------------------------------------------------------------- -static void CalcVisibleFogVolumes() -{ - byte uncompressed[MAX_MAP_LEAFS/8]; - - int i, j, k; - - // Clear the contents flags for water testing - for (i = 0; i < numleafs; ++i) - { - dleafs[i].contents &= ~CONTENTS_TESTFOGVOLUME; - g_LeafMinDistToWater[i] = 65535; - } - - for (i = 0; i < numleafs; ++i) - { - // If we've already discovered that this leaf needs testing, - // no need to go through the work again... - if (dleafs[i].contents & CONTENTS_TESTFOGVOLUME) - { - Assert((dleafs[i].contents & (CONTENTS_SLIME | CONTENTS_WATER)) == 0); - continue; - } - - // Don't bother checking fog volumes from solid leaves - if (dleafs[i].contents & CONTENTS_SOLID) - continue; - - // Look only for leaves which are visible from leaves that have fluid in them. - if ( dleafs[i].leafWaterDataID == -1 ) - continue; - - // Don't bother about looking from CONTENTS_SLIME; we're not going to treat that as interesting. - // because slime is opaque - if ( dleafs[i].contents & CONTENTS_SLIME ) - continue; - - // First get the vis data.. - int cluster = dleafs[i].cluster; - if (cluster < 0) - continue; - - DecompressVis( &dvisdata[dvis->bitofs[cluster][DVIS_PVS]], uncompressed ); - - // Iterate over all potentially visible clusters from this leaf - for (j = 0; j < dvis->numclusters; ++j) - { - // Don't need to bother if this is the same as the current cluster - if (j == cluster) - continue; - - if ( !CheckBit( uncompressed, j ) ) - continue; - - // Found a visible cluster, now iterate over all leaves - // inside that cluster - for (k = 0; k < g_ClusterLeaves[j].leafCount; ++k) - { - int nClusterLeaf = g_ClusterLeaves[j].leafs[k]; - - // Don't bother checking fog volumes from solid leaves - if ( dleafs[nClusterLeaf].contents & CONTENTS_SOLID ) - continue; - - // Don't bother checking from any leaf that's got fluid in it - if ( dleafs[nClusterLeaf].leafWaterDataID != -1 ) - continue; - - // Here, we've found a case where a non-liquid leaf is visible from a liquid leaf - // So, in this case, we have to do the expensive test during rendering. - dleafs[nClusterLeaf].contents |= CONTENTS_TESTFOGVOLUME; - } - } - } -} - - -//----------------------------------------------------------------------------- -// Compute the bounding box, excluding 3D skybox + skybox, add it to keyvalues -//----------------------------------------------------------------------------- -float DetermineVisRadius( ) -{ - float flRadius = -1; - - // Check the max vis range to determine the vis radius - for (int i = 0; i < num_entities; ++i) - { - char* pEntity = ValueForKey(&entities[i], "classname"); - if (!stricmp(pEntity, "env_fog_controller")) - { - flRadius = FloatForKey (&entities[i], "farz"); - if (flRadius == 0.0f) - flRadius = -1.0f; - break; - } - } - - return flRadius; -} - -void MarkLeavesAsRadial() -{ - for ( int i = 0; i < numleafs; i++ ) - { - dleafs[i].flags |= LEAF_FLAGS_RADIAL; - } -} - - -int ParseCommandLine( int argc, char **argv ) -{ - int i; - for (i=1 ; i : Override the VPROJECT environment variable.\n" - " -game : Same as -vproject.\n" - "\n" - "Other options:\n" - " -novconfig : Don't bring up graphical UI on vproject errors.\n" - " -radius_override: Force a vis radius, regardless of whether an\n" - " -mpi_pw : Use a password to choose a specific set of VMPI workers.\n" - " -threads : Control the number of threads vbsp uses (defaults to the #\n" - " or processors on your machine).\n" - " -nosort : Don't sort portals (sorting is an optimization).\n" - " -tmpin : Make portals come from \\tmp\\.\n" - " -tmpout : Make portals come from \\tmp\\.\n" - " -trace : Writes a linefile that traces the vis from one cluster to another for debugging map vis.\n" - " -FullMinidumps : Write large minidumps on crash.\n" - " -x360 : Generate Xbox360 version of vsp\n" - " -nox360 : Disable generation Xbox360 version of vsp (default)\n" - "\n" -#if 1 // Disabled for the initial SDK release with VMPI so we can get feedback from selected users. - ); -#else - " -mpi_ListParams : Show a list of VMPI parameters.\n" - "\n" - ); - - // Show VMPI parameters? - for ( int i=1; i < argc; i++ ) - { - if ( V_stricmp( argv[i], "-mpi_ListParams" ) == 0 ) - { - Warning( "VMPI-specific options:\n\n" ); - - bool bIsSDKMode = VMPI_IsSDKMode(); - for ( int i=k_eVMPICmdLineParam_FirstParam+1; i < k_eVMPICmdLineParam_LastParam; i++ ) - { - if ( (VMPI_GetParamFlags( (EVMPICmdLineParam)i ) & VMPI_PARAM_SDK_HIDDEN) && bIsSDKMode ) - continue; - - Warning( "[%s]\n", VMPI_GetParamString( (EVMPICmdLineParam)i ) ); - Warning( VMPI_GetParamHelpString( (EVMPICmdLineParam)i ) ); - Warning( "\n\n" ); - } - break; - } - } -#endif -} - - -int RunVVis( int argc, char **argv ) -{ - char portalfile[1024]; - char source[1024]; - double start, end; - - - Msg( "Valve Software - vvis.exe (%s)\n", __DATE__ ); - - verbose = false; - - Q_StripExtension( argv[ argc - 1 ], source, sizeof( source ) ); - CmdLib_InitFileSystem( argv[ argc - 1 ] ); - - Q_FileBase( source, source, sizeof( source ) ); - - LoadCmdLineFromFile( argc, argv, source, "vvis" ); - int i = ParseCommandLine( argc, argv ); - - // This part is just for VMPI. VMPI's file system needs the basedir in front of all filenames, - // so we prepend qdir here. - strcpy( source, ExpandPath( source ) ); - - if (i != argc - 1) - { - PrintUsage( argc, argv ); - DeleteCmdLine( argc, argv ); - CmdLib_Exit( 1 ); - } - - start = Plat_FloatTime(); - - - if (!g_bUseMPI) - { - // Setup the logfile. - char logFile[512]; - _snprintf( logFile, sizeof(logFile), "%s.log", source ); - SetSpewFunctionLogFile( logFile ); - } - - // Run in the background? - if( g_bLowPriority ) - { - SetLowPriority(); - } - - ThreadSetDefault (); - - char targetPath[1024]; - GetPlatformMapPath( source, targetPath, 0, 1024 ); - Msg ("reading %s\n", targetPath); - LoadBSPFile (targetPath); - if (numnodes == 0 || numfaces == 0) - Error ("Empty map"); - ParseEntities (); - - // Check the VMF for a vis radius - if (!g_bUseRadius) - { - float flRadius = DetermineVisRadius( ); - if (flRadius > 0.0f) - { - g_bUseRadius = true; - g_VisRadius = flRadius * flRadius; - } - } - - if ( g_bUseRadius ) - { - MarkLeavesAsRadial(); - } - - if ( inbase[0] == 0 ) - { - strcpy( portalfile, source ); - } - else - { - sprintf ( portalfile, "%s%s", inbase, argv[i] ); - Q_StripExtension( portalfile, portalfile, sizeof( portalfile ) ); - } - strcat (portalfile, ".prt"); - - Msg ("reading %s\n", portalfile); - LoadPortals (portalfile); - - // don't write out results when simply doing a trace - if ( g_TraceClusterStart < 0 ) - { - CalcVis (); - CalcPAS (); - - // We need a mapping from cluster to leaves, since the PVS - // deals with clusters for both CalcVisibleFogVolumes and - BuildClusterTable(); - - CalcVisibleFogVolumes(); - CalcDistanceFromLeavesToWater(); - - visdatasize = vismap_p - dvisdata; - Msg ("visdatasize:%i compressed from %i\n", visdatasize, originalvismapsize*2); - - Msg ("writing %s\n", targetPath); - WriteBSPFile (targetPath); - } - else - { - if ( g_TraceClusterStart < 0 || g_TraceClusterStart >= portalclusters || g_TraceClusterStop < 0 || g_TraceClusterStop >= portalclusters ) - { - Error("Invalid cluster trace: %d to %d, valid range is 0 to %d\n", g_TraceClusterStart, g_TraceClusterStop, portalclusters-1 ); - } - if ( g_bUseMPI ) - { - Warning("Can't compile trace in MPI mode\n"); - } - CalcVisTrace (); - WritePortalTrace(source); - } - - end = Plat_FloatTime(); - - char str[512]; - GetHourMinuteSecondsString( (int)( end - start ), str, sizeof( str ) ); - Msg( "%s elapsed\n", str ); - - ReleasePakFileLumps(); - DeleteCmdLine( argc, argv ); - CmdLib_Cleanup(); - return 0; -} - - -/* -=========== -main -=========== -*/ -int main (int argc, char **argv) -{ - CommandLine()->CreateCmdLine( argc, argv ); - - MathLib_Init( 2.2f, 2.2f, 0.0f, 1.0f, false, false, false, false ); - InstallAllocationFunctions(); - InstallSpewFunction(); - - VVIS_SetupMPI( argc, argv ); - - // Install an exception handler. - if ( g_bUseMPI && !g_bMPIMaster ) - SetupToolsMinidumpHandler( VMPI_ExceptionFilter ); - else - SetupDefaultToolsMinidumpHandler(); - - return RunVVis( argc, argv ); -} - - -// When VVIS is used as a DLL (makes debugging vmpi vvis a lot easier), this is used to -// get it going. -class CVVisDLL : public ILaunchableDLL -{ -public: - virtual int main( int argc, char **argv ) - { - return ::main( argc, argv ); - } -}; - -EXPOSE_SINGLE_INTERFACE( CVVisDLL, ILaunchableDLL, LAUNCHABLE_DLL_INTERFACE_VERSION ); +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vis.c + +#include +#include "vis.h" +#include "threads.h" +#include "stdlib.h" +#include "pacifier.h" +#include "vmpi.h" +#include "mpivis.h" +#include "tier1/strtools.h" +#include "collisionutils.h" +#include "tier0/icommandline.h" +#include "vmpi_tools_shared.h" +#include "ilaunchabledll.h" +#include "tools_minidump.h" +#include "loadcmdline.h" +#include "byteswap.h" + + +int g_numportals; +int portalclusters; + +char inbase[32]; + +portal_t *portals; +leaf_t *leafs; + +int c_portaltest, c_portalpass, c_portalcheck; + +byte *uncompressedvis; + +byte *vismap, *vismap_p, *vismap_end; // past visfile +int originalvismapsize; + +int leafbytes; // (portalclusters+63)>>3 +int leaflongs; + +int portalbytes, portallongs; + +bool fastvis; +bool nosort; + +int totalvis; + +portal_t *sorted_portals[MAX_MAP_PORTALS*2]; + +bool g_bUseRadius = false; +double g_VisRadius = 4096.0f * 4096.0f; + +bool g_bLowPriority = false; + +//============================================================================= + +void PlaneFromWinding (winding_t *w, plane_t *plane) +{ + Vector v1, v2; + +// calc plane + VectorSubtract (w->points[2], w->points[1], v1); + VectorSubtract (w->points[0], w->points[1], v2); + CrossProduct (v2, v1, plane->normal); + VectorNormalize (plane->normal); + plane->dist = DotProduct (w->points[0], plane->normal); +} + + +/* +================== +NewWinding +================== +*/ +winding_t *NewWinding (int points) +{ + winding_t *w; + int size; + + if (points > MAX_POINTS_ON_WINDING) + Error ("NewWinding: %i points, max %d", points, MAX_POINTS_ON_WINDING); + + size = (int)(&((winding_t *)0)->points[points]); + w = (winding_t*)malloc (size); + memset (w, 0, size); + + return w; +} + +void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + Msg ("(%5.1f, %5.1f, %5.1f)\n",w->points[i][0], w->points[i][1],w->points[i][2]); +} + +void prl(leaf_t *l) +{ + int i; + portal_t *p; + plane_t pl; + + int count = l->portals.Count(); + for (i=0 ; iportals[i]; + pl = p->plane; + Msg ("portal %4i to leaf %4i : %7.1f : (%4.1f, %4.1f, %4.1f)\n",(int)(p-portals),p->leaf,pl.dist, pl.normal[0], pl.normal[1], pl.normal[2]); + } +} + + +//============================================================================= + +/* +============= +SortPortals + +Sorts the portals from the least complex, so the later ones can reuse +the earlier information. +============= +*/ +int PComp (const void *a, const void *b) +{ + if ( (*(portal_t **)a)->nummightsee == (*(portal_t **)b)->nummightsee) + return 0; + if ( (*(portal_t **)a)->nummightsee < (*(portal_t **)b)->nummightsee) + return -1; + + return 1; +} + +void BuildTracePortals( int clusterStart ) +{ + leaf_t *leaf = &leafs[g_TraceClusterStart]; + g_numportals = leaf->portals.Count(); + for ( int i = 0; i < g_numportals; i++ ) + { + sorted_portals[i] = leaf->portals[i]; + } +} + +void SortPortals (void) +{ + int i; + + for (i=0 ; ileaf ); + } + } + + c_leafs = CountBits (leafbits, portalclusters); + + return c_leafs; +} + + +/* +=============== +ClusterMerge + +Merges the portal visibility for a leaf +=============== +*/ +void ClusterMerge (int clusternum) +{ + leaf_t *leaf; +// byte portalvector[MAX_PORTALS/8]; + byte portalvector[MAX_PORTALS/4]; // 4 because portal bytes is * 2 + byte uncompressed[MAX_MAP_LEAFS/8]; + int i, j; + int numvis; + portal_t *p; + int pnum; + + // OR together all the portalvis bits + + memset (portalvector, 0, portalbytes); + leaf = &leafs[clusternum]; + for (i=0 ; i < leaf->portals.Count(); i++) + { + p = leaf->portals[i]; + if (p->status != stat_done) + Error ("portal not done %d %p %p\n", i, p, portals); + for (j=0 ; jportalvis)[j]; + pnum = p - portals; + SetBit( portalvector, pnum ); + } + + // convert portal bits to leaf bits + numvis = LeafVectorFromPortalVector (portalvector, uncompressed); + +#if 0 + // func_viscluster makes this happen all the time because it allows a non-convex set of portals + // My analysis says this is ok, but it does make this check for errors in vis kind of useless + if ( CheckBit( uncompressed, clusternum ) ) + Warning("WARNING: Cluster portals saw into cluster\n"); +#endif + + SetBit( uncompressed, clusternum ); + numvis++; // count the leaf itself + + // save uncompressed for PHS calculation + memcpy (uncompressedvis + clusternum*leafbytes, uncompressed, leafbytes); + + qprintf ("cluster %4i : %4i visible\n", clusternum, numvis); + totalvis += numvis; +} + +static int CompressAndCrosscheckClusterVis( int clusternum ) +{ + int optimized = 0; + byte compressed[MAX_MAP_LEAFS/8]; +// +// compress the bit string +// + byte *uncompressed = uncompressedvis + clusternum*leafbytes; + for ( int i = 0; i < portalclusters; i++ ) + { + if ( i == clusternum ) + continue; + + if ( CheckBit( uncompressed, i ) ) + { + byte *other = uncompressedvis + i*leafbytes; + if ( !CheckBit( other, clusternum ) ) + { + ClearBit( uncompressed, i ); + optimized++; + } + } + } + int numbytes = CompressVis( uncompressed, compressed ); + + byte *dest = vismap_p; + vismap_p += numbytes; + + if (vismap_p > vismap_end) + Error ("Vismap expansion overflow"); + + dvis->bitofs[clusternum][DVIS_PVS] = dest-vismap; + + memcpy( dest, compressed, numbytes ); + + // check vis data + DecompressVis( vismap + dvis->bitofs[clusternum][DVIS_PVS], compressed ); + + return optimized; +} + + +/* +================== +CalcPortalVis +================== +*/ +void CalcPortalVis (void) +{ + int i; + + // fastvis just uses mightsee for a very loose bound + if( fastvis ) + { + for (i=0 ; iwinding; + VectorCopy (vec3_origin, total); + for (i=0 ; inumpoints ; i++) + { + VectorAdd (total, w->points[i], total); + } + + for (i=0 ; i<3 ; i++) + total[i] /= w->numpoints; + + bestr = 0; + for (i=0 ; inumpoints ; i++) + { + VectorSubtract (w->points[i], total, dist); + r = VectorLength (dist); + if (r > bestr) + bestr = r; + } + VectorCopy (total, p->origin); + p->radius = bestr; +} + +/* +============ +LoadPortals +============ +*/ +void LoadPortals (char *name) +{ + int i, j; + portal_t *p; + leaf_t *l; + char magic[80]; + int numpoints; + winding_t *w; + int leafnums[2]; + plane_t plane; + + FILE *f; + + // Open the portal file. + if ( g_bUseMPI ) + { + // If we're using MPI, copy off the file to a temporary first. This will download the file + // from the MPI master, then we get to use nice functions like fscanf on it. + char tempPath[MAX_PATH], tempFile[MAX_PATH]; + if ( GetTempPath( sizeof( tempPath ), tempPath ) == 0 ) + { + Error( "LoadPortals: GetTempPath failed.\n" ); + } + + if ( GetTempFileName( tempPath, "vvis_portal_", 0, tempFile ) == 0 ) + { + Error( "LoadPortals: GetTempFileName failed.\n" ); + } + + // Read all the data from the network file into memory. + FileHandle_t hFile = g_pFileSystem->Open(name, "r"); + if ( hFile == FILESYSTEM_INVALID_HANDLE ) + Error( "LoadPortals( %s ): couldn't get file from master.\n", name ); + + CUtlVector data; + data.SetSize( g_pFileSystem->Size( hFile ) ); + g_pFileSystem->Read( data.Base(), data.Count(), hFile ); + g_pFileSystem->Close( hFile ); + + // Dump it into a temp file. + f = fopen( tempFile, "wt" ); + fwrite( data.Base(), 1, data.Count(), f ); + fclose( f ); + + // Open the temp file up. + f = fopen( tempFile, "rSTD" ); // read only, sequential, temporary, delete on close + } + else + { + f = fopen( name, "r" ); + } + + if ( !f ) + Error ("LoadPortals: couldn't read %s\n",name); + + if (fscanf (f,"%79s\n%i\n%i\n",magic, &portalclusters, &g_numportals) != 3) + Error ("LoadPortals %s: failed to read header", name); + if (stricmp(magic,PORTALFILE)) + Error ("LoadPortals %s: not a portal file", name); + + Msg ("%4i portalclusters\n", portalclusters); + Msg ("%4i numportals\n", g_numportals); + + if (g_numportals * 2 >= MAX_PORTALS) + { + Error("The map overflows the max portal count (%d of max %d)!\n", g_numportals, MAX_PORTALS / 2 ); + } + + // these counts should take advantage of 64 bit systems automatically + leafbytes = ((portalclusters+63)&~63)>>3; + leaflongs = leafbytes/sizeof(long); + + portalbytes = ((g_numportals*2+63)&~63)>>3; + portallongs = portalbytes/sizeof(long); + +// each file portal is split into two memory portals + portals = (portal_t*)malloc(2*g_numportals*sizeof(portal_t)); + memset (portals, 0, 2*g_numportals*sizeof(portal_t)); + + leafs = (leaf_t*)malloc(portalclusters*sizeof(leaf_t)); + memset (leafs, 0, portalclusters*sizeof(leaf_t)); + + originalvismapsize = portalclusters*leafbytes; + uncompressedvis = (byte*)malloc(originalvismapsize); + + vismap = vismap_p = dvisdata; + dvis->numclusters = portalclusters; + vismap_p = (byte *)&dvis->bitofs[portalclusters]; + + vismap_end = vismap + MAX_MAP_VISIBILITY; + + for (i=0, p=portals ; i MAX_POINTS_ON_WINDING) + Error ("LoadPortals: portal %i has too many points", i); + if ( (unsigned)leafnums[0] > portalclusters + || (unsigned)leafnums[1] > portalclusters) + Error ("LoadPortals: reading portal %i", i); + + w = p->winding = NewWinding (numpoints); + w->original = true; + w->numpoints = numpoints; + + for (j=0 ; jpoints[j][k] = v[k]; + } + fscanf (f, "\n"); + + // calc plane + PlaneFromWinding (w, &plane); + + // create forward portal + l = &leafs[leafnums[0]]; + l->portals.AddToTail(p); + + p->winding = w; + VectorSubtract (vec3_origin, plane.normal, p->plane.normal); + p->plane.dist = -plane.dist; + p->leaf = leafnums[1]; + SetPortalSphere (p); + p++; + + // create backwards portal + l = &leafs[leafnums[1]]; + l->portals.AddToTail(p); + + p->winding = NewWinding(w->numpoints); + p->winding->numpoints = w->numpoints; + for (j=0 ; jnumpoints ; j++) + { + VectorCopy (w->points[w->numpoints-1-j], p->winding->points[j]); + } + + p->plane = plane; + p->leaf = leafnums[0]; + SetPortalSphere (p); + p++; + + } + + fclose (f); +} + + +/* +================ +CalcPAS + +Calculate the PAS (Potentially Audible Set) +by ORing together all the PVS visible from a leaf +================ +*/ +void CalcPAS (void) +{ + int i, j, k, l, index; + int bitbyte; + long *dest, *src; + byte *scan; + int count; + byte uncompressed[MAX_MAP_LEAFS/8]; + byte compressed[MAX_MAP_LEAFS/8]; + + Msg ("Building PAS...\n"); + + count = 0; + for (i=0 ; i= portalclusters) + Error ("Bad bit in PVS"); // pad bits should be 0 + src = (long *)(uncompressedvis + index*leafbytes); + dest = (long *)uncompressed; + for (l=0 ; l vismap_end) + Error ("Vismap expansion overflow"); + + dvis->bitofs[i][DVIS_PAS] = (byte *)dest-vismap; + + memcpy (dest, compressed, j); + } + + Msg ("Average clusters audible: %i\n", count/portalclusters); +} + + + +static void GetBoundsForFace( int faceID, Vector &faceMin, Vector &faceMax ) +{ + ClearBounds( faceMin, faceMax ); + dface_t *pFace = &dfaces[faceID]; + int i; + for( i = pFace->firstedge; i < pFace->firstedge + pFace->numedges; i++ ) + { + int edgeID = dsurfedges[i]; + if( edgeID < 0 ) + { + edgeID = -edgeID; + } + dedge_t *pEdge = &dedges[edgeID]; + dvertex_t *pVert0 = &dvertexes[pEdge->v[0]]; + dvertex_t *pVert1 = &dvertexes[pEdge->v[1]]; + AddPointToBounds( pVert0->point, faceMin, faceMax ); + AddPointToBounds( pVert1->point, faceMin, faceMax ); + } +} + +// FIXME: should stick this in mathlib +static float GetMinDistanceBetweenBoundingBoxes( const Vector &min1, const Vector &max1, + const Vector &min2, const Vector &max2 ) +{ + if( IsBoxIntersectingBox( min1, max1, min2, max2 ) ) + { + return 0.0f; + } + + Vector axisDist; + int i; + for( i = 0; i < 3; i++ ) + { + if( min1[i] <= max2[i] && max1[i] >= min2[i] ) + { + // the intersection in this dimension. + axisDist[i] = 0.0f; + } + else + { + float dist1, dist2; + dist1 = min1[i] - max2[i]; + dist2 = min2[i] - max1[i]; + axisDist[i] = dist1 > dist2 ? dist1 : dist2; + Assert( axisDist[i] > 0.0f ); + } + } + + float mag = axisDist.Length(); + Assert( mag > 0.0f ); + return mag; +} + +static float CalcDistanceFromLeafToWater( int leafNum ) +{ + byte uncompressed[MAX_MAP_LEAFS/8]; + + int j, k; + + // If we know that this one doesn't see a water surface then don't bother doing anything. + if( ((dleafs[leafNum].contents & CONTENTS_TESTFOGVOLUME) == 0) && ( dleafs[leafNum].leafWaterDataID == -1 ) ) + return 65535; // FIXME: make a define for this. + + // First get the vis data.. + int cluster = dleafs[leafNum].cluster; + if (cluster < 0) + return 65535; // FIXME: make a define for this. + + DecompressVis( &dvisdata[dvis->bitofs[cluster][DVIS_PVS]], uncompressed ); + + float minDist = 65535.0f; // FIXME: make a define for this. + + Vector leafMin, leafMax; + + leafMin[0] = ( float )dleafs[leafNum].mins[0]; + leafMin[1] = ( float )dleafs[leafNum].mins[1]; + leafMin[2] = ( float )dleafs[leafNum].mins[2]; + leafMax[0] = ( float )dleafs[leafNum].maxs[0]; + leafMax[1] = ( float )dleafs[leafNum].maxs[1]; + leafMax[2] = ( float )dleafs[leafNum].maxs[2]; + +/* + CUtlVector temp; + + // build a convex solid out of the planes so that we can get at the triangles. + for( j = dleafs[i].firstleafbrush; j < dleafs[i].firstleafbrush + dleafs[i].numleafbrushes; j++ ) + { + dbrush_t *pBrush = &dbrushes[j]; + for( k = pBrush->firstside; k < pBrush->firstside + pBrush->numsides; k++ ) + { + dbrushside_t *pside = dbrushsides + k; + dplane_t *pplane = dplanes + pside->planenum; + AddListPlane( &temp, pplane->normal[0], pplane->normal[1], pplane->normal[2], pplane->dist ); + } + CPhysConvex *pConvex = physcollision->ConvexFromPlanes( (float *)temp.Base(), temp.Count(), VPHYSICS_MERGE ); + ConvertConvexToCollide( &pConvex, + temp.RemoveAll(); + } +*/ + + // Iterate over all potentially visible clusters from this leaf + for (j = 0; j < dvis->numclusters; ++j) + { + // Don't need to bother if this is the same as the current cluster + if (j == cluster) + continue; + + // If the cluster isn't in our current pvs, then get out of here. + if ( !CheckBit( uncompressed, j ) ) + continue; + + // Found a visible cluster, now iterate over all leaves + // inside that cluster + for (k = 0; k < g_ClusterLeaves[j].leafCount; ++k) + { + int nClusterLeaf = g_ClusterLeaves[j].leafs[k]; + + // Don't bother testing the ones that don't see a water boundary. + if( ((dleafs[nClusterLeaf].contents & CONTENTS_TESTFOGVOLUME) == 0) && ( dleafs[nClusterLeaf].leafWaterDataID == -1 ) ) + continue; + + // Find the minimum distance between each surface on the boundary of the leaf + // that we have the pvs for and each water surface in the leaf that we are testing. + int nFirstFaceID = dleafs[nClusterLeaf].firstleafface; + for( int leafFaceID = 0; leafFaceID < dleafs[nClusterLeaf].numleaffaces; ++leafFaceID ) + { + int faceID = dleaffaces[nFirstFaceID + leafFaceID]; + dface_t *pFace = &dfaces[faceID]; + if( pFace->texinfo == -1 ) + continue; + + texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; + if( pTexInfo->flags & SURF_WARP ) + { + // Woo hoo!!! We found a water face. + // compare the bounding box of the face with the bounding + // box of the leaf that we are looking from and see + // what the closest distance is. + // FIXME: this could be a face/face distance between the water + // face and the bounding volume of the leaf. + + // Get the bounding box of the face + Vector faceMin, faceMax; + GetBoundsForFace( faceID, faceMin, faceMax ); + float dist = GetMinDistanceBetweenBoundingBoxes( leafMin, leafMax, faceMin, faceMax ); + if( dist < minDist ) + { + minDist = dist; + } + } + } + } + } + return minDist; +} + +static void CalcDistanceFromLeavesToWater( void ) +{ + int i; + for( i = 0; i < numleafs; i++ ) + { + g_LeafMinDistToWater[i] = ( unsigned short )CalcDistanceFromLeafToWater( i ); + } +} + +//----------------------------------------------------------------------------- +// Using the PVS, compute the visible fog volumes from each leaf +//----------------------------------------------------------------------------- +static void CalcVisibleFogVolumes() +{ + byte uncompressed[MAX_MAP_LEAFS/8]; + + int i, j, k; + + // Clear the contents flags for water testing + for (i = 0; i < numleafs; ++i) + { + dleafs[i].contents &= ~CONTENTS_TESTFOGVOLUME; + g_LeafMinDistToWater[i] = 65535; + } + + for (i = 0; i < numleafs; ++i) + { + // If we've already discovered that this leaf needs testing, + // no need to go through the work again... + if (dleafs[i].contents & CONTENTS_TESTFOGVOLUME) + { + Assert((dleafs[i].contents & (CONTENTS_SLIME | CONTENTS_WATER)) == 0); + continue; + } + + // Don't bother checking fog volumes from solid leaves + if (dleafs[i].contents & CONTENTS_SOLID) + continue; + + // Look only for leaves which are visible from leaves that have fluid in them. + if ( dleafs[i].leafWaterDataID == -1 ) + continue; + + // Don't bother about looking from CONTENTS_SLIME; we're not going to treat that as interesting. + // because slime is opaque + if ( dleafs[i].contents & CONTENTS_SLIME ) + continue; + + // First get the vis data.. + int cluster = dleafs[i].cluster; + if (cluster < 0) + continue; + + DecompressVis( &dvisdata[dvis->bitofs[cluster][DVIS_PVS]], uncompressed ); + + // Iterate over all potentially visible clusters from this leaf + for (j = 0; j < dvis->numclusters; ++j) + { + // Don't need to bother if this is the same as the current cluster + if (j == cluster) + continue; + + if ( !CheckBit( uncompressed, j ) ) + continue; + + // Found a visible cluster, now iterate over all leaves + // inside that cluster + for (k = 0; k < g_ClusterLeaves[j].leafCount; ++k) + { + int nClusterLeaf = g_ClusterLeaves[j].leafs[k]; + + // Don't bother checking fog volumes from solid leaves + if ( dleafs[nClusterLeaf].contents & CONTENTS_SOLID ) + continue; + + // Don't bother checking from any leaf that's got fluid in it + if ( dleafs[nClusterLeaf].leafWaterDataID != -1 ) + continue; + + // Here, we've found a case where a non-liquid leaf is visible from a liquid leaf + // So, in this case, we have to do the expensive test during rendering. + dleafs[nClusterLeaf].contents |= CONTENTS_TESTFOGVOLUME; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Compute the bounding box, excluding 3D skybox + skybox, add it to keyvalues +//----------------------------------------------------------------------------- +float DetermineVisRadius( ) +{ + float flRadius = -1; + + // Check the max vis range to determine the vis radius + for (int i = 0; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!stricmp(pEntity, "env_fog_controller")) + { + flRadius = FloatForKey (&entities[i], "farz"); + if (flRadius == 0.0f) + flRadius = -1.0f; + break; + } + } + + return flRadius; +} + +void MarkLeavesAsRadial() +{ + for ( int i = 0; i < numleafs; i++ ) + { + dleafs[i].flags |= LEAF_FLAGS_RADIAL; + } +} + + +int ParseCommandLine( int argc, char **argv ) +{ + int i; + for (i=1 ; i : Override the VPROJECT environment variable.\n" + " -game : Same as -vproject.\n" + "\n" + "Other options:\n" + " -novconfig : Don't bring up graphical UI on vproject errors.\n" + " -radius_override: Force a vis radius, regardless of whether an\n" + " -mpi_pw : Use a password to choose a specific set of VMPI workers.\n" + " -threads : Control the number of threads vbsp uses (defaults to the #\n" + " or processors on your machine).\n" + " -nosort : Don't sort portals (sorting is an optimization).\n" + " -tmpin : Make portals come from \\tmp\\.\n" + " -tmpout : Make portals come from \\tmp\\.\n" + " -trace : Writes a linefile that traces the vis from one cluster to another for debugging map vis.\n" + " -FullMinidumps : Write large minidumps on crash.\n" + " -x360 : Generate Xbox360 version of vsp\n" + " -nox360 : Disable generation Xbox360 version of vsp (default)\n" + "\n" +#if 1 // Disabled for the initial SDK release with VMPI so we can get feedback from selected users. + ); +#else + " -mpi_ListParams : Show a list of VMPI parameters.\n" + "\n" + ); + + // Show VMPI parameters? + for ( int i=1; i < argc; i++ ) + { + if ( V_stricmp( argv[i], "-mpi_ListParams" ) == 0 ) + { + Warning( "VMPI-specific options:\n\n" ); + + bool bIsSDKMode = VMPI_IsSDKMode(); + for ( int i=k_eVMPICmdLineParam_FirstParam+1; i < k_eVMPICmdLineParam_LastParam; i++ ) + { + if ( (VMPI_GetParamFlags( (EVMPICmdLineParam)i ) & VMPI_PARAM_SDK_HIDDEN) && bIsSDKMode ) + continue; + + Warning( "[%s]\n", VMPI_GetParamString( (EVMPICmdLineParam)i ) ); + Warning( VMPI_GetParamHelpString( (EVMPICmdLineParam)i ) ); + Warning( "\n\n" ); + } + break; + } + } +#endif +} + + +int RunVVis( int argc, char **argv ) +{ + char portalfile[1024]; + char source[1024]; + double start, end; + + + Msg( "Valve Software - vvis.exe (%s)\n", __DATE__ ); + + verbose = false; + + Q_StripExtension( argv[ argc - 1 ], source, sizeof( source ) ); + CmdLib_InitFileSystem( argv[ argc - 1 ] ); + + Q_FileBase( source, source, sizeof( source ) ); + + LoadCmdLineFromFile( argc, argv, source, "vvis" ); + int i = ParseCommandLine( argc, argv ); + + // This part is just for VMPI. VMPI's file system needs the basedir in front of all filenames, + // so we prepend qdir here. + strcpy( source, ExpandPath( source ) ); + + if (i != argc - 1) + { + PrintUsage( argc, argv ); + DeleteCmdLine( argc, argv ); + CmdLib_Exit( 1 ); + } + + start = Plat_FloatTime(); + + + if (!g_bUseMPI) + { + // Setup the logfile. + char logFile[512]; + _snprintf( logFile, sizeof(logFile), "%s.log", source ); + SetSpewFunctionLogFile( logFile ); + } + + // Run in the background? + if( g_bLowPriority ) + { + SetLowPriority(); + } + + ThreadSetDefault (); + + char targetPath[1024]; + GetPlatformMapPath( source, targetPath, 0, 1024 ); + Msg ("reading %s\n", targetPath); + LoadBSPFile (targetPath); + if (numnodes == 0 || numfaces == 0) + Error ("Empty map"); + ParseEntities (); + + // Check the VMF for a vis radius + if (!g_bUseRadius) + { + float flRadius = DetermineVisRadius( ); + if (flRadius > 0.0f) + { + g_bUseRadius = true; + g_VisRadius = flRadius * flRadius; + } + } + + if ( g_bUseRadius ) + { + MarkLeavesAsRadial(); + } + + if ( inbase[0] == 0 ) + { + strcpy( portalfile, source ); + } + else + { + sprintf ( portalfile, "%s%s", inbase, argv[i] ); + Q_StripExtension( portalfile, portalfile, sizeof( portalfile ) ); + } + strcat (portalfile, ".prt"); + + Msg ("reading %s\n", portalfile); + LoadPortals (portalfile); + + // don't write out results when simply doing a trace + if ( g_TraceClusterStart < 0 ) + { + CalcVis (); + CalcPAS (); + + // We need a mapping from cluster to leaves, since the PVS + // deals with clusters for both CalcVisibleFogVolumes and + BuildClusterTable(); + + CalcVisibleFogVolumes(); + CalcDistanceFromLeavesToWater(); + + visdatasize = vismap_p - dvisdata; + Msg ("visdatasize:%i compressed from %i\n", visdatasize, originalvismapsize*2); + + Msg ("writing %s\n", targetPath); + WriteBSPFile (targetPath); + } + else + { + if ( g_TraceClusterStart < 0 || g_TraceClusterStart >= portalclusters || g_TraceClusterStop < 0 || g_TraceClusterStop >= portalclusters ) + { + Error("Invalid cluster trace: %d to %d, valid range is 0 to %d\n", g_TraceClusterStart, g_TraceClusterStop, portalclusters-1 ); + } + if ( g_bUseMPI ) + { + Warning("Can't compile trace in MPI mode\n"); + } + CalcVisTrace (); + WritePortalTrace(source); + } + + end = Plat_FloatTime(); + + char str[512]; + GetHourMinuteSecondsString( (int)( end - start ), str, sizeof( str ) ); + Msg( "%s elapsed\n", str ); + + ReleasePakFileLumps(); + DeleteCmdLine( argc, argv ); + CmdLib_Cleanup(); + return 0; +} + + +/* +=========== +main +=========== +*/ +int main (int argc, char **argv) +{ + CommandLine()->CreateCmdLine( argc, argv ); + + MathLib_Init( 2.2f, 2.2f, 0.0f, 1.0f, false, false, false, false ); + InstallAllocationFunctions(); + InstallSpewFunction(); + + VVIS_SetupMPI( argc, argv ); + + // Install an exception handler. + if ( g_bUseMPI && !g_bMPIMaster ) + SetupToolsMinidumpHandler( VMPI_ExceptionFilter ); + else + SetupDefaultToolsMinidumpHandler(); + + return RunVVis( argc, argv ); +} + + +// When VVIS is used as a DLL (makes debugging vmpi vvis a lot easier), this is used to +// get it going. +class CVVisDLL : public ILaunchableDLL +{ +public: + virtual int main( int argc, char **argv ) + { + return ::main( argc, argv ); + } +}; + +EXPOSE_SINGLE_INTERFACE( CVVisDLL, ILaunchableDLL, LAUNCHABLE_DLL_INTERFACE_VERSION ); diff --git a/mp/src/utils/vvis/vvis_dll.vpc b/mp/src/utils/vvis/vvis_dll.vpc index 34ac8fcb..43001443 100644 --- a/mp/src/utils/vvis/vvis_dll.vpc +++ b/mp/src/utils/vvis/vvis_dll.vpc @@ -1,101 +1,101 @@ -//----------------------------------------------------------------------------- -// VVIS_DLL.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" - -$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" - -$Configuration -{ - $Compiler - { - $AdditionalIncludeDirectories "$BASE,..\common,..\vmpi,..\vmpi\mysql\include" - $PreprocessorDefinitions "$BASE;MPI;PROTECTED_THINGS_DISABLE" - } - - $Linker - { - $AdditionalDependencies "$BASE odbc32.lib odbccp32.lib ws2_32.lib" - } -} - -$Project "Vvis_dll" -{ - $Folder "Source Files" - { - -$File "$SRCDIR\public\tier0\memoverride.cpp" - - $File "..\common\bsplib.cpp" - $File "..\common\cmdlib.cpp" - $File "$SRCDIR\public\collisionutils.cpp" - $File "$SRCDIR\public\filesystem_helpers.cpp" - $File "flow.cpp" - $File "$SRCDIR\public\loadcmdline.cpp" - $File "$SRCDIR\public\lumpfiles.cpp" - $File "..\common\mpi_stats.cpp" - $File "mpivis.cpp" - $File "..\common\MySqlDatabase.cpp" - $File "..\common\pacifier.cpp" - $File "$SRCDIR\public\scratchpad3d.cpp" - $File "..\common\scratchpad_helpers.cpp" - $File "..\common\scriplib.cpp" - $File "..\common\threads.cpp" - $File "..\common\tools_minidump.cpp" - $File "..\common\tools_minidump.h" - $File "..\common\vmpi_tools_shared.cpp" - $File "vvis.cpp" - $File "WaterDist.cpp" - $File "$SRCDIR\public\zip_utils.cpp" - } - - $Folder "Header Files" - { - $File "$SRCDIR\public\mathlib\amd3dx.h" - $File "$SRCDIR\public\tier0\basetypes.h" - $File "$SRCDIR\public\BSPFILE.H" - $File "$SRCDIR\public\bspflags.h" - $File "..\common\bsplib.h" - $File "$SRCDIR\public\BSPTreeData.h" - $File "$SRCDIR\public\mathlib\bumpvects.h" - $File "$SRCDIR\public\tier1\byteswap.h" - $File "$SRCDIR\public\tier1\checksum_crc.h" - $File "$SRCDIR\public\tier1\checksum_md5.h" - $File "..\common\cmdlib.h" - $File "$SRCDIR\public\cmodel.h" - $File "$SRCDIR\public\tier0\commonmacros.h" - $File "$SRCDIR\public\GameBSPFile.h" - $File "..\common\ISQLDBReplyTarget.h" - $File "$SRCDIR\public\mathlib\mathlib.h" - $File "mpivis.h" - $File "..\common\MySqlDatabase.h" - $File "..\common\pacifier.h" - $File "..\common\scriplib.h" - $File "$SRCDIR\public\tier1\strtools.h" - $File "..\common\threads.h" - $File "$SRCDIR\public\tier1\utlbuffer.h" - $File "$SRCDIR\public\tier1\utllinkedlist.h" - $File "$SRCDIR\public\tier1\utlmemory.h" - $File "$SRCDIR\public\tier1\utlrbtree.h" - $File "$SRCDIR\public\tier1\utlsymbol.h" - $File "$SRCDIR\public\tier1\utlvector.h" - $File "$SRCDIR\public\vcollide.h" - $File "$SRCDIR\public\mathlib\vector.h" - $File "$SRCDIR\public\mathlib\vector2d.h" - $File "vis.h" - $File "..\vmpi\vmpi_distribute_work.h" - $File "..\common\vmpi_tools_shared.h" - $File "$SRCDIR\public\vstdlib\vstdlib.h" - $File "$SRCDIR\public\wadtypes.h" - } - - $Folder "Link Libraries" - { - $Lib mathlib - $Lib tier2 - $Lib vmpi - } -} +//----------------------------------------------------------------------------- +// VVIS_DLL.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\common,..\vmpi,..\vmpi\mysql\include" + $PreprocessorDefinitions "$BASE;MPI;PROTECTED_THINGS_DISABLE" + } + + $Linker + { + $AdditionalDependencies "$BASE odbc32.lib odbccp32.lib ws2_32.lib" + } +} + +$Project "Vvis_dll" +{ + $Folder "Source Files" + { + -$File "$SRCDIR\public\tier0\memoverride.cpp" + + $File "..\common\bsplib.cpp" + $File "..\common\cmdlib.cpp" + $File "$SRCDIR\public\collisionutils.cpp" + $File "$SRCDIR\public\filesystem_helpers.cpp" + $File "flow.cpp" + $File "$SRCDIR\public\loadcmdline.cpp" + $File "$SRCDIR\public\lumpfiles.cpp" + $File "..\common\mpi_stats.cpp" + $File "mpivis.cpp" + $File "..\common\MySqlDatabase.cpp" + $File "..\common\pacifier.cpp" + $File "$SRCDIR\public\scratchpad3d.cpp" + $File "..\common\scratchpad_helpers.cpp" + $File "..\common\scriplib.cpp" + $File "..\common\threads.cpp" + $File "..\common\tools_minidump.cpp" + $File "..\common\tools_minidump.h" + $File "..\common\vmpi_tools_shared.cpp" + $File "vvis.cpp" + $File "WaterDist.cpp" + $File "$SRCDIR\public\zip_utils.cpp" + } + + $Folder "Header Files" + { + $File "$SRCDIR\public\mathlib\amd3dx.h" + $File "$SRCDIR\public\tier0\basetypes.h" + $File "$SRCDIR\public\BSPFILE.H" + $File "$SRCDIR\public\bspflags.h" + $File "..\common\bsplib.h" + $File "$SRCDIR\public\BSPTreeData.h" + $File "$SRCDIR\public\mathlib\bumpvects.h" + $File "$SRCDIR\public\tier1\byteswap.h" + $File "$SRCDIR\public\tier1\checksum_crc.h" + $File "$SRCDIR\public\tier1\checksum_md5.h" + $File "..\common\cmdlib.h" + $File "$SRCDIR\public\cmodel.h" + $File "$SRCDIR\public\tier0\commonmacros.h" + $File "$SRCDIR\public\GameBSPFile.h" + $File "..\common\ISQLDBReplyTarget.h" + $File "$SRCDIR\public\mathlib\mathlib.h" + $File "mpivis.h" + $File "..\common\MySqlDatabase.h" + $File "..\common\pacifier.h" + $File "..\common\scriplib.h" + $File "$SRCDIR\public\tier1\strtools.h" + $File "..\common\threads.h" + $File "$SRCDIR\public\tier1\utlbuffer.h" + $File "$SRCDIR\public\tier1\utllinkedlist.h" + $File "$SRCDIR\public\tier1\utlmemory.h" + $File "$SRCDIR\public\tier1\utlrbtree.h" + $File "$SRCDIR\public\tier1\utlsymbol.h" + $File "$SRCDIR\public\tier1\utlvector.h" + $File "$SRCDIR\public\vcollide.h" + $File "$SRCDIR\public\mathlib\vector.h" + $File "$SRCDIR\public\mathlib\vector2d.h" + $File "vis.h" + $File "..\vmpi\vmpi_distribute_work.h" + $File "..\common\vmpi_tools_shared.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + $File "$SRCDIR\public\wadtypes.h" + } + + $Folder "Link Libraries" + { + $Lib mathlib + $Lib tier2 + $Lib vmpi + } +} diff --git a/mp/src/utils/vvis_launcher/StdAfx.cpp b/mp/src/utils/vvis_launcher/StdAfx.cpp index 4165f58f..37ff27f1 100644 --- a/mp/src/utils/vvis_launcher/StdAfx.cpp +++ b/mp/src/utils/vvis_launcher/StdAfx.cpp @@ -1,15 +1,15 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// stdafx.cpp : source file that includes just the standard includes -// vvis_launcher.pch will be the pre-compiled header -// stdafx.obj will contain the pre-compiled type information - -#include "stdafx.h" - -// TODO: reference any additional headers you need in STDAFX.H -// and not in this file +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// vvis_launcher.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/mp/src/utils/vvis_launcher/StdAfx.h b/mp/src/utils/vvis_launcher/StdAfx.h index 8446846d..e67fc75b 100644 --- a/mp/src/utils/vvis_launcher/StdAfx.h +++ b/mp/src/utils/vvis_launcher/StdAfx.h @@ -1,31 +1,31 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// stdafx.h : include file for standard system include files, -// or project specific include files that are used frequently, but -// are changed infrequently -// - -#if !defined(AFX_STDAFX_H__29316173_1244_4B6A_B361_1ADB126E69F2__INCLUDED_) -#define AFX_STDAFX_H__29316173_1244_4B6A_B361_1ADB126E69F2__INCLUDED_ - -#if _MSC_VER > 1000 -#pragma once -#endif // _MSC_VER > 1000 - -#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers - -#include -#include -#include "interface.h" - -// TODO: reference additional headers your program requires here - -//{{AFX_INSERT_LOCATION}} -// Microsoft Visual C++ will insert additional declarations immediately before the previous line. - -#endif // !defined(AFX_STDAFX_H__29316173_1244_4B6A_B361_1ADB126E69F2__INCLUDED_) +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__29316173_1244_4B6A_B361_1ADB126E69F2__INCLUDED_) +#define AFX_STDAFX_H__29316173_1244_4B6A_B361_1ADB126E69F2__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include +#include +#include "interface.h" + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__29316173_1244_4B6A_B361_1ADB126E69F2__INCLUDED_) diff --git a/mp/src/utils/vvis_launcher/vvis_launcher.cpp b/mp/src/utils/vvis_launcher/vvis_launcher.cpp index 6d628040..edf03d25 100644 --- a/mp/src/utils/vvis_launcher/vvis_launcher.cpp +++ b/mp/src/utils/vvis_launcher/vvis_launcher.cpp @@ -1,79 +1,79 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// vvis_launcher.cpp : Defines the entry point for the console application. -// - -#include "stdafx.h" -#include -#include "tier1/strtools.h" -#include "tier0/icommandline.h" -#include "ilaunchabledll.h" - - - -char* GetLastErrorString() -{ - static char err[2048]; - - LPVOID lpMsgBuf; - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language - (LPTSTR) &lpMsgBuf, - 0, - NULL - ); - - strncpy( err, (char*)lpMsgBuf, sizeof( err ) ); - LocalFree( lpMsgBuf ); - - err[ sizeof( err ) - 1 ] = 0; - - return err; -} - - -int main(int argc, char* argv[]) -{ - CommandLine()->CreateCmdLine( argc, argv ); - const char *pDLLName = "vvis_dll.dll"; - - CSysModule *pModule = Sys_LoadModule( pDLLName ); - if ( !pModule ) - { - printf( "vvis launcher error: can't load %s\n%s", pDLLName, GetLastErrorString() ); - return 1; - } - - CreateInterfaceFn fn = Sys_GetFactory( pModule ); - if( !fn ) - { - printf( "vvis launcher error: can't get factory from %s\n", pDLLName ); - Sys_UnloadModule( pModule ); - return 2; - } - - int retCode = 0; - ILaunchableDLL *pDLL = (ILaunchableDLL*)fn( LAUNCHABLE_DLL_INTERFACE_VERSION, &retCode ); - if( !pDLL ) - { - printf( "vvis launcher error: can't get IVVisDLL interface from %s\n", pDLLName ); - Sys_UnloadModule( pModule ); - return 3; - } - - pDLL->main( argc, argv ); - Sys_UnloadModule( pModule ); - - return 0; -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vvis_launcher.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" +#include +#include "tier1/strtools.h" +#include "tier0/icommandline.h" +#include "ilaunchabledll.h" + + + +char* GetLastErrorString() +{ + static char err[2048]; + + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + + strncpy( err, (char*)lpMsgBuf, sizeof( err ) ); + LocalFree( lpMsgBuf ); + + err[ sizeof( err ) - 1 ] = 0; + + return err; +} + + +int main(int argc, char* argv[]) +{ + CommandLine()->CreateCmdLine( argc, argv ); + const char *pDLLName = "vvis_dll.dll"; + + CSysModule *pModule = Sys_LoadModule( pDLLName ); + if ( !pModule ) + { + printf( "vvis launcher error: can't load %s\n%s", pDLLName, GetLastErrorString() ); + return 1; + } + + CreateInterfaceFn fn = Sys_GetFactory( pModule ); + if( !fn ) + { + printf( "vvis launcher error: can't get factory from %s\n", pDLLName ); + Sys_UnloadModule( pModule ); + return 2; + } + + int retCode = 0; + ILaunchableDLL *pDLL = (ILaunchableDLL*)fn( LAUNCHABLE_DLL_INTERFACE_VERSION, &retCode ); + if( !pDLL ) + { + printf( "vvis launcher error: can't get IVVisDLL interface from %s\n", pDLLName ); + Sys_UnloadModule( pModule ); + return 3; + } + + pDLL->main( argc, argv ); + Sys_UnloadModule( pModule ); + + return 0; +} + diff --git a/mp/src/utils/vvis_launcher/vvis_launcher.vpc b/mp/src/utils/vvis_launcher/vvis_launcher.vpc index 5cf8eb99..f3c5e958 100644 --- a/mp/src/utils/vvis_launcher/vvis_launcher.vpc +++ b/mp/src/utils/vvis_launcher/vvis_launcher.vpc @@ -1,46 +1,46 @@ -//----------------------------------------------------------------------------- -// VVIS_LAUNCHER.VPC -// -// Project Script -//----------------------------------------------------------------------------- - -$Macro SRCDIR "..\.." -$Macro OUTBINDIR "$SRCDIR\..\game\bin" -$Macro OUTBINNAME "vvis" - -$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" - -$Configuration -{ - $Compiler - { - $AdditionalIncludeDirectories "$BASE,..\common" - $Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)" - $PrecompiledHeaderFile "Debug/vvis_launcher.pch" - } -} - -$Project "Vvis_launcher" -{ - $Folder "Source Files" - { - $File "vvis_launcher.cpp" - - $File "StdAfx.cpp" - { - $Configuration - { - $Compiler - { - $Create/UsePrecompiledHeader "Create Precompiled Header (/Yc)" - } - } - } - } - - $Folder "Header Files" - { - $File "$SRCDIR\public\tier1\interface.h" - $File "StdAfx.h" - } -} +//----------------------------------------------------------------------------- +// VVIS_LAUNCHER.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" +$Macro OUTBINNAME "vvis" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\common" + $Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)" + $PrecompiledHeaderFile "Debug/vvis_launcher.pch" + } +} + +$Project "Vvis_launcher" +{ + $Folder "Source Files" + { + $File "vvis_launcher.cpp" + + $File "StdAfx.cpp" + { + $Configuration + { + $Compiler + { + $Create/UsePrecompiledHeader "Create Precompiled Header (/Yc)" + } + } + } + } + + $Folder "Header Files" + { + $File "$SRCDIR\public\tier1\interface.h" + $File "StdAfx.h" + } +} -- cgit v1.2.3