diff --git a/neo/CMakeLists.txt b/neo/CMakeLists.txt index fe76c9be..e8662216 100644 --- a/neo/CMakeLists.txt +++ b/neo/CMakeLists.txt @@ -1049,9 +1049,9 @@ set(RBDOOM3_INCLUDES ${DOOMCLASSIC_INCLUDES} ${TIMIDITY_INCLUDES} - #${COMPILER_INCLUDES} - #${COMPILER_AAS_INCLUDES} ${COMPILER_AAS_SOURCES} - #${COMPILER_DMAP_INCLUDES} ${COMPILER_DMAP_SOURCES} + ${COMPILER_INCLUDES} + ${COMPILER_AAS_INCLUDES} ${COMPILER_AAS_SOURCES} + ${COMPILER_DMAP_INCLUDES} ${COMPILER_DMAP_SOURCES} #${COMPILER_RENDERBUMP_INCLUDES} ${COMPILER_RENDERBUMP_SOURCES} #${COMPILER_ROQVQ_INCLUDES} ${COMPILER_ROQVQ_SOURCES} ) @@ -1084,8 +1084,8 @@ set(RBDOOM3_SOURCES ${DOOMCLASSIC_SOURCES} ${TIMIDITY_SOURCES} - #${COMPILER_AAS_SOURCES} - #${COMPILER_DMAP_SOURCES} + ${COMPILER_AAS_SOURCES} + ${COMPILER_DMAP_SOURCES} #${COMPILER_RENDERBUMP_SOURCES} #${COMPILER_ROQVQ_SOURCES} ) diff --git a/neo/cm/CollisionModel_load.cpp b/neo/cm/CollisionModel_load.cpp index a0070ffd..cc15bb8d 100644 --- a/neo/cm/CollisionModel_load.cpp +++ b/neo/cm/CollisionModel_load.cpp @@ -4453,6 +4453,14 @@ cmHandle_t idCollisionModelManagerLocal::LoadModel( const char* modelName ) ID_TIME_T sourceTimeStamp = fileSystem->GetTimestamp( modelName ); + if( models == NULL ) + { + // raynorpat: best clear this if there are no models ( hit by dmap ) + maxModels = MAX_SUBMODELS; + numModels = 0; + models = ( cm_model_t** ) Mem_ClearedAlloc( ( maxModels + 1 ) * sizeof( cm_model_t* ), TAG_COLLISION ); + } + models[ numModels ] = LoadBinaryModel( generatedFileName, sourceTimeStamp ); if( models[ numModels ] != NULL ) { diff --git a/neo/framework/Common.cpp b/neo/framework/Common.cpp index b8d9e11c..ed7fefcb 100644 --- a/neo/framework/Common.cpp +++ b/neo/framework/Common.cpp @@ -1336,6 +1336,9 @@ void idCommonLocal::Init( int argc, const char* const* argv, const char* cmdline // startup the script debugger // DebuggerServerInit(); + // Init tool commands + InitCommands(); + // load the game dll LoadGameDLL(); @@ -1675,6 +1678,21 @@ void idCommonLocal::BusyWait() session->Pump(); } + +/* +=============== +idCommonLocal::InitCommands +=============== +*/ +void idCommonLocal::InitCommands( void ) +{ + // compilers + cmdSystem->AddCommand( "dmap", Dmap_f, CMD_FL_TOOL, "compiles a map", idCmdSystem::ArgCompletion_MapName ); + cmdSystem->AddCommand( "runAAS", RunAAS_f, CMD_FL_TOOL, "compiles an AAS file for a map", idCmdSystem::ArgCompletion_MapName ); + cmdSystem->AddCommand( "runAASDir", RunAASDir_f, CMD_FL_TOOL, "compiles AAS files for all maps in a folder", idCmdSystem::ArgCompletion_MapName ); + cmdSystem->AddCommand( "runReach", RunReach_f, CMD_FL_TOOL, "calculates reachability for an AAS file", idCmdSystem::ArgCompletion_MapName ); +} + /* =============== idCommonLocal::WaitForSessionState diff --git a/neo/framework/Common.h b/neo/framework/Common.h index 15f110a8..85fc83d9 100644 --- a/neo/framework/Common.h +++ b/neo/framework/Common.h @@ -115,18 +115,17 @@ ID_INLINE bool EndTraceRecording() typedef enum { EDITOR_NONE = 0, - EDITOR_RADIANT = BIT( 1 ), - EDITOR_GUI = BIT( 2 ), - EDITOR_DEBUGGER = BIT( 3 ), - EDITOR_SCRIPT = BIT( 4 ), - EDITOR_LIGHT = BIT( 5 ), - EDITOR_SOUND = BIT( 6 ), - EDITOR_DECL = BIT( 7 ), - EDITOR_AF = BIT( 8 ), - EDITOR_PARTICLE = BIT( 9 ), - EDITOR_PDA = BIT( 10 ), - EDITOR_AAS = BIT( 11 ), - EDITOR_MATERIAL = BIT( 12 ) + EDITOR_GUI = BIT( 1 ), + EDITOR_DEBUGGER = BIT( 2 ), + EDITOR_SCRIPT = BIT( 3 ), + EDITOR_LIGHT = BIT( 4 ), + EDITOR_SOUND = BIT( 5 ), + EDITOR_DECL = BIT( 6 ), + EDITOR_AF = BIT( 7 ), + EDITOR_PARTICLE = BIT( 8 ), + EDITOR_PDA = BIT( 9 ), + EDITOR_AAS = BIT( 10 ), + EDITOR_MATERIAL = BIT( 11 ) } toolFlag_t; #define STRTABLE_ID "#str_" diff --git a/neo/idlib/containers/VectorSet.h b/neo/idlib/containers/VectorSet.h index b2d97538..ba78bd72 100644 --- a/neo/idlib/containers/VectorSet.h +++ b/neo/idlib/containers/VectorSet.h @@ -94,7 +94,7 @@ ID_INLINE void idVectorSet::Init( const type& mins, const type& float boxSize; idList::AssureSize( initialSize ); - idList::SetNum( 0, false ); + idList::SetNum( 0 ); hash.Clear( idMath::IPow( boxHashSize, dimension ), initialSize ); diff --git a/neo/idlib/geometry/DrawVert.h b/neo/idlib/geometry/DrawVert.h index 1169d566..2c97f855 100644 --- a/neo/idlib/geometry/DrawVert.h +++ b/neo/idlib/geometry/DrawVert.h @@ -166,6 +166,8 @@ public: void SetTexCoordS( float s ); void SetTexCoordT( float t ); const idVec2 GetTexCoord() const; + const float GetTexCoordS() const; + const float GetTexCoordT() const; const halfFloat_t GetTexCoordNativeS() const; const halfFloat_t GetTexCoordNativeT() const; @@ -605,6 +607,26 @@ ID_INLINE const idVec2 idDrawVert::GetTexCoord() const return idVec2( F16toF32( st[0] ), F16toF32( st[1] ) ); } +/* +======================== +idDrawVert::GetTexCoordT +======================== +*/ +ID_INLINE const float idDrawVert::GetTexCoordS() const +{ + return F16toF32( st[0] ); +} + +/* +======================== +idDrawVert::GetTexCoordS +======================== +*/ +ID_INLINE const float idDrawVert::GetTexCoordT() const +{ + return F16toF32( st[1] ); +} + /* ======================== idDrawVert::GetTexCoordNativeS diff --git a/neo/idlib/geometry/Winding.cpp b/neo/idlib/geometry/Winding.cpp index 0a95197a..139e7a96 100644 --- a/neo/idlib/geometry/Winding.cpp +++ b/neo/idlib/geometry/Winding.cpp @@ -1398,6 +1398,69 @@ bool idWinding::InsertPointIfOnEdge( const idVec5& point, const idPlane& plane, return false; } +/* +============= +idWinding::InsertPointIfOnEdge +============= +*/ +bool idWinding::InsertPointIfOnEdge( const idVec3& point, const idPlane& plane, const float epsilon ) +{ + int i; + float dist, dot; + idVec3 normal; + + // point may not be too far from the winding plane + if( idMath::Fabs( plane.Distance( point ) ) > epsilon ) + { + return false; + } + + for( i = 0; i < numPoints; i++ ) + { + + // create plane through edge orthogonal to winding plane + normal = ( p[( i + 1 ) % numPoints].ToVec3() - p[i].ToVec3() ).Cross( plane.Normal() ); + normal.Normalize(); + dist = normal * p[i].ToVec3(); + + if( idMath::Fabs( normal * point - dist ) > epsilon ) + { + continue; + } + + normal = plane.Normal().Cross( normal ); + dot = normal * point; + + dist = dot - normal * p[i].ToVec3(); + + if( dist < epsilon ) + { + // if the winding already has the point + if( dist > -epsilon ) + { + return false; + } + continue; + } + + dist = dot - normal * p[( i + 1 ) % numPoints].ToVec3(); + + if( dist > -epsilon ) + { + // if the winding already has the point + if( dist < epsilon ) + { + return false; + } + continue; + } + + InsertPoint( idVec5( point.x, point.y, point.z, 0, 0 ), i + 1 ); + return true; + } + return false; +} + /* ============= idWinding::IsTiny diff --git a/neo/idlib/geometry/Winding.h b/neo/idlib/geometry/Winding.h index 0e84fa92..372390c8 100644 --- a/neo/idlib/geometry/Winding.h +++ b/neo/idlib/geometry/Winding.h @@ -87,6 +87,7 @@ public: void RemovePoint( int point ); void InsertPoint( const idVec5& point, int spot ); bool InsertPointIfOnEdge( const idVec5& point, const idPlane& plane, const float epsilon = ON_EPSILON ); + bool InsertPointIfOnEdge( const idVec3& point, const idPlane& plane, const float epsilon = ON_EPSILON ); // add a winding to the convex hull void AddToConvexHull( const idWinding* winding, const idVec3& normal, const float epsilon = ON_EPSILON ); // add a point to the convex hull diff --git a/neo/idlib/precompiled.h b/neo/idlib/precompiled.h index 92ef0c36..92186473 100644 --- a/neo/idlib/precompiled.h +++ b/neo/idlib/precompiled.h @@ -132,6 +132,9 @@ const int MAX_EXPRESSION_REGISTERS = 4096; #include "../sys/sys_session.h" #include "../sys/sys_achievements.h" +// tools +#include "../tools/compilers/compiler_public.h" + //----------------------------------------------------- #ifndef _D3SDK diff --git a/neo/idlib/sys/sys_alloc_tags.h b/neo/idlib/sys/sys_alloc_tags.h index 5b44384c..2ad906da 100644 --- a/neo/idlib/sys/sys_alloc_tags.h +++ b/neo/idlib/sys/sys_alloc_tags.h @@ -143,4 +143,5 @@ MEM_TAG( PHYSICS_CLIP_ENTITY ) MEM_TAG( PHYSICS_BRITTLE ) MEM_TAG( PHYSICS_AF ) MEM_TAG( RENDERPROG ) +MEM_TAG( TOOLS ) #undef MEM_TAG diff --git a/neo/sound/XAudio2/XA2_SoundHardware.cpp b/neo/sound/XAudio2/XA2_SoundHardware.cpp index 47612292..b70d1d7b 100644 --- a/neo/sound/XAudio2/XA2_SoundHardware.cpp +++ b/neo/sound/XAudio2/XA2_SoundHardware.cpp @@ -29,7 +29,9 @@ If you have questions concerning this license or the applicable additional terms #pragma hdrstop #include "precompiled.h" #include "../snd_local.h" +#if defined(USE_DOOMCLASSIC) #include "../../../doomclassic/doom/i_sound.h" +#endif idCVar s_showLevelMeter( "s_showLevelMeter", "0", CVAR_BOOL | CVAR_ARCHIVE, "Show VU meter" ); idCVar s_meterTopTime( "s_meterTopTime", "1000", CVAR_INTEGER | CVAR_ARCHIVE, "How long (in milliseconds) peaks are displayed on the VU meter" ); @@ -336,10 +338,12 @@ void idSoundHardware_XAudio2::Init() idSoundVoice::InitSurround( outputChannels, channelMask ); +#if defined(USE_DOOMCLASSIC) // --------------------- // Initialize the Doom classic sound system. // --------------------- I_InitSoundHardware( outputChannels, channelMask ); +#endif // --------------------- // Create VU Meter Effect @@ -424,10 +428,12 @@ void idSoundHardware_XAudio2::Shutdown() freeVoices.Clear(); zombieVoices.Clear(); +#if defined(USE_DOOMCLASSIC) // --------------------- // Shutdown the Doom classic sound system. // --------------------- I_ShutdownSoundHardware(); +#endif if( pXAudio2 != NULL ) { diff --git a/neo/sys/posix/platform_linux.cpp b/neo/sys/posix/platform_linux.cpp index 8342a00e..22ddc841 100644 --- a/neo/sys/posix/platform_linux.cpp +++ b/neo/sys/posix/platform_linux.cpp @@ -258,35 +258,6 @@ void Sys_CPUCount( int& numLogicalCPUCores, int& numPhysicalCPUCores, int& numCP } // RB end -/* -================ -Sys_GetSystemRam -returns in megabytes -================ -*/ -int Sys_GetSystemRam() -{ - int mb; - long count, page_size; - - count = sysconf( _SC_PHYS_PAGES ); - if( count == -1 ) - { - common->Printf( "GetSystemRam: sysconf _SC_PHYS_PAGES failed\n" ); - return 512; - } - page_size = sysconf( _SC_PAGE_SIZE ); - if( page_size == -1 ) - { - common->Printf( "GetSystemRam: sysconf _SC_PAGE_SIZE failed\n" ); - return 512; - } - mb = ( int )( ( double )count * ( double )page_size / ( 1024 * 1024 ) ); - // round to the nearest 16Mb - mb = ( mb + 8 ) & ~15; - return mb; -} - /* ================== Sys_DoStartProcess diff --git a/neo/sys/posix/platform_osx.cpp b/neo/sys/posix/platform_osx.cpp index dd484003..05d09dcd 100644 --- a/neo/sys/posix/platform_osx.cpp +++ b/neo/sys/posix/platform_osx.cpp @@ -166,32 +166,6 @@ void Sys_CPUCount( int& numLogicalCPUCores, int& numPhysicalCPUCores, int& numCP } // RB end -/* -================ -Sys_GetSystemRam -returns in megabytes -================ -*/ -int Sys_GetSystemRam() -{ - int mb, mib[2]; - - mib[0] = CTL_HW; - mib[1] = HW_MEMSIZE; - int64_t size = 0; - size_t len = sizeof( size ); - if( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 ) - { - mb = size / ( 1024 * 1024 ); - mb = ( mb + 8 ) & ~15; - return mb; - } - - common->Printf( "GetSystemRam: sysctl HW_MEMSIZE failed\n" ); - return 512; -} - - /* ================== Sys_DoStartProcess diff --git a/neo/sys/sys_public.h b/neo/sys/sys_public.h index 8fc66034..24cfc3e1 100644 --- a/neo/sys/sys_public.h +++ b/neo/sys/sys_public.h @@ -498,12 +498,6 @@ void Sys_FPU_SetFTZ( bool enable ); // sets Denormals-Are-Zero mode (only available when CPUID_DAZ is set) void Sys_FPU_SetDAZ( bool enable ); -// returns amount of system ram -int Sys_GetSystemRam(); - -// returns amount of video ram -int Sys_GetVideoRam(); - // returns amount of drive space in path int Sys_GetDriveFreeSpace( const char* path ); diff --git a/neo/sys/win32/win_main.cpp b/neo/sys/win32/win_main.cpp index 19ab553c..157d1e0f 100644 --- a/neo/sys/win32/win_main.cpp +++ b/neo/sys/win32/win_main.cpp @@ -1140,8 +1140,14 @@ void Sys_Init() { win32.sys_arch.SetString( "Win2K (NT)" ); } else if( win32.osversion.dwMajorVersion == 5 && win32.osversion.dwMinorVersion == 1 ) { win32.sys_arch.SetString( "WinXP (NT)" ); - } else if ( win32.osversion.dwMajorVersion == 6 ) { + } else if( win32.osversion.dwMajorVersion == 6 ) { win32.sys_arch.SetString( "Vista" ); + } else if( win32.osversion.dwMajorVersion == 6 && win32.osversion.dwMinorVersion == 1 ) { + win32.sys_arch.SetString( "Win7" ); + } else if( win32.osversion.dwMajorVersion == 6 && win32.osversion.dwMinorVersion == 2 ) { + win32.sys_arch.SetString( "Win8" ); + } else if( win32.osversion.dwMajorVersion == 6 && win32.osversion.dwMinorVersion == 3 ) { + win32.sys_arch.SetString( "Win8.1" ); } else { win32.sys_arch.SetString( "Unknown NT variant" ); } @@ -1249,8 +1255,6 @@ void Sys_Init() { } common->Printf( "%s\n", win32.sys_cpustring.GetString() ); - common->Printf( "%d MB System Memory\n", Sys_GetSystemRam() ); - common->Printf( "%d MB Video Memory\n", Sys_GetVideoRam() ); if ( ( win32.cpuid & CPUID_SSE2 ) == 0 ) { common->Error( "SSE2 not supported!" ); } diff --git a/neo/sys/win32/win_shared.cpp b/neo/sys/win32/win_shared.cpp index bae1b630..88a6d35d 100644 --- a/neo/sys/win32/win_shared.cpp +++ b/neo/sys/win32/win_shared.cpp @@ -92,25 +92,6 @@ uint64 Sys_Microseconds() return ( ( uint64 )( ( int64 )Sys_GetClockTicks() << 10 ) ) / ticksPerMicrosecondTimes1024; } -/* -================ -Sys_GetSystemRam - - returns amount of physical memory in MB -================ -*/ -int Sys_GetSystemRam() -{ - MEMORYSTATUSEX statex; - statex.dwLength = sizeof( statex ); - GlobalMemoryStatusEx( &statex ); - int physRam = statex.ullTotalPhys / ( 1024 * 1024 ); - // HACK: For some reason, ullTotalPhys is sometimes off by a meg or two, so we round up to the nearest 16 megs - physRam = ( physRam + 8 ) & ~15; - return physRam; -} - - /* ================ Sys_GetDriveFreeSpace @@ -150,74 +131,6 @@ int64 Sys_GetDriveFreeSpaceInBytes( const char* path ) return ret; } -/* -================ -Sys_GetVideoRam -returns in megabytes -================ -*/ -int Sys_GetVideoRam() -{ - unsigned int retSize = 64; - - // RB begin -#if !defined(__MINGW32__) - CComPtr spLoc = NULL; - HRESULT hr = CoCreateInstance( CLSID_WbemLocator, 0, CLSCTX_SERVER, IID_IWbemLocator, ( LPVOID* ) &spLoc ); - if( hr != S_OK || spLoc == NULL ) - { - return retSize; - } - - CComBSTR bstrNamespace( _T( "\\\\.\\root\\CIMV2" ) ); - CComPtr spServices; - - // Connect to CIM - hr = spLoc->ConnectServer( bstrNamespace, NULL, NULL, 0, NULL, 0, 0, &spServices ); - if( hr != WBEM_S_NO_ERROR ) - { - return retSize; - } - - // Switch the security level to IMPERSONATE so that provider will grant access to system-level objects. - hr = CoSetProxyBlanket( spServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE ); - if( hr != S_OK ) - { - return retSize; - } - - // Get the vid controller - CComPtr spEnumInst = NULL; - hr = spServices->CreateInstanceEnum( CComBSTR( "Win32_VideoController" ), WBEM_FLAG_SHALLOW, NULL, &spEnumInst ); - if( hr != WBEM_S_NO_ERROR || spEnumInst == NULL ) - { - return retSize; - } - - ULONG uNumOfInstances = 0; - CComPtr spInstance = NULL; - hr = spEnumInst->Next( 10000, 1, &spInstance, &uNumOfInstances ); - - if( hr == S_OK && spInstance ) - { - // Get properties from the object - CComVariant varSize; - hr = spInstance->Get( CComBSTR( _T( "AdapterRAM" ) ), 0, &varSize, 0, 0 ); - if( hr == S_OK ) - { - retSize = varSize.intVal / ( 1024 * 1024 ); - if( retSize == 0 ) - { - retSize = 64; - } - } - } -#endif - // RB end - - return retSize; -} - /* ================ Sys_GetCurrentMemoryStatus diff --git a/neo/tools/compilers/aas/AASBuild.cpp b/neo/tools/compilers/aas/AASBuild.cpp new file mode 100644 index 00000000..e3d7bbb8 --- /dev/null +++ b/neo/tools/compilers/aas/AASBuild.cpp @@ -0,0 +1,1172 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "AASBuild_local.h" + +#define BFL_PATCH 0x1000 + +//=============================================================== +// +// idAASBuild +// +//=============================================================== + +/* +============ +idAASBuild::idAASBuild +============ +*/ +idAASBuild::idAASBuild( void ) +{ + file = NULL; + procNodes = NULL; + numProcNodes = 0; + numGravitationalSubdivisions = 0; + numMergedLeafNodes = 0; + numLedgeSubdivisions = 0; + ledgeMap = NULL; +} + +/* +============ +idAASBuild::~idAASBuild +============ +*/ +idAASBuild::~idAASBuild( void ) +{ + Shutdown(); +} + +/* +================ +idAASBuild::Shutdown +================ +*/ +void idAASBuild::Shutdown( void ) +{ + aasSettings = NULL; + if( file ) + { + delete file; + file = NULL; + } + DeleteProcBSP(); + numGravitationalSubdivisions = 0; + numMergedLeafNodes = 0; + numLedgeSubdivisions = 0; + ledgeList.Clear(); + if( ledgeMap ) + { + delete ledgeMap; + ledgeMap = NULL; + } +} + +/* +================ +idAASBuild::ParseProcNodes +================ +*/ +void idAASBuild::ParseProcNodes( idLexer* src ) +{ + int i; + + src->ExpectTokenString( "{" ); + + idAASBuild::numProcNodes = src->ParseInt(); + if( idAASBuild::numProcNodes < 0 ) + { + src->Error( "idAASBuild::ParseProcNodes: bad numProcNodes" ); + } + idAASBuild::procNodes = ( aasProcNode_t* )Mem_ClearedAlloc( idAASBuild::numProcNodes * sizeof( aasProcNode_t ), TAG_TOOLS ); + + for( i = 0; i < idAASBuild::numProcNodes; i++ ) + { + aasProcNode_t* node; + + node = &( idAASBuild::procNodes[i] ); + + src->Parse1DMatrix( 4, node->plane.ToFloatPtr() ); + node->children[0] = src->ParseInt(); + node->children[1] = src->ParseInt(); + } + + src->ExpectTokenString( "}" ); +} + +/* +================ +idAASBuild::LoadProcBSP +================ +*/ +bool idAASBuild::LoadProcBSP( const char* name, ID_TIME_T minFileTime ) +{ + idStr fileName; + idToken token; + idLexer* src; + + // load it + fileName = name; + fileName.SetFileExtension( PROC_FILE_EXT ); + src = new idLexer( fileName, LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE ); + if( !src->IsLoaded() ) + { + common->Warning( "idAASBuild::LoadProcBSP: couldn't load %s", fileName.c_str() ); + delete src; + return false; + } + + // if the file is too old + if( src->GetFileTime() < minFileTime ) + { + delete src; + return false; + } + + if( !src->ReadToken( &token ) || token.Icmp( PROC_FILE_ID ) ) + { + common->Warning( "idAASBuild::LoadProcBSP: bad id '%s' instead of '%s'", token.c_str(), PROC_FILE_ID ); + delete src; + return false; + } + + // parse the file + while( 1 ) + { + if( !src->ReadToken( &token ) ) + { + break; + } + + if( token == "model" ) + { + src->SkipBracedSection(); + continue; + } + + if( token == "shadowModel" ) + { + src->SkipBracedSection(); + continue; + } + + if( token == "interAreaPortals" ) + { + src->SkipBracedSection(); + continue; + } + + if( token == "nodes" ) + { + idAASBuild::ParseProcNodes( src ); + break; + } + + src->Error( "idAASBuild::LoadProcBSP: bad token \"%s\"", token.c_str() ); + } + + delete src; + + return true; +} + +/* +============ +idAASBuild::DeleteProcBSP +============ +*/ +void idAASBuild::DeleteProcBSP( void ) +{ + if( procNodes ) + { + Mem_Free( procNodes ); + procNodes = NULL; + } + numProcNodes = 0; +} + +/* +============ +idAASBuild::ChoppedAwayByProcBSP +============ +*/ +bool idAASBuild::ChoppedAwayByProcBSP( int nodeNum, idFixedWinding* w, const idVec3& normal, const idVec3& origin, const float radius ) +{ + int res; + idFixedWinding back; + aasProcNode_t* node; + float dist; + + do + { + node = idAASBuild::procNodes + nodeNum; + dist = node->plane.Normal() * origin + node->plane[3]; + if( dist > radius ) + { + res = SIDE_FRONT; + } + else if( dist < -radius ) + { + res = SIDE_BACK; + } + else + { + res = w->Split( &back, node->plane, ON_EPSILON ); + } + if( res == SIDE_FRONT ) + { + nodeNum = node->children[0]; + } + else if( res == SIDE_BACK ) + { + nodeNum = node->children[1]; + } + else if( res == SIDE_ON ) + { + // continue with the side the winding faces + if( node->plane.Normal() * normal > 0.0f ) + { + nodeNum = node->children[0]; + } + else + { + nodeNum = node->children[1]; + } + } + else + { + // if either node is not solid + if( node->children[0] < 0 || node->children[1] < 0 ) + { + return false; + } + // only recurse if the node is not solid + if( node->children[1] > 0 ) + { + if( !idAASBuild::ChoppedAwayByProcBSP( node->children[1], &back, normal, origin, radius ) ) + { + return false; + } + } + nodeNum = node->children[0]; + } + } + while( nodeNum > 0 ); + if( nodeNum < 0 ) + { + return false; + } + return true; +} + +/* +============ +idAASBuild::ClipBrushSidesWithProcBSP +============ +*/ +void idAASBuild::ClipBrushSidesWithProcBSP( idBrushList& brushList ) +{ + int i, clippedSides; + idBrush* brush; + idFixedWinding neww; + idBounds bounds; + float radius; + idVec3 origin; + + // if the .proc file has no BSP tree + if( idAASBuild::procNodes == NULL ) + { + return; + } + + clippedSides = 0; + for( brush = brushList.Head(); brush; brush = brush->Next() ) + { + for( i = 0; i < brush->GetNumSides(); i++ ) + { + + if( !brush->GetSide( i )->GetWinding() ) + { + continue; + } + + // make a local copy of the winding + neww = *brush->GetSide( i )->GetWinding(); + neww.GetBounds( bounds ); + origin = ( bounds[1] - bounds[0] ) * 0.5f; + radius = origin.Length() + ON_EPSILON; + origin = bounds[0] + origin; + + if( ChoppedAwayByProcBSP( 0, &neww, brush->GetSide( i )->GetPlane().Normal(), origin, radius ) ) + { + brush->GetSide( i )->SetFlag( SFL_USED_SPLITTER ); + clippedSides++; + } + } + } + + common->Printf( "%6d brush sides clipped\n", clippedSides ); +} + +/* +============ +idAASBuild::ContentsForAAS +============ +*/ +int idAASBuild::ContentsForAAS( int contents ) +{ + int c; + + if( contents & ( CONTENTS_SOLID | CONTENTS_AAS_SOLID | CONTENTS_MONSTERCLIP ) ) + { + return AREACONTENTS_SOLID; + } + c = 0; + if( contents & CONTENTS_WATER ) + { + c |= AREACONTENTS_WATER; + } + if( contents & CONTENTS_AREAPORTAL ) + { + c |= AREACONTENTS_CLUSTERPORTAL; + } + if( contents & CONTENTS_AAS_OBSTACLE ) + { + c |= AREACONTENTS_OBSTACLE; + } + return c; +} + +/* +============ +idAASBuild::AddBrushForMapBrush +============ +*/ +idBrushList idAASBuild::AddBrushesForMapBrush( const idMapBrush* mapBrush, const idVec3& origin, const idMat3& axis, int entityNum, int primitiveNum, idBrushList brushList ) +{ + int contents, i; + idMapBrushSide* mapSide; + const idMaterial* mat; + idList sideList; + idBrush* brush; + idPlane plane; + + contents = 0; + for( i = 0; i < mapBrush->GetNumSides(); i++ ) + { + mapSide = mapBrush->GetSide( i ); + mat = declManager->FindMaterial( mapSide->GetMaterial() ); + contents |= mat->GetContentFlags(); + plane = mapSide->GetPlane(); + plane.FixDegeneracies( DEGENERATE_DIST_EPSILON ); + sideList.Append( new idBrushSide( plane, -1 ) ); + } + + contents = ContentsForAAS( contents ); + if( !contents ) + { + for( i = 0; i < sideList.Num(); i++ ) + { + delete sideList[i]; + } + return brushList; + } + + brush = new idBrush(); + brush->SetContents( contents ); + + if( !brush->FromSides( sideList ) ) + { + common->Warning( "brush primitive %d on entity %d is degenerate", primitiveNum, entityNum ); + delete brush; + return brushList; + } + + brush->SetEntityNum( entityNum ); + brush->SetPrimitiveNum( primitiveNum ); + brush->Transform( origin, axis ); + brushList.AddToTail( brush ); + + return brushList; +} + +/* +============ +idAASBuild::AddBrushesForPatch +============ +*/ +idBrushList idAASBuild::AddBrushesForMapPatch( const idMapPatch* mapPatch, const idVec3& origin, const idMat3& axis, int entityNum, int primitiveNum, idBrushList brushList ) +{ + int i, j, contents, validBrushes; + float dot; + int v1, v2, v3, v4; + idFixedWinding w; + idPlane plane; + idVec3 d1, d2; + idBrush* brush; + idSurface_Patch mesh; + const idMaterial* mat; + + mat = declManager->FindMaterial( mapPatch->GetMaterial() ); + contents = ContentsForAAS( mat->GetContentFlags() ); + + if( !contents ) + { + return brushList; + } + + mesh = idSurface_Patch( *mapPatch ); + + // if the patch has an explicit number of subdivisions use it to avoid cracks + if( mapPatch->GetExplicitlySubdivided() ) + { + mesh.SubdivideExplicit( mapPatch->GetHorzSubdivisions(), mapPatch->GetVertSubdivisions(), false, true ); + } + else + { + mesh.Subdivide( DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_LENGTH_CD, false ); + } + + validBrushes = 0; + + for( i = 0; i < mesh.GetWidth() - 1; i++ ) + { + for( j = 0; j < mesh.GetHeight() - 1; j++ ) + { + + v1 = j * mesh.GetWidth() + i; + v2 = v1 + 1; + v3 = v1 + mesh.GetWidth() + 1; + v4 = v1 + mesh.GetWidth(); + + d1 = mesh[v2].xyz - mesh[v1].xyz; + d2 = mesh[v3].xyz - mesh[v1].xyz; + plane.SetNormal( d1.Cross( d2 ) ); + if( plane.Normalize() != 0.0f ) + { + plane.FitThroughPoint( mesh[v1].xyz ); + dot = plane.Distance( mesh[v4].xyz ); + // if we can turn it into a quad + if( idMath::Fabs( dot ) < 0.1f ) + { + w.Clear(); + w += mesh[v1].xyz; + w += mesh[v2].xyz; + w += mesh[v3].xyz; + w += mesh[v4].xyz; + + brush = new idBrush(); + brush->SetContents( contents ); + if( brush->FromWinding( w, plane ) ) + { + brush->SetEntityNum( entityNum ); + brush->SetPrimitiveNum( primitiveNum ); + brush->SetFlag( BFL_PATCH ); + brush->Transform( origin, axis ); + brushList.AddToTail( brush ); + validBrushes++; + } + else + { + delete brush; + } + continue; + } + else + { + // create one of the triangles + w.Clear(); + w += mesh[v1].xyz; + w += mesh[v2].xyz; + w += mesh[v3].xyz; + + brush = new idBrush(); + brush->SetContents( contents ); + if( brush->FromWinding( w, plane ) ) + { + brush->SetEntityNum( entityNum ); + brush->SetPrimitiveNum( primitiveNum ); + brush->SetFlag( BFL_PATCH ); + brush->Transform( origin, axis ); + brushList.AddToTail( brush ); + validBrushes++; + } + else + { + delete brush; + } + } + } + // create the other triangle + d1 = mesh[v3].xyz - mesh[v1].xyz; + d2 = mesh[v4].xyz - mesh[v1].xyz; + plane.SetNormal( d1.Cross( d2 ) ); + if( plane.Normalize() != 0.0f ) + { + plane.FitThroughPoint( mesh[v1].xyz ); + + w.Clear(); + w += mesh[v1].xyz; + w += mesh[v3].xyz; + w += mesh[v4].xyz; + + brush = new idBrush(); + brush->SetContents( contents ); + if( brush->FromWinding( w, plane ) ) + { + brush->SetEntityNum( entityNum ); + brush->SetPrimitiveNum( primitiveNum ); + brush->SetFlag( BFL_PATCH ); + brush->Transform( origin, axis ); + brushList.AddToTail( brush ); + validBrushes++; + } + else + { + delete brush; + } + } + } + } + + if( !validBrushes ) + { + common->Warning( "patch primitive %d on entity %d is completely degenerate", primitiveNum, entityNum ); + } + + return brushList; +} + +/* +============ +idAASBuild::AddBrushesForMapEntity +============ +*/ +idBrushList idAASBuild::AddBrushesForMapEntity( const idMapEntity* mapEnt, int entityNum, idBrushList brushList ) +{ + int i; + idVec3 origin; + idMat3 axis; + + if( mapEnt->GetNumPrimitives() < 1 ) + { + return brushList; + } + + mapEnt->epairs.GetVector( "origin", "0 0 0", origin ); + if( !mapEnt->epairs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) + { + float angle = mapEnt->epairs.GetFloat( "angle" ); + if( angle != 0.0f ) + { + axis = idAngles( 0.0f, angle, 0.0f ).ToMat3(); + } + else + { + axis.Identity(); + } + } + + for( i = 0; i < mapEnt->GetNumPrimitives(); i++ ) + { + idMapPrimitive* mapPrim; + + mapPrim = mapEnt->GetPrimitive( i ); + if( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) + { + brushList = AddBrushesForMapBrush( static_cast( mapPrim ), origin, axis, entityNum, i, brushList ); + continue; + } + if( mapPrim->GetType() == idMapPrimitive::TYPE_PATCH ) + { + if( aasSettings->usePatches ) + { + brushList = AddBrushesForMapPatch( static_cast( mapPrim ), origin, axis, entityNum, i, brushList ); + } + continue; + } + } + + return brushList; +} + +/* +============ +idAASBuild::AddBrushesForMapFile +============ +*/ +idBrushList idAASBuild::AddBrushesForMapFile( const idMapFile* mapFile, idBrushList brushList ) +{ + int i; + + common->Printf( "[Brush Load]\n" ); + + brushList = AddBrushesForMapEntity( mapFile->GetEntity( 0 ), 0, brushList ); + + for( i = 1; i < mapFile->GetNumEntities(); i++ ) + { + const char* classname = mapFile->GetEntity( i )->epairs.GetString( "classname" ); + + if( idStr::Icmp( classname, "func_aas_obstacle" ) == 0 ) + { + brushList = AddBrushesForMapEntity( mapFile->GetEntity( i ), i, brushList ); + } + } + + common->Printf( "%6d brushes\n", brushList.Num() ); + + return brushList; +} + +/* +============ +idAASBuild::CheckForEntities +============ +*/ +bool idAASBuild::CheckForEntities( const idMapFile* mapFile, idStrList& entityClassNames ) const +{ + int i; + idStr classname; + + for( i = 0; i < mapFile->GetNumEntities(); i++ ) + { + if( !mapFile->GetEntity( i )->epairs.GetString( "classname", "", classname ) ) + { + continue; + } + + if( aasSettings->ValidEntity( classname ) ) + { + entityClassNames.AddUnique( classname ); + } + } + + return ( entityClassNames.Num() != 0 ); +} + +/* +============ +MergeAllowed +============ +*/ +bool MergeAllowed( idBrush* b1, idBrush* b2 ) +{ + return ( b1->GetContents() == b2->GetContents() && !( ( b1->GetFlags() | b2->GetFlags() ) & BFL_PATCH ) ); +} + +/* +============ +ExpandedChopAllowed +============ +*/ +bool ExpandedChopAllowed( idBrush* b1, idBrush* b2 ) +{ + return ( b1->GetContents() == b2->GetContents() ); +} + +/* +============ +ExpandedMergeAllowed +============ +*/ +bool ExpandedMergeAllowed( idBrush* b1, idBrush* b2 ) +{ + return ( b1->GetContents() == b2->GetContents() ); +} + +/* +============ +idAASBuild::ChangeMultipleBoundingBoxContents +============ +*/ +void idAASBuild::ChangeMultipleBoundingBoxContents_r( idBrushBSPNode* node, int mask ) +{ + while( node ) + { + if( !( node->GetContents() & mask ) ) + { + node->SetContents( node->GetContents() & ~AREACONTENTS_SOLID ); + } + ChangeMultipleBoundingBoxContents_r( node->GetChild( 0 ), mask ); + node = node->GetChild( 1 ); + } +} + +/* +============ +idAASBuild::Build +============ +*/ +bool idAASBuild::Build( const idStr& fileName, const idAASSettings* settings ) +{ + int i, bit, mask, startTime; + idMapFile* mapFile; + idBrushList brushList; + idList expandedBrushes; + idBrush* b; + idBrushBSP bsp; + idStr name; + idAASReach reach; + idAASCluster cluster; + idStrList entityClassNames; + + startTime = Sys_Milliseconds(); + + Shutdown(); + + aasSettings = settings; + + name = fileName; + name.SetFileExtension( "map" ); + + mapFile = new idMapFile; + if( !mapFile->Parse( name ) ) + { + delete mapFile; + common->Error( "Couldn't load map file: '%s'", name.c_str() ); + return false; + } + + // check if this map has any entities that use this AAS file + if( !CheckForEntities( mapFile, entityClassNames ) ) + { + delete mapFile; + common->Printf( "no entities in map that use %s\n", settings->fileExtension.c_str() ); + return true; + } + + // load map file brushes + brushList = AddBrushesForMapFile( mapFile, brushList ); + + // if empty map + if( brushList.Num() == 0 ) + { + delete mapFile; + common->Error( "%s is empty", name.c_str() ); + return false; + } + + // merge as many brushes as possible before expansion + brushList.Merge( MergeAllowed ); + + // if there is a .proc file newer than the .map file + if( LoadProcBSP( fileName, mapFile->GetFileTime() ) ) + { + ClipBrushSidesWithProcBSP( brushList ); + DeleteProcBSP(); + } + + // make copies of the brush list + expandedBrushes.Append( &brushList ); + for( i = 1; i < aasSettings->numBoundingBoxes; i++ ) + { + expandedBrushes.Append( brushList.Copy() ); + } + + // expand brushes for the axial bounding boxes + mask = AREACONTENTS_SOLID; + for( i = 0; i < expandedBrushes.Num(); i++ ) + { + for( b = expandedBrushes[i]->Head(); b; b = b->Next() ) + { + b->ExpandForAxialBox( aasSettings->boundingBoxes[i] ); + bit = 1 << ( i + AREACONTENTS_BBOX_BIT ); + mask |= bit; + b->SetContents( b->GetContents() | bit ); + } + } + + // move all brushes back into the original list + for( i = 1; i < aasSettings->numBoundingBoxes; i++ ) + { + brushList.AddToTail( *expandedBrushes[i] ); + delete expandedBrushes[i]; + } + + if( aasSettings->writeBrushMap ) + { + bsp.WriteBrushMap( fileName, "_" + aasSettings->fileExtension, AREACONTENTS_SOLID ); + } + + // build BSP tree from brushes + bsp.Build( brushList, AREACONTENTS_SOLID, ExpandedChopAllowed, ExpandedMergeAllowed ); + + // only solid nodes with all bits set for all bounding boxes need to stay solid + ChangeMultipleBoundingBoxContents_r( bsp.GetRootNode(), mask ); + + // portalize the bsp tree + bsp.Portalize(); + + // remove subspaces not reachable by entities + if( !bsp.RemoveOutside( mapFile, AREACONTENTS_SOLID, entityClassNames ) ) + { + bsp.LeakFile( name ); + delete mapFile; + common->Printf( "%s has no outside", name.c_str() ); + return false; + } + + // gravitational subdivision + GravitationalSubdivision( bsp ); + + // merge portals where possible + bsp.MergePortals( AREACONTENTS_SOLID ); + + // melt portal windings + bsp.MeltPortals( AREACONTENTS_SOLID ); + + if( aasSettings->writeBrushMap ) + { + WriteLedgeMap( fileName, "_" + aasSettings->fileExtension + "_ledge" ); + } + + // ledge subdivisions + LedgeSubdivision( bsp ); + + // merge leaf nodes + MergeLeafNodes( bsp ); + + // merge portals where possible + bsp.MergePortals( AREACONTENTS_SOLID ); + + // melt portal windings + bsp.MeltPortals( AREACONTENTS_SOLID ); + + // store the file from the bsp tree + StoreFile( bsp ); + file->settings = *aasSettings; + + // calculate reachability + reach.Build( mapFile, file ); + + // build clusters + cluster.Build( file ); + + // optimize the file + if( !aasSettings->noOptimize ) + { + file->Optimize(); + } + + // write the file + name.SetFileExtension( aasSettings->fileExtension ); + file->Write( name, mapFile->GetGeometryCRC() ); + + // delete the map file + delete mapFile; + + common->Printf( "%6d seconds to create AAS\n", ( Sys_Milliseconds() - startTime ) / 1000 ); + + return true; +} + +/* +============ +idAASBuild::BuildReachability +============ +*/ +bool idAASBuild::BuildReachability( const idStr& fileName, const idAASSettings* settings ) +{ + int startTime; + idMapFile* mapFile; + idStr name; + idAASReach reach; + idAASCluster cluster; + + startTime = Sys_Milliseconds(); + + aasSettings = settings; + + name = fileName; + name.SetFileExtension( "map" ); + + mapFile = new idMapFile; + if( !mapFile->Parse( name ) ) + { + delete mapFile; + common->Error( "Couldn't load map file: '%s'", name.c_str() ); + return false; + } + + file = new idAASFileLocal(); + + name.SetFileExtension( aasSettings->fileExtension ); + if( !file->Load( name, 0 ) ) + { + delete mapFile; + common->Error( "Couldn't load AAS file: '%s'", name.c_str() ); + return false; + } + + file->settings = *aasSettings; + + // calculate reachability + reach.Build( mapFile, file ); + + // build clusters + cluster.Build( file ); + + // write the file + file->Write( name, mapFile->GetGeometryCRC() ); + + // delete the map file + delete mapFile; + + common->Printf( "%6d seconds to calculate reachability\n", ( Sys_Milliseconds() - startTime ) / 1000 ); + + return true; +} + +/* +============ +ParseOptions +============ +*/ +int ParseOptions( const idCmdArgs& args, idAASSettings& settings ) +{ + int i; + idStr str; + + for( i = 1; i < args.Argc(); i++ ) + { + + str = args.Argv( i ); + str.StripLeading( '-' ); + + if( str.Icmp( "usePatches" ) == 0 ) + { + settings.usePatches = true; + common->Printf( "usePatches = true\n" ); + } + else if( str.Icmp( "writeBrushMap" ) == 0 ) + { + settings.writeBrushMap = true; + common->Printf( "writeBrushMap = true\n" ); + } + else if( str.Icmp( "playerFlood" ) == 0 ) + { + settings.playerFlood = true; + common->Printf( "playerFlood = true\n" ); + } + else if( str.Icmp( "noOptimize" ) == 0 ) + { + settings.noOptimize = true; + common->Printf( "noOptimize = true\n" ); + } + } + return args.Argc() - 1; +} + +/* +============ +RunAAS_f +============ +*/ +void RunAAS_f( const idCmdArgs& args ) +{ + int i; + idAASBuild aas; + idAASSettings settings; + idStr mapName; + + if( args.Argc() <= 1 ) + { + common->Printf( "runAAS [options] \n" + "options:\n" + " -usePatches = use bezier patches for collision detection.\n" + " -writeBrushMap = write a brush map with the AAS geometry.\n" + " -playerFlood = use player spawn points as valid AAS positions.\n" ); + return; + } + + common->ClearWarnings( "compiling AAS" ); + + common->SetRefreshOnPrint( true ); + + // get the aas settings definitions + const idDict* dict = gameEdit->FindEntityDefDict( "aas_types", false ); + if( !dict ) + { + common->Error( "Unable to find entityDef for 'aas_types'" ); + } + + const idKeyValue* kv = dict->MatchPrefix( "type" ); + while( kv != NULL ) + { + const idDict* settingsDict = gameEdit->FindEntityDefDict( kv->GetValue(), false ); + if( !settingsDict ) + { + common->Warning( "Unable to find '%s' in def/aas.def", kv->GetValue().c_str() ); + } + else + { + settings.FromDict( kv->GetValue(), settingsDict ); + i = ParseOptions( args, settings ); + mapName = args.Argv( i ); + mapName.BackSlashesToSlashes(); + if( mapName.Icmpn( "maps/", 4 ) != 0 ) + { + mapName = "maps/" + mapName; + } + aas.Build( mapName, &settings ); + } + + kv = dict->MatchPrefix( "type", kv ); + if( kv ) + { + common->Printf( "=======================================================\n" ); + } + } + common->SetRefreshOnPrint( false ); + common->PrintWarnings(); +} + +/* +============ +RunAASDir_f +============ +*/ +void RunAASDir_f( const idCmdArgs& args ) +{ + int i; + idAASBuild aas; + idAASSettings settings; + idFileList* mapFiles; + + if( args.Argc() <= 1 ) + { + common->Printf( "runAASDir \n" ); + return; + } + + common->ClearWarnings( "compiling AAS" ); + + common->SetRefreshOnPrint( true ); + + // get the aas settings definitions + const idDict* dict = gameEdit->FindEntityDefDict( "aas_types", false ); + if( !dict ) + { + common->Error( "Unable to find entityDef for 'aas_types'" ); + } + + // scan for .map files + mapFiles = fileSystem->ListFiles( idStr( "maps/" ) + args.Argv( 1 ), ".map" ); + + // create AAS files for all the .map files + for( i = 0; i < mapFiles->GetNumFiles(); i++ ) + { + if( i ) + { + common->Printf( "=======================================================\n" ); + } + + const idKeyValue* kv = dict->MatchPrefix( "type" ); + while( kv != NULL ) + { + const idDict* settingsDict = gameEdit->FindEntityDefDict( kv->GetValue(), false ); + if( !settingsDict ) + { + common->Warning( "Unable to find '%s' in def/aas.def", kv->GetValue().c_str() ); + } + else + { + settings.FromDict( kv->GetValue(), settingsDict ); + aas.Build( idStr( "maps/" ) + args.Argv( 1 ) + "/" + mapFiles->GetFile( i ), &settings ); + } + + kv = dict->MatchPrefix( "type", kv ); + if( kv ) + { + common->Printf( "=======================================================\n" ); + } + } + } + + fileSystem->FreeFileList( mapFiles ); + + common->SetRefreshOnPrint( false ); + common->PrintWarnings(); +} + +/* +============ +RunReach_f +============ +*/ +void RunReach_f( const idCmdArgs& args ) +{ + int i; + idAASBuild aas; + idAASSettings settings; + + if( args.Argc() <= 1 ) + { + common->Printf( "runReach [options] \n" ); + return; + } + + common->ClearWarnings( "calculating AAS reachability" ); + + common->SetRefreshOnPrint( true ); + + // get the aas settings definitions + const idDict* dict = gameEdit->FindEntityDefDict( "aas_types", false ); + if( !dict ) + { + common->Error( "Unable to find entityDef for 'aas_types'" ); + } + + const idKeyValue* kv = dict->MatchPrefix( "type" ); + while( kv != NULL ) + { + const idDict* settingsDict = gameEdit->FindEntityDefDict( kv->GetValue(), false ); + if( !settingsDict ) + { + common->Warning( "Unable to find '%s' in def/aas.def", kv->GetValue().c_str() ); + } + else + { + settings.FromDict( kv->GetValue(), settingsDict ); + i = ParseOptions( args, settings ); + aas.BuildReachability( idStr( "maps/" ) + args.Argv( i ), &settings ); + } + + kv = dict->MatchPrefix( "type", kv ); + if( kv ) + { + common->Printf( "=======================================================\n" ); + } + } + + common->SetRefreshOnPrint( false ); + common->PrintWarnings(); +} diff --git a/neo/tools/compilers/aas/AASBuild_file.cpp b/neo/tools/compilers/aas/AASBuild_file.cpp new file mode 100644 index 00000000..124da546 --- /dev/null +++ b/neo/tools/compilers/aas/AASBuild_file.cpp @@ -0,0 +1,559 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "AASBuild_local.h" + +#define VERTEX_HASH_BOXSIZE (1<<6) // must be power of 2 +#define VERTEX_HASH_SIZE (VERTEX_HASH_BOXSIZE*VERTEX_HASH_BOXSIZE) +#define EDGE_HASH_SIZE (1<<14) + +#define INTEGRAL_EPSILON 0.01f +#define VERTEX_EPSILON 0.1f + +#define AAS_PLANE_NORMAL_EPSILON 0.00001f +#define AAS_PLANE_DIST_EPSILON 0.01f + + +idHashIndex* aas_vertexHash; +idHashIndex* aas_edgeHash; +idBounds aas_vertexBounds; +int aas_vertexShift; + +/* +================ +idAASBuild::SetupHash +================ +*/ +void idAASBuild::SetupHash( void ) +{ + aas_vertexHash = new idHashIndex( VERTEX_HASH_SIZE, 1024 ); + aas_edgeHash = new idHashIndex( EDGE_HASH_SIZE, 1024 ); +} + +/* +================ +idAASBuild::ShutdownHash +================ +*/ +void idAASBuild::ShutdownHash( void ) +{ + delete aas_vertexHash; + delete aas_edgeHash; +} + +/* +================ +idAASBuild::ClearHash +================ +*/ +void idAASBuild::ClearHash( const idBounds& bounds ) +{ + int i; + float f, max; + + aas_vertexHash->Clear(); + aas_edgeHash->Clear(); + aas_vertexBounds = bounds; + + max = bounds[1].x - bounds[0].x; + f = bounds[1].y - bounds[0].y; + if( f > max ) + { + max = f; + } + aas_vertexShift = ( float ) max / VERTEX_HASH_BOXSIZE; + for( i = 0; ( 1 << i ) < aas_vertexShift; i++ ) + { + } + if( i == 0 ) + { + aas_vertexShift = 1; + } + else + { + aas_vertexShift = i; + } +} + +/* +================ +idAASBuild::HashVec +================ +*/ +ID_INLINE int idAASBuild::HashVec( const idVec3& vec ) +{ + int x, y; + + x = ( ( ( int )( vec[0] - aas_vertexBounds[0].x + 0.5 ) ) + 2 ) >> 2; + y = ( ( ( int )( vec[1] - aas_vertexBounds[0].y + 0.5 ) ) + 2 ) >> 2; + return ( x + y * VERTEX_HASH_BOXSIZE ) & ( VERTEX_HASH_SIZE - 1 ); +} + +/* +================ +idAASBuild::GetVertex +================ +*/ +bool idAASBuild::GetVertex( const idVec3& v, int* vertexNum ) +{ + int i, hashKey, vn; + aasVertex_t vert, *p; + + for( i = 0; i < 3; i++ ) + { + if( idMath::Fabs( v[i] - idMath::Rint( v[i] ) ) < INTEGRAL_EPSILON ) + { + vert[i] = idMath::Rint( v[i] ); + } + else + { + vert[i] = v[i]; + } + } + + hashKey = idAASBuild::HashVec( vert ); + + for( vn = aas_vertexHash->First( hashKey ); vn >= 0; vn = aas_vertexHash->Next( vn ) ) + { + p = &file->vertices[vn]; + // first compare z-axis because hash is based on x-y plane + if( idMath::Fabs( vert.z - p->z ) < VERTEX_EPSILON && + idMath::Fabs( vert.x - p->x ) < VERTEX_EPSILON && + idMath::Fabs( vert.y - p->y ) < VERTEX_EPSILON ) + { + *vertexNum = vn; + return true; + } + } + + *vertexNum = file->vertices.Num(); + aas_vertexHash->Add( hashKey, file->vertices.Num() ); + file->vertices.Append( vert ); + + return false; +} + +/* +================ +idAASBuild::GetEdge +================ +*/ +bool idAASBuild::GetEdge( const idVec3& v1, const idVec3& v2, int* edgeNum, int v1num ) +{ + int v2num, hashKey, e; + int* vertexNum; + aasEdge_t edge; + bool found; + + if( v1num != -1 ) + { + found = true; + } + else + { + found = GetVertex( v1, &v1num ); + } + found &= GetVertex( v2, &v2num ); + // if both vertexes are the same or snapped onto each other + if( v1num == v2num ) + { + *edgeNum = 0; + return true; + } + hashKey = aas_edgeHash->GenerateKey( v1num, v2num ); + // if both vertexes where already stored + if( found ) + { + for( e = aas_edgeHash->First( hashKey ); e >= 0; e = aas_edgeHash->Next( e ) ) + { + + vertexNum = file->edges[e].vertexNum; + if( vertexNum[0] == v2num ) + { + if( vertexNum[1] == v1num ) + { + // negative for a reversed edge + *edgeNum = -e; + break; + } + } + else if( vertexNum[0] == v1num ) + { + if( vertexNum[1] == v2num ) + { + *edgeNum = e; + break; + } + } + } + // if edge found in hash + if( e >= 0 ) + { + return true; + } + } + + *edgeNum = file->edges.Num(); + aas_edgeHash->Add( hashKey, file->edges.Num() ); + + edge.vertexNum[0] = v1num; + edge.vertexNum[1] = v2num; + + file->edges.Append( edge ); + + return false; +} + +/* +================ +idAASBuild::GetFaceForPortal +================ +*/ +bool idAASBuild::GetFaceForPortal( idBrushBSPPortal* portal, int side, int* faceNum ) +{ + int i, j, v1num; + int numFaceEdges, faceEdges[MAX_POINTS_ON_WINDING]; + idWinding* w; + aasFace_t face; + + if( portal->GetFaceNum() > 0 ) + { + if( side ) + { + *faceNum = -portal->GetFaceNum(); + } + else + { + *faceNum = portal->GetFaceNum(); + } + return true; + } + + w = portal->GetWinding(); + // turn the winding into a sequence of edges + numFaceEdges = 0; + v1num = -1; // first vertex unknown + for( i = 0; i < w->GetNumPoints(); i++ ) + { + + GetEdge( ( *w )[i].ToVec3(), ( *w )[( i + 1 ) % w->GetNumPoints()].ToVec3(), &faceEdges[numFaceEdges], v1num ); + + if( faceEdges[numFaceEdges] ) + { + // last vertex of this edge is the first vertex of the next edge + v1num = file->edges[abs( faceEdges[numFaceEdges] )].vertexNum[INT32_SIGNBITNOTSET( faceEdges[numFaceEdges] )]; + + // this edge is valid so keep it + numFaceEdges++; + } + } + + // should have at least 3 edges + if( numFaceEdges < 3 ) + { + return false; + } + + // the polygon is invalid if some edge is found twice + for( i = 0; i < numFaceEdges; i++ ) + { + for( j = i + 1; j < numFaceEdges; j++ ) + { + if( faceEdges[i] == faceEdges[j] || faceEdges[i] == -faceEdges[j] ) + { + return false; + } + } + } + + portal->SetFaceNum( file->faces.Num() ); + + face.planeNum = file->planeList.FindPlane( portal->GetPlane(), AAS_PLANE_NORMAL_EPSILON, AAS_PLANE_DIST_EPSILON ); + face.flags = portal->GetFlags(); + face.areas[0] = face.areas[1] = 0; + face.firstEdge = file->edgeIndex.Num(); + face.numEdges = numFaceEdges; + for( i = 0; i < numFaceEdges; i++ ) + { + file->edgeIndex.Append( faceEdges[i] ); + } + if( side ) + { + *faceNum = -file->faces.Num(); + } + else + { + *faceNum = file->faces.Num(); + } + file->faces.Append( face ); + + return true; +} + +/* +================ +idAASBuild::GetAreaForLeafNode +================ +*/ +bool idAASBuild::GetAreaForLeafNode( idBrushBSPNode* node, int* areaNum ) +{ + int s, faceNum; + idBrushBSPPortal* p; + aasArea_t area; + + if( node->GetAreaNum() ) + { + *areaNum = -node->GetAreaNum(); + return true; + } + + area.flags = node->GetFlags(); + area.cluster = area.clusterAreaNum = 0; + area.contents = node->GetContents(); + area.firstFace = file->faceIndex.Num(); + area.numFaces = 0; + area.reach = NULL; + area.rev_reach = NULL; + + for( p = node->GetPortals(); p; p = p->Next( s ) ) + { + s = ( p->GetNode( 1 ) == node ); + + if( !GetFaceForPortal( p, s, &faceNum ) ) + { + continue; + } + + file->faceIndex.Append( faceNum ); + area.numFaces++; + + if( faceNum > 0 ) + { + file->faces[abs( faceNum )].areas[0] = file->areas.Num(); + } + else + { + file->faces[abs( faceNum )].areas[1] = file->areas.Num(); + } + } + + if( !area.numFaces ) + { + *areaNum = 0; + return false; + } + + *areaNum = -file->areas.Num(); + node->SetAreaNum( file->areas.Num() ); + file->areas.Append( area ); + + DisplayRealTimeString( "\r%6d", file->areas.Num() ); + + return true; +} + +/* +================ +idAASBuild::StoreTree_r +================ +*/ +int idAASBuild::StoreTree_r( idBrushBSPNode* node ) +{ + int areaNum, nodeNum, child0, child1; + aasNode_t aasNode; + + if( !node ) + { + return 0; + } + + if( node->GetContents() & AREACONTENTS_SOLID ) + { + return 0; + } + + if( !node->GetChild( 0 ) && !node->GetChild( 1 ) ) + { + if( GetAreaForLeafNode( node, &areaNum ) ) + { + return areaNum; + } + return 0; + } + + aasNode.planeNum = file->planeList.FindPlane( node->GetPlane(), AAS_PLANE_NORMAL_EPSILON, AAS_PLANE_DIST_EPSILON ); + aasNode.children[0] = aasNode.children[1] = 0; + nodeNum = file->nodes.Num(); + file->nodes.Append( aasNode ); + + // !@#$%^ cause of some bug we cannot set the children directly with the StoreTree_r return value + child0 = StoreTree_r( node->GetChild( 0 ) ); + file->nodes[nodeNum].children[0] = child0; + child1 = StoreTree_r( node->GetChild( 1 ) ); + file->nodes[nodeNum].children[1] = child1; + + if( !child0 && !child1 ) + { + file->nodes.SetNum( file->nodes.Num() - 1 ); + return 0; + } + + return nodeNum; +} + +/* +================ +idAASBuild::GetSizeEstimate_r +================ +*/ +typedef struct sizeEstimate_s +{ + int numEdgeIndexes; + int numFaceIndexes; + int numAreas; + int numNodes; +} sizeEstimate_t; + +void idAASBuild::GetSizeEstimate_r( idBrushBSPNode* parent, idBrushBSPNode* node, struct sizeEstimate_s& size ) +{ + idBrushBSPPortal* p; + int s; + + if( !node ) + { + return; + } + + if( node->GetContents() & AREACONTENTS_SOLID ) + { + return; + } + + if( !node->GetChild( 0 ) && !node->GetChild( 1 ) ) + { + // multiple branches of the bsp tree might point to the same leaf node + if( node->GetParent() == parent ) + { + size.numAreas++; + for( p = node->GetPortals(); p; p = p->Next( s ) ) + { + s = ( p->GetNode( 1 ) == node ); + size.numFaceIndexes++; + size.numEdgeIndexes += p->GetWinding()->GetNumPoints(); + } + } + } + else + { + size.numNodes++; + } + + GetSizeEstimate_r( node, node->GetChild( 0 ), size ); + GetSizeEstimate_r( node, node->GetChild( 1 ), size ); +} + +/* +================ +idAASBuild::SetSizeEstimate +================ +*/ +void idAASBuild::SetSizeEstimate( const idBrushBSP& bsp, idAASFileLocal* file ) +{ + sizeEstimate_t size; + + size.numEdgeIndexes = 1; + size.numFaceIndexes = 1; + size.numAreas = 1; + size.numNodes = 1; + + GetSizeEstimate_r( NULL, bsp.GetRootNode(), size ); + + file->planeList.Resize( size.numNodes / 2, 1024 ); + file->vertices.Resize( size.numEdgeIndexes / 3, 1024 ); + file->edges.Resize( size.numEdgeIndexes / 2, 1024 ); + file->edgeIndex.Resize( size.numEdgeIndexes, 4096 ); + file->faces.Resize( size.numFaceIndexes, 1024 ); + file->faceIndex.Resize( size.numFaceIndexes, 4096 ); + file->areas.Resize( size.numAreas, 1024 ); + file->nodes.Resize( size.numNodes, 1024 ); +} + +/* +================ +idAASBuild::StoreFile +================ +*/ +bool idAASBuild::StoreFile( const idBrushBSP& bsp ) +{ + aasEdge_t edge; + aasFace_t face; + aasArea_t area; + aasNode_t node; + + common->Printf( "[Store AAS]\n" ); + + SetupHash(); + ClearHash( bsp.GetTreeBounds() ); + + file = new idAASFileLocal(); + + file->Clear(); + + SetSizeEstimate( bsp, file ); + + // the first edge is a dummy + memset( &edge, 0, sizeof( edge ) ); + file->edges.Append( edge ); + + // the first face is a dummy + memset( &face, 0, sizeof( face ) ); + file->faces.Append( face ); + + // the first area is a dummy + memset( &area, 0, sizeof( area ) ); + file->areas.Append( area ); + + // the first node is a dummy + memset( &node, 0, sizeof( node ) ); + file->nodes.Append( node ); + + // store the tree + StoreTree_r( bsp.GetRootNode() ); + + // calculate area bounds and a reachable point in the area + file->FinishAreas(); + + ShutdownHash(); + + common->Printf( "\r%6d areas\n", file->areas.Num() ); + + return true; +} diff --git a/neo/tools/compilers/aas/AASBuild_gravity.cpp b/neo/tools/compilers/aas/AASBuild_gravity.cpp new file mode 100644 index 00000000..37e5942e --- /dev/null +++ b/neo/tools/compilers/aas/AASBuild_gravity.cpp @@ -0,0 +1,421 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "AASBuild_local.h" + + +/* +============ +idAASBuild::SetPortalFlags_r +============ +*/ +void idAASBuild::SetPortalFlags_r( idBrushBSPNode* node ) +{ + int s; + idBrushBSPPortal* p; + idVec3 normal; + + if( !node ) + { + return; + } + + if( node->GetContents() & AREACONTENTS_SOLID ) + { + return; + } + + if( !node->GetChild( 0 ) && !node->GetChild( 1 ) ) + { + for( p = node->GetPortals(); p; p = p->Next( s ) ) + { + s = ( p->GetNode( 1 ) == node ); + + // if solid at the other side of the portal + if( p->GetNode( !s )->GetContents() & AREACONTENTS_SOLID ) + { + if( s ) + { + normal = -p->GetPlane().Normal(); + } + else + { + normal = p->GetPlane().Normal(); + } + if( normal * aasSettings->invGravityDir > aasSettings->minFloorCos ) + { + p->SetFlag( FACE_FLOOR ); + } + else + { + p->SetFlag( FACE_SOLID ); + } + } + } + return; + } + + SetPortalFlags_r( node->GetChild( 0 ) ); + SetPortalFlags_r( node->GetChild( 1 ) ); +} + +/* +============ +idAASBuild::PortalIsGap +============ +*/ +bool idAASBuild::PortalIsGap( idBrushBSPPortal* portal, int side ) +{ + idVec3 normal; + + // if solid at the other side of the portal + if( portal->GetNode( !side )->GetContents() & AREACONTENTS_SOLID ) + { + return false; + } + + if( side ) + { + normal = -( portal->GetPlane().Normal() ); + } + else + { + normal = portal->GetPlane().Normal(); + } + if( normal * aasSettings->invGravityDir > aasSettings->minFloorCos ) + { + return true; + } + return false; +} + +/* +============ +idAASBuild::GravSubdivLeafNode +============ +*/ +#define FACE_CHECKED BIT(31) +#define GRAVSUBDIV_EPSILON 0.1f + +void idAASBuild::GravSubdivLeafNode( idBrushBSPNode* node ) +{ + int s1, s2, i, j, k, side1; + int numSplits, numSplitters; + idBrushBSPPortal* p1, *p2; + idWinding* w1, *w2; + idVec3 normal; + idPlane plane; + idPlaneSet planeList; + float d, min, max; + int* splitterOrder; + int* bestNumSplits; + int floor, gap, numFloorChecked; + + // if this leaf node is already classified it cannot have a combination of floor and gap portals + if( node->GetFlags() & ( AREA_FLOOR | AREA_GAP ) ) + { + return; + } + + floor = gap = 0; + + // check if the area has a floor + for( p1 = node->GetPortals(); p1; p1 = p1->Next( s1 ) ) + { + s1 = ( p1->GetNode( 1 ) == node ); + + if( p1->GetFlags() & FACE_FLOOR ) + { + floor++; + } + } + + // find seperating planes between gap and floor portals + for( p1 = node->GetPortals(); p1; p1 = p1->Next( s1 ) ) + { + s1 = ( p1->GetNode( 1 ) == node ); + + // if the portal is a gap seen from this side + if( PortalIsGap( p1, s1 ) ) + { + gap++; + // if the area doesn't have a floor + if( !floor ) + { + break; + } + } + else + { + continue; + } + + numFloorChecked = 0; + + w1 = p1->GetWinding(); + + // test all edges of the gap + for( i = 0; i < w1->GetNumPoints(); i++ ) + { + + // create a plane through the edge of the gap parallel to the direction of gravity + normal = ( *w1 )[( i + 1 ) % w1->GetNumPoints()].ToVec3() - ( *w1 )[i].ToVec3(); + normal = normal.Cross( aasSettings->invGravityDir ); + if( normal.Normalize() < 0.2f ) + { + continue; + } + plane.SetNormal( normal ); + plane.FitThroughPoint( ( *w1 )[i].ToVec3() ); + + // get the side of the plane the gap is on + side1 = w1->PlaneSide( plane, GRAVSUBDIV_EPSILON ); + if( side1 == SIDE_ON ) + { + break; + } + + // test if the plane through the edge of the gap seperates the gap from a floor portal + for( p2 = node->GetPortals(); p2; p2 = p2->Next( s2 ) ) + { + s2 = ( p2->GetNode( 1 ) == node ); + + if( !( p2->GetFlags() & FACE_FLOOR ) ) + { + continue; + } + + if( p2->GetFlags() & FACE_CHECKED ) + { + continue; + } + + w2 = p2->GetWinding(); + + min = 2.0f * GRAVSUBDIV_EPSILON; + max = GRAVSUBDIV_EPSILON; + if( side1 == SIDE_FRONT ) + { + for( j = 0; j < w2->GetNumPoints(); j++ ) + { + d = plane.Distance( ( *w2 )[j].ToVec3() ); + if( d >= GRAVSUBDIV_EPSILON ) + { + break; // point at the same side of the plane as the gap + } + d = idMath::Fabs( d ); + if( d < min ) + { + min = d; + } + if( d > max ) + { + max = d; + } + } + } + else + { + for( j = 0; j < w2->GetNumPoints(); j++ ) + { + d = plane.Distance( ( *w2 )[j].ToVec3() ); + if( d <= -GRAVSUBDIV_EPSILON ) + { + break; // point at the same side of the plane as the gap + } + d = idMath::Fabs( d ); + if( d < min ) + { + min = d; + } + if( d > max ) + { + max = d; + } + } + } + + // a point of the floor portal was found to be at the same side of the plane as the gap + if( j < w2->GetNumPoints() ) + { + continue; + } + + // if the floor portal touches the plane + if( min < GRAVSUBDIV_EPSILON && max > GRAVSUBDIV_EPSILON ) + { + planeList.FindPlane( plane, 0.00001f, 0.1f ); + } + + p2->SetFlag( FACE_CHECKED ); + numFloorChecked++; + + } + if( numFloorChecked == floor ) + { + break; + } + } + + for( p2 = node->GetPortals(); p2; p2 = p2->Next( s2 ) ) + { + s2 = ( p2->GetNode( 1 ) == node ); + p2->RemoveFlag( FACE_CHECKED ); + } + } + + // if the leaf node does not have both floor and gap portals + if( !( gap && floor ) ) + { + if( floor ) + { + node->SetFlag( AREA_FLOOR ); + } + else if( gap ) + { + node->SetFlag( AREA_GAP ); + } + return; + } + + // if no valid seperators found + if( planeList.Num() == 0 ) + { + // NOTE: this should never happend, if it does the leaf node has degenerate portals + return; + } + + splitterOrder = ( int* ) _alloca( planeList.Num() * sizeof( int ) ); + bestNumSplits = ( int* ) _alloca( planeList.Num() * sizeof( int ) ); + numSplitters = 0; + + // test all possible seperators and sort them from best to worst + for( i = 0; i < planeList.Num(); i += 2 ) + { + numSplits = 0; + + for( p1 = node->GetPortals(); p1; p1 = p1->Next( s1 ) ) + { + s1 = ( p1->GetNode( 1 ) == node ); + if( p1->GetWinding()->PlaneSide( planeList[i], 0.1f ) == SIDE_CROSS ) + { + numSplits++; + } + } + + for( j = 0; j < numSplitters; j++ ) + { + if( numSplits < bestNumSplits[j] ) + { + for( k = numSplitters; k > j; k-- ) + { + bestNumSplits[k] = bestNumSplits[k - 1]; + splitterOrder[k] = splitterOrder[k - 1]; + } + bestNumSplits[j] = numSplits; + splitterOrder[j] = i; + numSplitters++; + break; + } + } + if( j >= numSplitters ) + { + bestNumSplits[j] = numSplits; + splitterOrder[j] = i; + numSplitters++; + } + } + + // try all seperators in order from best to worst + for( i = 0; i < numSplitters; i++ ) + { + if( node->Split( planeList[splitterOrder[i]], -1 ) ) + { + // we found a seperator that works + break; + } + } + if( i >= numSplitters ) + { + return; + } + + DisplayRealTimeString( "\r%6d", ++numGravitationalSubdivisions ); + + // test children for further splits + GravSubdivLeafNode( node->GetChild( 0 ) ); + GravSubdivLeafNode( node->GetChild( 1 ) ); +} + +/* +============ +idAASBuild::GravSubdiv_r +============ +*/ +void idAASBuild::GravSubdiv_r( idBrushBSPNode* node ) +{ + + if( !node ) + { + return; + } + + if( node->GetContents() & AREACONTENTS_SOLID ) + { + return; + } + + if( !node->GetChild( 0 ) && !node->GetChild( 1 ) ) + { + GravSubdivLeafNode( node ); + return; + } + + GravSubdiv_r( node->GetChild( 0 ) ); + GravSubdiv_r( node->GetChild( 1 ) ); +} + +/* +============ +idAASBuild::GravitationalSubdivision +============ +*/ +void idAASBuild::GravitationalSubdivision( idBrushBSP& bsp ) +{ + numGravitationalSubdivisions = 0; + + common->Printf( "[Gravitational Subdivision]\n" ); + + SetPortalFlags_r( bsp.GetRootNode() ); + GravSubdiv_r( bsp.GetRootNode() ); + + common->Printf( "\r%6d subdivisions\n", numGravitationalSubdivisions ); +} diff --git a/neo/tools/compilers/aas/AASBuild_ledge.cpp b/neo/tools/compilers/aas/AASBuild_ledge.cpp new file mode 100644 index 00000000..ad70145b --- /dev/null +++ b/neo/tools/compilers/aas/AASBuild_ledge.cpp @@ -0,0 +1,652 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "AASBuild_local.h" + +#define LEDGE_EPSILON 0.1f + +//=============================================================== +// +// idLedge +// +//=============================================================== + +/* +============ +idLedge::idLedge +============ +*/ +idLedge::idLedge( void ) +{ +} + +/* +============ +idLedge::idLedge +============ +*/ +idLedge::idLedge( const idVec3& v1, const idVec3& v2, const idVec3& gravityDir, idBrushBSPNode* n ) +{ + start = v1; + end = v2; + node = n; + numPlanes = 4; + planes[0].SetNormal( ( v1 - v2 ).Cross( gravityDir ) ); + planes[0].Normalize(); + planes[0].FitThroughPoint( v1 ); + planes[1].SetNormal( ( v1 - v2 ).Cross( planes[0].Normal() ) ); + planes[1].Normalize(); + planes[1].FitThroughPoint( v1 ); + planes[2].SetNormal( v1 - v2 ); + planes[2].Normalize(); + planes[2].FitThroughPoint( v1 ); + planes[3].SetNormal( v2 - v1 ); + planes[3].Normalize(); + planes[3].FitThroughPoint( v2 ); +} + +/* +============ +idLedge::AddPoint +============ +*/ +void idLedge::AddPoint( const idVec3& v ) +{ + if( planes[2].Distance( v ) > 0.0f ) + { + start = v; + planes[2].FitThroughPoint( start ); + } + if( planes[3].Distance( v ) > 0.0f ) + { + end = v; + planes[3].FitThroughPoint( end ); + } +} + +/* +============ +idLedge::CreateBevels + + NOTE: this assumes the gravity is vertical +============ +*/ +void idLedge::CreateBevels( const idVec3& gravityDir ) +{ + int i, j; + idBounds bounds; + idVec3 size, normal; + + bounds.Clear(); + bounds.AddPoint( start ); + bounds.AddPoint( end ); + size = bounds[1] - bounds[0]; + + // plane through ledge + planes[0].SetNormal( ( start - end ).Cross( gravityDir ) ); + planes[0].Normalize(); + planes[0].FitThroughPoint( start ); + // axial bevels at start and end point + i = size[1] > size[0]; + normal = vec3_origin; + normal[i] = 1.0f; + j = end[i] > start[i]; + planes[1 + j].SetNormal( normal ); + planes[1 + !j].SetNormal( -normal ); + planes[1].FitThroughPoint( start ); + planes[2].FitThroughPoint( end ); + numExpandedPlanes = 3; + // if additional bevels are required + if( idMath::Fabs( size[!i] ) > 0.01f ) + { + normal = vec3_origin; + normal[!i] = 1.0f; + j = end[!i] > start[!i]; + planes[3 + j].SetNormal( normal ); + planes[3 + !j].SetNormal( -normal ); + planes[3].FitThroughPoint( start ); + planes[4].FitThroughPoint( end ); + numExpandedPlanes = 5; + } + // opposite of first + planes[numExpandedPlanes + 0] = -planes[0]; + // number of planes used for splitting + numSplitPlanes = numExpandedPlanes + 1; + // top plane + planes[numSplitPlanes + 0].SetNormal( ( start - end ).Cross( planes[0].Normal() ) ); + planes[numSplitPlanes + 0].Normalize(); + planes[numSplitPlanes + 0].FitThroughPoint( start ); + // bottom plane + planes[numSplitPlanes + 1] = -planes[numSplitPlanes + 0]; + // total number of planes + numPlanes = numSplitPlanes + 2; +} + +/* +============ +idLedge::Expand +============ +*/ +void idLedge::Expand( const idBounds& bounds, float maxStepHeight ) +{ + int i, j; + idVec3 v; + + for( i = 0; i < numExpandedPlanes; i++ ) + { + + for( j = 0; j < 3; j++ ) + { + if( planes[i].Normal()[j] > 0.0f ) + { + v[j] = bounds[0][j]; + } + else + { + v[j] = bounds[1][j]; + } + } + + planes[i].SetDist( planes[i].Dist() + v * -planes[i].Normal() ); + } + + planes[numSplitPlanes + 0].SetDist( planes[numSplitPlanes + 0].Dist() + maxStepHeight ); + planes[numSplitPlanes + 1].SetDist( planes[numSplitPlanes + 1].Dist() + 1.0f ); +} + +/* +============ +idLedge::ChopWinding +============ +*/ +idWinding* idLedge::ChopWinding( const idWinding* winding ) const +{ + int i; + idWinding* w; + + w = winding->Copy(); + for( i = 0; i < numPlanes && w; i++ ) + { + w = w->Clip( -planes[i], ON_EPSILON, true ); + } + return w; +} + +/* +============ +idLedge::PointBetweenBounds +============ +*/ +bool idLedge::PointBetweenBounds( const idVec3& v ) const +{ + return ( planes[2].Distance( v ) < LEDGE_EPSILON ) && ( planes[3].Distance( v ) < LEDGE_EPSILON ); +} + + +//=============================================================== +// +// idAASBuild +// +//=============================================================== + +/* +============ +idAASBuild::LedgeSubdivFlood_r +============ +*/ +void idAASBuild::LedgeSubdivFlood_r( idBrushBSPNode* node, const idLedge* ledge ) +{ + int s1, i; + idBrushBSPPortal* p1; + idWinding* w; + idList nodeList; + + if( node->GetFlags() & NODE_VISITED ) + { + return; + } + + // if this is not already a ledge area + if( !( node->GetFlags() & AREA_LEDGE ) ) + { + for( p1 = node->GetPortals(); p1; p1 = p1->Next( s1 ) ) + { + s1 = ( p1->GetNode( 1 ) == node ); + + if( !( p1->GetFlags() & FACE_FLOOR ) ) + { + continue; + } + + // split the area if some part of the floor portal is inside the expanded ledge + w = ledge->ChopWinding( p1->GetWinding() ); + if( !w ) + { + continue; + } + delete w; + + for( i = 0; i < ledge->numSplitPlanes; i++ ) + { + if( node->PlaneSide( ledge->planes[i], 0.1f ) != SIDE_CROSS ) + { + continue; + } + if( !node->Split( ledge->planes[i], -1 ) ) + { + continue; + } + numLedgeSubdivisions++; + DisplayRealTimeString( "\r%6d", numLedgeSubdivisions ); + node->GetChild( 0 )->SetFlag( NODE_VISITED ); + LedgeSubdivFlood_r( node->GetChild( 1 ), ledge ); + return; + } + + node->SetFlag( AREA_LEDGE ); + break; + } + } + + node->SetFlag( NODE_VISITED ); + + // get all nodes we might need to flood into + for( p1 = node->GetPortals(); p1; p1 = p1->Next( s1 ) ) + { + s1 = ( p1->GetNode( 1 ) == node ); + + if( p1->GetNode( !s1 )->GetContents() & AREACONTENTS_SOLID ) + { + continue; + } + + // flood through this portal if the portal is partly inside the expanded ledge + w = ledge->ChopWinding( p1->GetWinding() ); + if( !w ) + { + continue; + } + delete w; + // add to list, cannot flood directly cause portals might be split on the way + nodeList.Append( p1->GetNode( !s1 ) ); + } + + // flood into other nodes + for( i = 0; i < nodeList.Num(); i++ ) + { + LedgeSubdivLeafNodes_r( nodeList[i], ledge ); + } +} + +/* +============ +idAASBuild::LedgeSubdivLeafNodes_r + + The node the ledge was originally part of might be split by other ledges. + Here we recurse down the tree from the original node to find all the new leaf nodes the ledge might be part of. +============ +*/ +void idAASBuild::LedgeSubdivLeafNodes_r( idBrushBSPNode* node, const idLedge* ledge ) +{ + if( !node ) + { + return; + } + if( !node->GetChild( 0 ) && !node->GetChild( 1 ) ) + { + LedgeSubdivFlood_r( node, ledge ); + return; + } + LedgeSubdivLeafNodes_r( node->GetChild( 0 ), ledge ); + LedgeSubdivLeafNodes_r( node->GetChild( 1 ), ledge ); +} + +/* +============ +idAASBuild::LedgeSubdiv +============ +*/ +void idAASBuild::LedgeSubdiv( idBrushBSPNode* root ) +{ + int i, j; + idBrush* brush; + idList sideList; + + // create ledge bevels and expand ledges + for( i = 0; i < ledgeList.Num(); i++ ) + { + + ledgeList[i].CreateBevels( aasSettings->gravityDir ); + ledgeList[i].Expand( aasSettings->boundingBoxes[0], aasSettings->maxStepHeight ); + + // if we should write out a ledge map + if( ledgeMap ) + { + sideList.SetNum( 0 ); + for( j = 0; j < ledgeList[i].numPlanes; j++ ) + { + sideList.Append( new idBrushSide( ledgeList[i].planes[j], -1 ) ); + } + + brush = new idBrush(); + brush->FromSides( sideList ); + + ledgeMap->WriteBrush( brush ); + + delete brush; + } + + // flood tree from the ledge node and subdivide areas with the ledge + LedgeSubdivLeafNodes_r( ledgeList[i].node, &ledgeList[i] ); + + // remove the node visited flags + ledgeList[i].node->RemoveFlagRecurseFlood( NODE_VISITED ); + } +} + +/* +============ +idAASBuild::IsLedgeSide_r +============ +*/ +bool idAASBuild::IsLedgeSide_r( idBrushBSPNode* node, idFixedWinding* w, const idPlane& plane, const idVec3& normal, const idVec3& origin, const float radius ) +{ + int res, i; + idFixedWinding back; + float dist; + + if( !node ) + { + return false; + } + + while( node->GetChild( 0 ) && node->GetChild( 1 ) ) + { + dist = node->GetPlane().Distance( origin ); + if( dist > radius ) + { + res = SIDE_FRONT; + } + else if( dist < -radius ) + { + res = SIDE_BACK; + } + else + { + res = w->Split( &back, node->GetPlane(), LEDGE_EPSILON ); + } + if( res == SIDE_FRONT ) + { + node = node->GetChild( 0 ); + } + else if( res == SIDE_BACK ) + { + node = node->GetChild( 1 ); + } + else if( res == SIDE_ON ) + { + // continue with the side the winding faces + if( node->GetPlane().Normal() * normal > 0.0f ) + { + node = node->GetChild( 0 ); + } + else + { + node = node->GetChild( 1 ); + } + } + else + { + if( IsLedgeSide_r( node->GetChild( 1 ), &back, plane, normal, origin, radius ) ) + { + return true; + } + node = node->GetChild( 0 ); + } + } + + if( node->GetContents() & AREACONTENTS_SOLID ) + { + return false; + } + + for( i = 0; i < w->GetNumPoints(); i++ ) + { + if( plane.Distance( ( *w )[i].ToVec3() ) > 0.0f ) + { + return true; + } + } + + return false; +} + +/* +============ +idAASBuild::AddLedge +============ +*/ +void idAASBuild::AddLedge( const idVec3& v1, const idVec3& v2, idBrushBSPNode* node ) +{ + int i, j, merged; + + // first try to merge the ledge with existing ledges + merged = -1; + for( i = 0; i < ledgeList.Num(); i++ ) + { + + for( j = 0; j < 2; j++ ) + { + if( idMath::Fabs( ledgeList[i].planes[j].Distance( v1 ) ) > LEDGE_EPSILON ) + { + break; + } + if( idMath::Fabs( ledgeList[i].planes[j].Distance( v2 ) ) > LEDGE_EPSILON ) + { + break; + } + } + if( j < 2 ) + { + continue; + } + + if( !ledgeList[i].PointBetweenBounds( v1 ) && + !ledgeList[i].PointBetweenBounds( v2 ) ) + { + continue; + } + + if( merged == -1 ) + { + ledgeList[i].AddPoint( v1 ); + ledgeList[i].AddPoint( v2 ); + merged = i; + } + else + { + ledgeList[merged].AddPoint( ledgeList[i].start ); + ledgeList[merged].AddPoint( ledgeList[i].end ); + ledgeList.RemoveIndex( i ); + break; + } + } + + // if the ledge could not be merged + if( merged == -1 ) + { + ledgeList.Append( idLedge( v1, v2, aasSettings->gravityDir, node ) ); + } +} + +/* +============ +idAASBuild::FindLeafNodeLedges +============ +*/ +void idAASBuild::FindLeafNodeLedges( idBrushBSPNode* root, idBrushBSPNode* node ) +{ + int s1, i; + idBrushBSPPortal* p1; + idWinding* w; + idVec3 v1, v2, normal, origin; + idFixedWinding winding; + idBounds bounds; + idPlane plane; + float radius; + + for( p1 = node->GetPortals(); p1; p1 = p1->Next( s1 ) ) + { + s1 = ( p1->GetNode( 1 ) == node ); + + if( !( p1->GetFlags() & FACE_FLOOR ) ) + { + continue; + } + + if( s1 ) + { + plane = p1->GetPlane(); + w = p1->GetWinding()->Reverse(); + } + else + { + plane = -p1->GetPlane(); + w = p1->GetWinding(); + } + + for( i = 0; i < w->GetNumPoints(); i++ ) + { + + v1 = ( *w )[i].ToVec3(); + v2 = ( *w )[( i + 1 ) % w->GetNumPoints()].ToVec3(); + normal = ( v2 - v1 ).Cross( aasSettings->gravityDir ); + if( normal.Normalize() < 0.5f ) + { + continue; + } + + winding.Clear(); + winding += v1 + normal * LEDGE_EPSILON * 0.5f; + winding += v2 + normal * LEDGE_EPSILON * 0.5f; + winding += winding[1].ToVec3() + ( aasSettings->maxStepHeight + 1.0f ) * aasSettings->gravityDir; + winding += winding[0].ToVec3() + ( aasSettings->maxStepHeight + 1.0f ) * aasSettings->gravityDir; + + winding.GetBounds( bounds ); + origin = ( bounds[1] - bounds[0] ) * 0.5f; + radius = origin.Length() + LEDGE_EPSILON; + origin = bounds[0] + origin; + + plane.FitThroughPoint( v1 + aasSettings->maxStepHeight * aasSettings->gravityDir ); + + if( !IsLedgeSide_r( root, &winding, plane, normal, origin, radius ) ) + { + continue; + } + + AddLedge( v1, v2, node ); + } + + if( w != p1->GetWinding() ) + { + delete w; + } + } +} + +/* +============ +idAASBuild::FindLedges_r +============ +*/ +void idAASBuild::FindLedges_r( idBrushBSPNode* root, idBrushBSPNode* node ) +{ + if( !node ) + { + return; + } + + if( node->GetContents() & AREACONTENTS_SOLID ) + { + return; + } + + if( !node->GetChild( 0 ) && !node->GetChild( 1 ) ) + { + if( node->GetFlags() & NODE_VISITED ) + { + return; + } + FindLeafNodeLedges( root, node ); + node->SetFlag( NODE_VISITED ); + return; + } + + FindLedges_r( root, node->GetChild( 0 ) ); + FindLedges_r( root, node->GetChild( 1 ) ); +} + +/* +============ +idAASBuild::WriteLedgeMap +============ +*/ +void idAASBuild::WriteLedgeMap( const idStr& fileName, const idStr& ext ) +{ + ledgeMap = new idBrushMap( fileName, ext ); + ledgeMap->SetTexture( "textures/base_trim/bluetex4q_ed" ); +} + +/* +============ +idAASBuild::LedgeSubdivision + + NOTE: this assumes the bounding box is higher than the maximum step height + only ledges with vertical sides are considered +============ +*/ +void idAASBuild::LedgeSubdivision( idBrushBSP& bsp ) +{ + numLedgeSubdivisions = 0; + ledgeList.Clear(); + + common->Printf( "[Ledge Subdivision]\n" ); + + bsp.GetRootNode()->RemoveFlagRecurse( NODE_VISITED ); + FindLedges_r( bsp.GetRootNode(), bsp.GetRootNode() ); + bsp.GetRootNode()->RemoveFlagRecurse( NODE_VISITED ); + + common->Printf( "\r%6d ledges\n", ledgeList.Num() ); + + LedgeSubdiv( bsp.GetRootNode() ); + + common->Printf( "\r%6d subdivisions\n", numLedgeSubdivisions ); +} diff --git a/neo/tools/compilers/aas/AASBuild_local.h b/neo/tools/compilers/aas/AASBuild_local.h new file mode 100644 index 00000000..2604820a --- /dev/null +++ b/neo/tools/compilers/aas/AASBuild_local.h @@ -0,0 +1,152 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __AASBUILD_LOCAL_H__ +#define __AASBUILD_LOCAL_H__ + +#include "..\..\aas\AASFile.h" +#include "..\..\aas\AASFile_local.h" + +#include "Brush.h" +#include "BrushBSP.h" +#include "AASReach.h" +#include "AASCluster.h" + + +//=============================================================== +// +// idAASBuild +// +//=============================================================== + +typedef struct aasProcNode_s +{ + idPlane plane; + int children[2]; // negative numbers are (-1 - areaNumber), 0 = solid +} aasProcNode_t; + + +class idLedge +{ + +public: + idVec3 start; + idVec3 end; + idBrushBSPNode* node; + int numExpandedPlanes; + int numSplitPlanes; + int numPlanes; + idPlane planes[8]; + +public: + idLedge( void ); + idLedge( const idVec3& v1, const idVec3& v2, const idVec3& gravityDir, idBrushBSPNode* n ); + void AddPoint( const idVec3& v ); + void CreateBevels( const idVec3& gravityDir ); + void Expand( const idBounds& bounds, float maxStepHeight ); + idWinding* ChopWinding( const idWinding* winding ) const; + bool PointBetweenBounds( const idVec3& v ) const; +}; + + +class idAASBuild +{ + +public: + idAASBuild( void ); + ~idAASBuild( void ); + bool Build( const idStr& fileName, const idAASSettings* settings ); + bool BuildReachability( const idStr& fileName, const idAASSettings* settings ); + void Shutdown( void ); + +private: + const idAASSettings* aasSettings; + idAASFileLocal* file; + aasProcNode_t* procNodes; + int numProcNodes; + int numGravitationalSubdivisions; + int numMergedLeafNodes; + int numLedgeSubdivisions; + idList ledgeList; + idBrushMap* ledgeMap; + +private: // map loading + void ParseProcNodes( idLexer* src ); + bool LoadProcBSP( const char* name, ID_TIME_T minFileTime ); + void DeleteProcBSP( void ); + bool ChoppedAwayByProcBSP( int nodeNum, idFixedWinding* w, const idVec3& normal, const idVec3& origin, const float radius ); + void ClipBrushSidesWithProcBSP( idBrushList& brushList ); + int ContentsForAAS( int contents ); + idBrushList AddBrushesForMapBrush( const idMapBrush* mapBrush, const idVec3& origin, const idMat3& axis, int entityNum, int primitiveNum, idBrushList brushList ); + idBrushList AddBrushesForMapPatch( const idMapPatch* mapPatch, const idVec3& origin, const idMat3& axis, int entityNum, int primitiveNum, idBrushList brushList ); + idBrushList AddBrushesForMapEntity( const idMapEntity* mapEnt, int entityNum, idBrushList brushList ); + idBrushList AddBrushesForMapFile( const idMapFile* mapFile, idBrushList brushList ); + bool CheckForEntities( const idMapFile* mapFile, idStrList& entityClassNames ) const; + void ChangeMultipleBoundingBoxContents_r( idBrushBSPNode* node, int mask ); + +private: // gravitational subdivision + void SetPortalFlags_r( idBrushBSPNode* node ); + bool PortalIsGap( idBrushBSPPortal* portal, int side ); + void GravSubdivLeafNode( idBrushBSPNode* node ); + void GravSubdiv_r( idBrushBSPNode* node ); + void GravitationalSubdivision( idBrushBSP& bsp ); + +private: // ledge subdivision + void LedgeSubdivFlood_r( idBrushBSPNode* node, const idLedge* ledge ); + void LedgeSubdivLeafNodes_r( idBrushBSPNode* node, const idLedge* ledge ); + void LedgeSubdiv( idBrushBSPNode* root ); + bool IsLedgeSide_r( idBrushBSPNode* node, idFixedWinding* w, const idPlane& plane, const idVec3& normal, const idVec3& origin, const float radius ); + void AddLedge( const idVec3& v1, const idVec3& v2, idBrushBSPNode* node ); + void FindLeafNodeLedges( idBrushBSPNode* root, idBrushBSPNode* node ); + void FindLedges_r( idBrushBSPNode* root, idBrushBSPNode* node ); + void LedgeSubdivision( idBrushBSP& bsp ); + void WriteLedgeMap( const idStr& fileName, const idStr& ext ); + +private: // merging + bool AllGapsLeadToOtherNode( idBrushBSPNode* nodeWithGaps, idBrushBSPNode* otherNode ); + bool MergeWithAdjacentLeafNodes( idBrushBSP& bsp, idBrushBSPNode* node ); + void MergeLeafNodes_r( idBrushBSP& bsp, idBrushBSPNode* node ); + void MergeLeafNodes( idBrushBSP& bsp ); + +private: // storing file + void SetupHash( void ); + void ShutdownHash( void ); + void ClearHash( const idBounds& bounds ); + int HashVec( const idVec3& vec ); + bool GetVertex( const idVec3& v, int* vertexNum ); + bool GetEdge( const idVec3& v1, const idVec3& v2, int* edgeNum, int v1num ); + bool GetFaceForPortal( idBrushBSPPortal* portal, int side, int* faceNum ); + bool GetAreaForLeafNode( idBrushBSPNode* node, int* areaNum ); + int StoreTree_r( idBrushBSPNode* node ); + void GetSizeEstimate_r( idBrushBSPNode* parent, idBrushBSPNode* node, struct sizeEstimate_s& size ); + void SetSizeEstimate( const idBrushBSP& bsp, idAASFileLocal* file ); + bool StoreFile( const idBrushBSP& bsp ); + +}; + +#endif /* !__AASBUILD_LOCAL_H__ */ diff --git a/neo/tools/compilers/aas/AASBuild_merge.cpp b/neo/tools/compilers/aas/AASBuild_merge.cpp new file mode 100644 index 00000000..2ef60a86 --- /dev/null +++ b/neo/tools/compilers/aas/AASBuild_merge.cpp @@ -0,0 +1,188 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "AASBuild_local.h" + +/* +============ +idAASBuild::AllGapsLeadToOtherNode +============ +*/ +bool idAASBuild::AllGapsLeadToOtherNode( idBrushBSPNode* nodeWithGaps, idBrushBSPNode* otherNode ) +{ + int s; + idBrushBSPPortal* p; + + for( p = nodeWithGaps->GetPortals(); p; p = p->Next( s ) ) + { + s = ( p->GetNode( 1 ) == nodeWithGaps ); + + if( !PortalIsGap( p, s ) ) + { + continue; + } + + if( p->GetNode( !s ) != otherNode ) + { + return false; + } + } + return true; +} + +/* +============ +idAASBuild::MergeWithAdjacentLeafNodes +============ +*/ +bool idAASBuild::MergeWithAdjacentLeafNodes( idBrushBSP& bsp, idBrushBSPNode* node ) +{ + int s, numMerges = 0, otherNodeFlags; + idBrushBSPPortal* p; + + do + { + for( p = node->GetPortals(); p; p = p->Next( s ) ) + { + s = ( p->GetNode( 1 ) == node ); + + // both leaf nodes must have the same contents + if( node->GetContents() != p->GetNode( !s )->GetContents() ) + { + continue; + } + + // cannot merge leaf nodes if one is near a ledge and the other is not + if( ( node->GetFlags() & AREA_LEDGE ) != ( p->GetNode( !s )->GetFlags() & AREA_LEDGE ) ) + { + continue; + } + + // cannot merge leaf nodes if one has a floor portal and the other a gap portal + if( node->GetFlags() & AREA_FLOOR ) + { + if( p->GetNode( !s )->GetFlags() & AREA_GAP ) + { + if( !AllGapsLeadToOtherNode( p->GetNode( !s ), node ) ) + { + continue; + } + } + } + else if( node->GetFlags() & AREA_GAP ) + { + if( p->GetNode( !s )->GetFlags() & AREA_FLOOR ) + { + if( !AllGapsLeadToOtherNode( node, p->GetNode( !s ) ) ) + { + continue; + } + } + } + + otherNodeFlags = p->GetNode( !s )->GetFlags(); + + // try to merge the leaf nodes + if( bsp.TryMergeLeafNodes( p, s ) ) + { + node->SetFlag( otherNodeFlags ); + if( node->GetFlags() & AREA_FLOOR ) + { + node->RemoveFlag( AREA_GAP ); + } + numMerges++; + DisplayRealTimeString( "\r%6d", ++numMergedLeafNodes ); + break; + } + } + } + while( p ); + + if( numMerges ) + { + return true; + } + return false; +} + +/* +============ +idAASBuild::MergeLeafNodes_r +============ +*/ +void idAASBuild::MergeLeafNodes_r( idBrushBSP& bsp, idBrushBSPNode* node ) +{ + + if( !node ) + { + return; + } + + if( node->GetContents() & AREACONTENTS_SOLID ) + { + return; + } + + if( node->GetFlags() & NODE_DONE ) + { + return; + } + + if( !node->GetChild( 0 ) && !node->GetChild( 1 ) ) + { + MergeWithAdjacentLeafNodes( bsp, node ); + node->SetFlag( NODE_DONE ); + return; + } + + MergeLeafNodes_r( bsp, node->GetChild( 0 ) ); + MergeLeafNodes_r( bsp, node->GetChild( 1 ) ); + + return; +} + +/* +============ +idAASBuild::MergeLeafNodes +============ +*/ +void idAASBuild::MergeLeafNodes( idBrushBSP& bsp ) +{ + numMergedLeafNodes = 0; + + common->Printf( "[Merge Leaf Nodes]\n" ); + + MergeLeafNodes_r( bsp, bsp.GetRootNode() ); + bsp.GetRootNode()->RemoveFlagRecurse( NODE_DONE ); + bsp.PruneMergedTree_r( bsp.GetRootNode() ); + + common->Printf( "\r%6d leaf nodes merged\n", numMergedLeafNodes ); +} diff --git a/neo/tools/compilers/aas/AASCluster.cpp b/neo/tools/compilers/aas/AASCluster.cpp new file mode 100644 index 00000000..c943454d --- /dev/null +++ b/neo/tools/compilers/aas/AASCluster.cpp @@ -0,0 +1,645 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "..\..\aas\AASFile.h" +#include "..\..\aas\AASFile_local.h" +#include "AASCluster.h" + + +/* +================ +idAASCluster::UpdatePortal +================ +*/ +bool idAASCluster::UpdatePortal( int areaNum, int clusterNum ) +{ + int portalNum; + aasPortal_t* portal; + + // find the portal for this area + for( portalNum = 1; portalNum < file->portals.Num(); portalNum++ ) + { + if( file->portals[portalNum].areaNum == areaNum ) + { + break; + } + } + + if( portalNum >= file->portals.Num() ) + { + common->Error( "no portal for area %d", areaNum ); + return true; + } + + portal = &file->portals[portalNum]; + + // if the portal is already fully updated + if( portal->clusters[0] == clusterNum ) + { + return true; + } + if( portal->clusters[1] == clusterNum ) + { + return true; + } + // if the portal has no front cluster yet + if( !portal->clusters[0] ) + { + portal->clusters[0] = clusterNum; + } + // if the portal has no back cluster yet + else if( !portal->clusters[1] ) + { + portal->clusters[1] = clusterNum; + } + else + { + // remove the cluster portal flag contents + file->areas[areaNum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + return false; + } + + // set the area cluster number to the negative portal number + file->areas[areaNum].cluster = -portalNum; + + // add the portal to the cluster using the portal index + file->portalIndex.Append( portalNum ); + file->clusters[clusterNum].numPortals++; + return true; +} + +/* +================ +idAASCluster::FloodClusterAreas_r +================ +*/ +bool idAASCluster::FloodClusterAreas_r( int areaNum, int clusterNum ) +{ + aasArea_t* area; + aasFace_t* face; + int faceNum, i; + idReachability* reach; + + area = &file->areas[areaNum]; + + // if the area is already part of a cluster + if( area->cluster > 0 ) + { + if( area->cluster == clusterNum ) + { + return true; + } + // there's a reachability going from one cluster to another only in one direction + common->Error( "cluster %d touched cluster %d at area %d\r\n", clusterNum, file->areas[areaNum].cluster, areaNum ); + return false; + } + + // if this area is a cluster portal + if( area->contents & AREACONTENTS_CLUSTERPORTAL ) + { + return UpdatePortal( areaNum, clusterNum ); + } + + // set the area cluster number + area->cluster = clusterNum; + + if( !noFaceFlood ) + { + // use area faces to flood into adjacent areas + for( i = 0; i < area->numFaces; i++ ) + { + faceNum = abs( file->faceIndex[area->firstFace + i] ); + face = &file->faces[faceNum]; + if( face->areas[0] == areaNum ) + { + if( face->areas[1] ) + { + if( !FloodClusterAreas_r( face->areas[1], clusterNum ) ) + { + return false; + } + } + } + else + { + if( face->areas[0] ) + { + if( !FloodClusterAreas_r( face->areas[0], clusterNum ) ) + { + return false; + } + } + } + } + } + + // use the reachabilities to flood into other areas + for( reach = file->areas[areaNum].reach; reach; reach = reach->next ) + { + if( !FloodClusterAreas_r( reach->toAreaNum, clusterNum ) ) + { + return false; + } + } + + // use the reversed reachabilities to flood into other areas + for( reach = file->areas[areaNum].rev_reach; reach; reach = reach->rev_next ) + { + if( !FloodClusterAreas_r( reach->fromAreaNum, clusterNum ) ) + { + return false; + } + } + + return true; +} + +/* +================ +idAASCluster::RemoveAreaClusterNumbers +================ +*/ +void idAASCluster::RemoveAreaClusterNumbers( void ) +{ + int i; + + for( i = 1; i < file->areas.Num(); i++ ) + { + file->areas[i].cluster = 0; + } +} + +/* +================ +idAASCluster::NumberClusterAreas +================ +*/ +void idAASCluster::NumberClusterAreas( int clusterNum ) +{ + int i, portalNum; + aasCluster_t* cluster; + aasPortal_t* portal; + + cluster = &file->clusters[clusterNum]; + cluster->numAreas = 0; + cluster->numReachableAreas = 0; + + // number all areas in this cluster WITH reachabilities + for( i = 1; i < file->areas.Num(); i++ ) + { + + if( file->areas[i].cluster != clusterNum ) + { + continue; + } + + if( !( file->areas[i].flags & ( AREA_REACHABLE_WALK | AREA_REACHABLE_FLY ) ) ) + { + continue; + } + + file->areas[i].clusterAreaNum = cluster->numAreas++; + cluster->numReachableAreas++; + } + + // number all portals in this cluster WITH reachabilities + for( i = 0; i < cluster->numPortals; i++ ) + { + portalNum = file->portalIndex[cluster->firstPortal + i]; + portal = &file->portals[portalNum]; + + if( !( file->areas[portal->areaNum].flags & ( AREA_REACHABLE_WALK | AREA_REACHABLE_FLY ) ) ) + { + continue; + } + + if( portal->clusters[0] == clusterNum ) + { + portal->clusterAreaNum[0] = cluster->numAreas++; + } + else + { + portal->clusterAreaNum[1] = cluster->numAreas++; + } + cluster->numReachableAreas++; + } + + // number all areas in this cluster WITHOUT reachabilities + for( i = 1; i < file->areas.Num(); i++ ) + { + + if( file->areas[i].cluster != clusterNum ) + { + continue; + } + + if( file->areas[i].flags & ( AREA_REACHABLE_WALK | AREA_REACHABLE_FLY ) ) + { + continue; + } + + file->areas[i].clusterAreaNum = cluster->numAreas++; + } + + // number all portals in this cluster WITHOUT reachabilities + for( i = 0; i < cluster->numPortals; i++ ) + { + portalNum = file->portalIndex[cluster->firstPortal + i]; + portal = &file->portals[portalNum]; + + if( file->areas[portal->areaNum].flags & ( AREA_REACHABLE_WALK | AREA_REACHABLE_FLY ) ) + { + continue; + } + + if( portal->clusters[0] == clusterNum ) + { + portal->clusterAreaNum[0] = cluster->numAreas++; + } + else + { + portal->clusterAreaNum[1] = cluster->numAreas++; + } + } +} + +/* +================ +idAASCluster::FindClusters +================ +*/ +bool idAASCluster::FindClusters( void ) +{ + int i, clusterNum; + aasCluster_t cluster; + + RemoveAreaClusterNumbers(); + + for( i = 1; i < file->areas.Num(); i++ ) + { + // if the area is already part of a cluster + if( file->areas[i].cluster ) + { + continue; + } + + // if not flooding through faces only use areas that have reachabilities + if( noFaceFlood ) + { + if( !( file->areas[i].flags & ( AREA_REACHABLE_WALK | AREA_REACHABLE_FLY ) ) ) + { + continue; + } + } + + // if the area is a cluster portal + if( file->areas[i].contents & AREACONTENTS_CLUSTERPORTAL ) + { + continue; + } + + cluster.numAreas = 0; + cluster.numReachableAreas = 0; + cluster.firstPortal = file->portalIndex.Num(); + cluster.numPortals = 0; + clusterNum = file->clusters.Num(); + file->clusters.Append( cluster ); + + // flood the areas in this cluster + if( !FloodClusterAreas_r( i, clusterNum ) ) + { + return false; + } + + // number the cluster areas + NumberClusterAreas( clusterNum ); + } + return true; +} + +/* +================ +idAASCluster::CreatePortals +================ +*/ +void idAASCluster::CreatePortals( void ) +{ + int i; + aasPortal_t portal; + + for( i = 1; i < file->areas.Num(); i++ ) + { + // if the area is a cluster portal + if( file->areas[i].contents & AREACONTENTS_CLUSTERPORTAL ) + { + portal.areaNum = i; + portal.clusters[0] = portal.clusters[1] = 0; + portal.maxAreaTravelTime = 0; + file->portals.Append( portal ); + } + } +} + +/* +================ +idAASCluster::TestPortals +================ +*/ +bool idAASCluster::TestPortals( void ) +{ + int i; + aasPortal_t* portal, *portal2; + aasArea_t* area, *area2; + idReachability* reach; + bool ok; + + ok = true; + for( i = 1; i < file->portals.Num(); i++ ) + { + portal = &file->portals[i]; + area = &file->areas[portal->areaNum]; + + // if this portal was already removed + if( !( area->contents & AREACONTENTS_CLUSTERPORTAL ) ) + { + continue; + } + + // may not removed this portal if it has a reachability to a removed portal + for( reach = area->reach; reach; reach = reach->next ) + { + area2 = &file->areas[ reach->toAreaNum ]; + if( area2->contents & AREACONTENTS_CLUSTERPORTAL ) + { + continue; + } + if( area2->cluster < 0 ) + { + break; + } + } + if( reach ) + { + continue; + } + + // may not removed this portal if it has a reversed reachability to a removed portal + for( reach = area->rev_reach; reach; reach = reach->rev_next ) + { + area2 = &file->areas[ reach->toAreaNum ]; + if( area2->contents & AREACONTENTS_CLUSTERPORTAL ) + { + continue; + } + if( area2->cluster < 0 ) + { + break; + } + } + if( reach ) + { + continue; + } + + // portal should have two clusters set + if( !portal->clusters[0] ) + { + area->contents &= ~AREACONTENTS_CLUSTERPORTAL; + ok = false; + continue; + } + if( !portal->clusters[1] ) + { + area->contents &= ~AREACONTENTS_CLUSTERPORTAL; + ok = false; + continue; + } + + // this portal may not have reachabilities to a portal that doesn't seperate the same clusters + for( reach = area->reach; reach; reach = reach->next ) + { + area2 = &file->areas[ reach->toAreaNum ]; + + if( !( area2->contents & AREACONTENTS_CLUSTERPORTAL ) ) + { + continue; + } + + if( area2->cluster > 0 ) + { + area2->contents &= ~AREACONTENTS_CLUSTERPORTAL; + ok = false; + continue; + } + + portal2 = &file->portals[ -file->areas[ reach->toAreaNum ].cluster ]; + + if( ( portal2->clusters[0] != portal->clusters[0] && portal2->clusters[0] != portal->clusters[1] ) || + ( portal2->clusters[1] != portal->clusters[0] && portal2->clusters[1] != portal->clusters[1] ) ) + { + area2->contents &= ~AREACONTENTS_CLUSTERPORTAL; + ok = false; + continue; + } + } + } + + return ok; +} + +/* +================ +idAASCluster::RemoveInvalidPortals +================ +*/ +void idAASCluster::RemoveInvalidPortals( void ) +{ + int i, j, k, face1Num, face2Num, otherAreaNum, numOpenAreas, numInvalidPortals; + aasFace_t* face1, *face2; + + numInvalidPortals = 0; + for( i = 0; i < file->areas.Num(); i++ ) + { + if( !( file->areas[i].contents & AREACONTENTS_CLUSTERPORTAL ) ) + { + continue; + } + + numOpenAreas = 0; + for( j = 0; j < file->areas[i].numFaces; j++ ) + { + face1Num = file->faceIndex[ file->areas[i].firstFace + j ]; + face1 = &file->faces[ abs( face1Num ) ]; + otherAreaNum = face1->areas[ face1Num < 0 ]; + + if( !otherAreaNum ) + { + continue; + } + + for( k = 0; k < j; k++ ) + { + face2Num = file->faceIndex[ file->areas[i].firstFace + k ]; + face2 = &file->faces[ abs( face2Num ) ]; + if( otherAreaNum == face2->areas[ face2Num < 0 ] ) + { + break; + } + } + if( k < j ) + { + continue; + } + + if( !( file->areas[otherAreaNum].contents & AREACONTENTS_CLUSTERPORTAL ) ) + { + numOpenAreas++; + } + } + + if( numOpenAreas <= 1 ) + { + file->areas[i].contents &= AREACONTENTS_CLUSTERPORTAL; + numInvalidPortals++; + } + } + + common->Printf( "\r%6d invalid portals removed\n", numInvalidPortals ); +} + +/* +================ +idAASCluster::Build +================ +*/ +bool idAASCluster::Build( idAASFileLocal* file ) +{ + + common->Printf( "[Clustering]\n" ); + + this->file = file; + this->noFaceFlood = true; + + RemoveInvalidPortals(); + + while( 1 ) + { + + // delete all existing clusters + file->DeleteClusters(); + + // create the portals from the portal areas + CreatePortals(); + + common->Printf( "\r%6d", file->portals.Num() ); + + // find the clusters + if( !FindClusters() ) + { + continue; + } + + // test the portals + if( !TestPortals() ) + { + continue; + } + + break; + } + + common->Printf( "\r%6d portals\n", file->portals.Num() ); + common->Printf( "%6d clusters\n", file->clusters.Num() ); + + for( int i = 0; i < file->clusters.Num(); i++ ) + { + common->Printf( "%6d reachable areas in cluster %d\n", file->clusters[i].numReachableAreas, i ); + } + + file->ReportRoutingEfficiency(); + + return true; +} + +/* +================ +idAASCluster::BuildSingleCluster +================ +*/ +bool idAASCluster::BuildSingleCluster( idAASFileLocal* file ) +{ + int i, numAreas; + aasCluster_t cluster; + + common->Printf( "[Clustering]\n" ); + + this->file = file; + + // delete all existing clusters + file->DeleteClusters(); + + cluster.firstPortal = 0; + cluster.numPortals = 0; + cluster.numAreas = file->areas.Num(); + cluster.numReachableAreas = 0; + // give all reachable areas in the cluster a number + for( i = 0; i < file->areas.Num(); i++ ) + { + file->areas[i].cluster = file->clusters.Num(); + if( file->areas[i].flags & ( AREA_REACHABLE_WALK | AREA_REACHABLE_FLY ) ) + { + file->areas[i].clusterAreaNum = cluster.numReachableAreas++; + } + } + // give the remaining areas a number within the cluster + numAreas = cluster.numReachableAreas; + for( i = 0; i < file->areas.Num(); i++ ) + { + if( file->areas[i].flags & ( AREA_REACHABLE_WALK | AREA_REACHABLE_FLY ) ) + { + continue; + } + file->areas[i].clusterAreaNum = numAreas++; + } + file->clusters.Append( cluster ); + + common->Printf( "%6d portals\n", file->portals.Num() ); + common->Printf( "%6d clusters\n", file->clusters.Num() ); + + for( i = 0; i < file->clusters.Num(); i++ ) + { + common->Printf( "%6d reachable areas in cluster %d\n", file->clusters[i].numReachableAreas, i ); + } + + file->ReportRoutingEfficiency(); + + return true; +} diff --git a/neo/tools/compilers/aas/AASCluster.h b/neo/tools/compilers/aas/AASCluster.h new file mode 100644 index 00000000..a410e865 --- /dev/null +++ b/neo/tools/compilers/aas/AASCluster.h @@ -0,0 +1,63 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __AASCLUSTER_H__ +#define __AASCLUSTER_H__ + +/* +=============================================================================== + + Area Clustering + +=============================================================================== +*/ + +class idAASCluster +{ + +public: + bool Build( idAASFileLocal* file ); + bool BuildSingleCluster( idAASFileLocal* file ); + +private: + idAASFileLocal* file; + bool noFaceFlood; + +private: + bool UpdatePortal( int areaNum, int clusterNum ); + bool FloodClusterAreas_r( int areaNum, int clusterNum ); + void RemoveAreaClusterNumbers( void ); + void NumberClusterAreas( int clusterNum ); + bool FindClusters( void ); + void CreatePortals( void ); + bool TestPortals( void ); + void ReportEfficiency( void ); + void RemoveInvalidPortals( void ); +}; + +#endif /* !__AASCLUSTER_H__ */ diff --git a/neo/tools/compilers/aas/AASReach.cpp b/neo/tools/compilers/aas/AASReach.cpp new file mode 100644 index 00000000..718b41fb --- /dev/null +++ b/neo/tools/compilers/aas/AASReach.cpp @@ -0,0 +1,1083 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "..\..\aas\AASFile.h" +#include "..\..\aas\AASFile_local.h" +#include "AASReach.h" + +#define INSIDEUNITS 2.0f +#define INSIDEUNITS_WALKEND 0.5f +#define INSIDEUNITS_WALKSTART 0.1f +#define INSIDEUNITS_SWIMEND 0.5f +#define INSIDEUNITS_FLYEND 0.5f +#define INSIDEUNITS_WATERJUMP 15.0f + + +/* +================ +idAASReach::ReachabilityExists +================ +*/ +bool idAASReach::ReachabilityExists( int fromAreaNum, int toAreaNum ) +{ + aasArea_t* area; + idReachability* reach; + + area = &file->areas[fromAreaNum]; + for( reach = area->reach; reach; reach = reach->next ) + { + if( reach->toAreaNum == toAreaNum ) + { + return true; + } + } + return false; +} + +/* +================ +idAASReach::CanSwimInArea +================ +*/ +ID_INLINE bool idAASReach::CanSwimInArea( int areaNum ) +{ + return ( file->areas[areaNum].contents & AREACONTENTS_WATER ) != 0; +} + +/* +================ +idAASReach::AreaHasFloor +================ +*/ +ID_INLINE bool idAASReach::AreaHasFloor( int areaNum ) +{ + return ( file->areas[areaNum].flags & AREA_FLOOR ) != 0; +} + +/* +================ +idAASReach::AreaIsClusterPortal +================ +*/ +ID_INLINE bool idAASReach::AreaIsClusterPortal( int areaNum ) +{ + return ( file->areas[areaNum].contents & AREACONTENTS_CLUSTERPORTAL ) != 0; +} + +/* +================ +idAASReach::AddReachabilityToArea +================ +*/ +void idAASReach::AddReachabilityToArea( idReachability* reach, int areaNum ) +{ + aasArea_t* area; + + area = &file->areas[areaNum]; + reach->next = area->reach; + area->reach = reach; + numReachabilities++; +} + +/* +================ +idAASReach::Reachability_Fly +================ +*/ +void idAASReach::Reachability_Fly( int areaNum ) +{ + int i, faceNum, otherAreaNum; + aasArea_t* area; + aasFace_t* face; + idReachability_Fly* reach; + + area = &file->areas[areaNum]; + + for( i = 0; i < area->numFaces; i++ ) + { + faceNum = file->faceIndex[area->firstFace + i]; + face = &file->faces[abs( faceNum )]; + + otherAreaNum = face->areas[INT32_SIGNBITNOTSET( faceNum )]; + + if( otherAreaNum == 0 ) + { + continue; + } + + if( ReachabilityExists( areaNum, otherAreaNum ) ) + { + continue; + } + + // create reachability going through this face + reach = new idReachability_Fly(); + reach->travelType = TFL_FLY; + reach->toAreaNum = otherAreaNum; + reach->fromAreaNum = areaNum; + reach->edgeNum = 0; + reach->travelTime = 1; + reach->start = file->FaceCenter( abs( faceNum ) ); + if( faceNum < 0 ) + { + reach->end = reach->start + file->planeList[face->planeNum].Normal() * INSIDEUNITS_FLYEND; + } + else + { + reach->end = reach->start - file->planeList[face->planeNum].Normal() * INSIDEUNITS_FLYEND; + } + AddReachabilityToArea( reach, areaNum ); + } +} + +/* +================ +idAASReach::Reachability_Swim +================ +*/ +void idAASReach::Reachability_Swim( int areaNum ) +{ + int i, faceNum, otherAreaNum; + aasArea_t* area; + aasFace_t* face; + idReachability_Swim* reach; + + if( !CanSwimInArea( areaNum ) ) + { + return; + } + + area = &file->areas[areaNum]; + + for( i = 0; i < area->numFaces; i++ ) + { + faceNum = file->faceIndex[area->firstFace + i]; + face = &file->faces[abs( faceNum )]; + + otherAreaNum = face->areas[INT32_SIGNBITNOTSET( faceNum )]; + + if( otherAreaNum == 0 ) + { + continue; + } + + if( !CanSwimInArea( otherAreaNum ) ) + { + continue; + } + + if( ReachabilityExists( areaNum, otherAreaNum ) ) + { + continue; + } + + // create reachability going through this face + reach = new idReachability_Swim(); + reach->travelType = TFL_SWIM; + reach->toAreaNum = otherAreaNum; + reach->fromAreaNum = areaNum; + reach->edgeNum = 0; + reach->travelTime = 1; + reach->start = file->FaceCenter( abs( faceNum ) ); + if( faceNum < 0 ) + { + reach->end = reach->start + file->planeList[face->planeNum].Normal() * INSIDEUNITS_SWIMEND; + } + else + { + reach->end = reach->start - file->planeList[face->planeNum].Normal() * INSIDEUNITS_SWIMEND; + } + AddReachabilityToArea( reach, areaNum ); + } +} + +/* +================ +idAASReach::Reachability_EqualFloorHeight +================ +*/ +void idAASReach::Reachability_EqualFloorHeight( int areaNum ) +{ + int i, k, l, m, n, faceNum, face1Num, face2Num, otherAreaNum, edge1Num = 0, edge2Num; + aasArea_t* area, *otherArea; + aasFace_t* face, *face1, *face2; + idReachability_Walk* reach; + + if( !AreaHasFloor( areaNum ) ) + { + return; + } + + area = &file->areas[areaNum]; + + for( i = 0; i < area->numFaces; i++ ) + { + faceNum = file->faceIndex[area->firstFace + i]; + face = &file->faces[abs( faceNum )]; + + otherAreaNum = face->areas[INT32_SIGNBITNOTSET( faceNum )]; + if( !AreaHasFloor( otherAreaNum ) ) + { + continue; + } + + otherArea = &file->areas[otherAreaNum]; + + for( k = 0; k < area->numFaces; k++ ) + { + face1Num = file->faceIndex[area->firstFace + k]; + face1 = &file->faces[abs( face1Num )]; + + if( !( face1->flags & FACE_FLOOR ) ) + { + continue; + } + for( l = 0; l < otherArea->numFaces; l++ ) + { + face2Num = file->faceIndex[otherArea->firstFace + l]; + face2 = &file->faces[abs( face2Num )]; + + if( !( face2->flags & FACE_FLOOR ) ) + { + continue; + } + + for( m = 0; m < face1->numEdges; m++ ) + { + edge1Num = abs( file->edgeIndex[face1->firstEdge + m] ); + for( n = 0; n < face2->numEdges; n++ ) + { + edge2Num = abs( file->edgeIndex[face2->firstEdge + n] ); + if( edge1Num == edge2Num ) + { + break; + } + } + if( n < face2->numEdges ) + { + break; + } + } + if( m < face1->numEdges ) + { + break; + } + } + if( l < otherArea->numFaces ) + { + break; + } + } + if( k < area->numFaces ) + { + // create reachability + reach = new idReachability_Walk(); + reach->travelType = TFL_WALK; + reach->toAreaNum = otherAreaNum; + reach->fromAreaNum = areaNum; + reach->edgeNum = abs( edge1Num ); + reach->travelTime = 1; + reach->start = file->EdgeCenter( edge1Num ); + if( faceNum < 0 ) + { + reach->end = reach->start + file->planeList[face->planeNum].Normal() * INSIDEUNITS_WALKEND; + } + else + { + reach->end = reach->start - file->planeList[face->planeNum].Normal() * INSIDEUNITS_WALKEND; + } + AddReachabilityToArea( reach, areaNum ); + } + } +} + +/* +================ +idAASReach::Reachability_Step_Barrier_WaterJump_WalkOffLedge +================ +*/ +bool idAASReach::Reachability_Step_Barrier_WaterJump_WalkOffLedge( int area1num, int area2num ) +{ + int i, j, k, l, edge1Num, edge2Num, areas[10]; + int floor_bestArea1FloorEdgeNum = 0, floor_bestArea2FloorEdgeNum, floor_foundReach; + int water_bestArea1FloorEdgeNum, water_bestArea2FloorEdgeNum, water_foundReach; + int side1, faceSide1, floorFace1Num; + float dist, dist1, dist2, diff, invGravityDot, orthogonalDot; + float x1, x2, x3, x4, y1, y2, y3, y4, tmp, y; + float length, floor_bestLength, water_bestLength, floor_bestDist, water_bestDist; + idVec3 v1, v2, v3, v4, tmpv, p1area1, p1area2, p2area1, p2area2; + idVec3 normal, orthogonal, edgeVec, start, end; + idVec3 floor_bestStart, floor_bestEnd, floor_bestNormal; + idVec3 water_bestStart, water_bestEnd, water_bestNormal; + idVec3 testPoint; + idPlane* plane; + aasArea_t* area1, *area2; + aasFace_t* floorFace1, *floorFace2, *floor_bestFace1, *water_bestFace1; + aasEdge_t* edge1, *edge2; + idReachability_Walk* walkReach; + idReachability_BarrierJump* barrierJumpReach; + idReachability_WaterJump* waterJumpReach; + idReachability_WalkOffLedge* walkOffLedgeReach; + aasTrace_t trace; + + // must be able to walk or swim in the first area + if( !AreaHasFloor( area1num ) && !CanSwimInArea( area1num ) ) + { + return false; + } + + if( !AreaHasFloor( area2num ) && !CanSwimInArea( area2num ) ) + { + return false; + } + + area1 = &file->areas[area1num]; + area2 = &file->areas[area2num]; + + // if the areas are not near anough in the x-y direction + for( i = 0; i < 2; i++ ) + { + if( area1->bounds[0][i] > area2->bounds[1][i] + 2.0f ) + { + return false; + } + if( area1->bounds[1][i] < area2->bounds[0][i] - 2.0f ) + { + return false; + } + } + + floor_foundReach = false; + floor_bestDist = 99999; + floor_bestLength = 0; + floor_bestArea2FloorEdgeNum = 0; + + water_foundReach = false; + water_bestDist = 99999; + water_bestLength = 0; + water_bestArea2FloorEdgeNum = 0; + + for( i = 0; i < area1->numFaces; i++ ) + { + floorFace1Num = file->faceIndex[area1->firstFace + i]; + faceSide1 = floorFace1Num < 0; + floorFace1 = &file->faces[abs( floorFace1Num )]; + + // if this isn't a floor face + if( !( floorFace1->flags & FACE_FLOOR ) ) + { + + // if we can swim in the first area + if( CanSwimInArea( area1num ) ) + { + + // face plane must be more or less horizontal + plane = &file->planeList[ floorFace1->planeNum ^ ( !faceSide1 ) ]; + if( plane->Normal() * file->settings.invGravityDir < file->settings.minFloorCos ) + { + continue; + } + } + else + { + // if we can't swim in the area it must be a ground face + continue; + } + } + + for( k = 0; k < floorFace1->numEdges; k++ ) + { + edge1Num = file->edgeIndex[floorFace1->firstEdge + k]; + side1 = ( edge1Num < 0 ); + // NOTE: for water faces we must take the side area 1 is on into + // account because the face is shared and doesn't have to be oriented correctly + if( !( floorFace1->flags & FACE_FLOOR ) ) + { + side1 = ( side1 == faceSide1 ); + } + edge1Num = abs( edge1Num ); + edge1 = &file->edges[edge1Num]; + // vertices of the edge + v1 = file->vertices[edge1->vertexNum[!side1]]; + v2 = file->vertices[edge1->vertexNum[side1]]; + // get a vertical plane through the edge + // NOTE: normal is pointing into area 2 because the face edges are stored counter clockwise + edgeVec = v2 - v1; + normal = edgeVec.Cross( file->settings.invGravityDir ); + normal.Normalize(); + dist = normal * v1; + + // check the faces from the second area + for( j = 0; j < area2->numFaces; j++ ) + { + floorFace2 = &file->faces[abs( file->faceIndex[area2->firstFace + j] )]; + // must be a ground face + if( !( floorFace2->flags & FACE_FLOOR ) ) + { + continue; + } + // check the edges of this ground face + for( l = 0; l < floorFace2->numEdges; l++ ) + { + edge2Num = abs( file->edgeIndex[floorFace2->firstEdge + l] ); + edge2 = &file->edges[edge2Num]; + // vertices of the edge + v3 = file->vertices[edge2->vertexNum[0]]; + v4 = file->vertices[edge2->vertexNum[1]]; + // check the distance between the two points and the vertical plane through the edge of area1 + diff = normal * v3 - dist; + if( diff < -0.2f || diff > 0.2f ) + { + continue; + } + diff = normal * v4 - dist; + if( diff < -0.2f || diff > 0.2f ) + { + continue; + } + + // project the two ground edges into the step side plane + // and calculate the shortest distance between the two + // edges if they overlap in the direction orthogonal to + // the gravity direction + orthogonal = file->settings.invGravityDir.Cross( normal ); + invGravityDot = file->settings.invGravityDir * file->settings.invGravityDir; + orthogonalDot = orthogonal * orthogonal; + // projection into the step plane + // NOTE: since gravity is vertical this is just the z coordinate + y1 = v1[2];//(v1 * file->settings.invGravity) / invGravityDot; + y2 = v2[2];//(v2 * file->settings.invGravity) / invGravityDot; + y3 = v3[2];//(v3 * file->settings.invGravity) / invGravityDot; + y4 = v4[2];//(v4 * file->settings.invGravity) / invGravityDot; + + x1 = ( v1 * orthogonal ) / orthogonalDot; + x2 = ( v2 * orthogonal ) / orthogonalDot; + x3 = ( v3 * orthogonal ) / orthogonalDot; + x4 = ( v4 * orthogonal ) / orthogonalDot; + + if( x1 > x2 ) + { + tmp = x1; + x1 = x2; + x2 = tmp; + tmp = y1; + y1 = y2; + y2 = tmp; + tmpv = v1; + v1 = v2; + v2 = tmpv; + } + if( x3 > x4 ) + { + tmp = x3; + x3 = x4; + x4 = tmp; + tmp = y3; + y3 = y4; + y4 = tmp; + tmpv = v3; + v3 = v4; + v4 = tmpv; + } + // if the two projected edge lines have no overlap + if( x2 <= x3 || x4 <= x1 ) + { + continue; + } + // if the two lines fully overlap + if( ( x1 - 0.5f < x3 && x4 < x2 + 0.5f ) && ( x3 - 0.5f < x1 && x2 < x4 + 0.5f ) ) + { + dist1 = y3 - y1; + dist2 = y4 - y2; + p1area1 = v1; + p2area1 = v2; + p1area2 = v3; + p2area2 = v4; + } + else + { + // if the points are equal + if( x1 > x3 - 0.1f && x1 < x3 + 0.1f ) + { + dist1 = y3 - y1; + p1area1 = v1; + p1area2 = v3; + } + else if( x1 < x3 ) + { + y = y1 + ( x3 - x1 ) * ( y2 - y1 ) / ( x2 - x1 ); + dist1 = y3 - y; + p1area1 = v3; + p1area1[2] = y; + p1area2 = v3; + } + else + { + y = y3 + ( x1 - x3 ) * ( y4 - y3 ) / ( x4 - x3 ); + dist1 = y - y1; + p1area1 = v1; + p1area2 = v1; + p1area2[2] = y; + } + // if the points are equal + if( x2 > x4 - 0.1f && x2 < x4 + 0.1f ) + { + dist2 = y4 - y2; + p2area1 = v2; + p2area2 = v4; + } + else if( x2 < x4 ) + { + y = y3 + ( x2 - x3 ) * ( y4 - y3 ) / ( x4 - x3 ); + dist2 = y - y2; + p2area1 = v2; + p2area2 = v2; + p2area2[2] = y; + } + else + { + y = y1 + ( x4 - x1 ) * ( y2 - y1 ) / ( x2 - x1 ); + dist2 = y4 - y; + p2area1 = v4; + p2area1[2] = y; + p2area2 = v4; + } + } + + // if both distances are pretty much equal then we take the middle of the points + if( dist1 > dist2 - 1.0f && dist1 < dist2 + 1.0f ) + { + dist = dist1; + start = ( p1area1 + p2area1 ) * 0.5f; + end = ( p1area2 + p2area2 ) * 0.5f; + } + else if( dist1 < dist2 ) + { + dist = dist1; + start = p1area1; + end = p1area2; + } + else + { + dist = dist2; + start = p2area1; + end = p2area2; + } + + // get the length of the overlapping part of the edges of the two areas + length = ( p2area2 - p1area2 ).Length(); + + if( floorFace1->flags & FACE_FLOOR ) + { + // if the vertical distance is smaller + if( dist < floor_bestDist || + // or the vertical distance is pretty much the same + // but the overlapping part of the edges is longer + ( dist < floor_bestDist + 1.0f && length > floor_bestLength ) ) + { + floor_bestDist = dist; + floor_bestLength = length; + floor_foundReach = true; + floor_bestArea1FloorEdgeNum = edge1Num; + floor_bestArea2FloorEdgeNum = edge2Num; + floor_bestFace1 = floorFace1; + floor_bestStart = start; + floor_bestNormal = normal; + floor_bestEnd = end; + } + } + else + { + // if the vertical distance is smaller + if( dist < water_bestDist || + //or the vertical distance is pretty much the same + //but the overlapping part of the edges is longer + ( dist < water_bestDist + 1.0f && length > water_bestLength ) ) + { + water_bestDist = dist; + water_bestLength = length; + water_foundReach = true; + water_bestArea1FloorEdgeNum = edge1Num; + water_bestArea2FloorEdgeNum = edge2Num; + water_bestFace1 = floorFace1; + water_bestStart = start; // best start point in area1 + water_bestNormal = normal; // normal is pointing into area2 + water_bestEnd = end; // best point towards area2 + } + } + } + } + } + } + // + // NOTE: swim reachabilities should already be filtered out + // + // Steps + // + // --------- + // | step height -> TFL_WALK + // --------| + // + // --------- + // ~~~~~~~~| step height and low water -> TFL_WALK + // --------| + // + // ~~~~~~~~~~~~~~~~~~ + // --------- + // | step height and low water up to the step -> TFL_WALK + // --------| + // + // check for a step reachability + if( floor_foundReach ) + { + // if area2 is higher but lower than the maximum step height + // NOTE: floor_bestDist >= 0 also catches equal floor reachabilities + if( floor_bestDist >= 0 && floor_bestDist < file->settings.maxStepHeight ) + { + // create walk reachability from area1 to area2 + walkReach = new idReachability_Walk(); + walkReach->travelType = TFL_WALK; + walkReach->toAreaNum = area2num; + walkReach->fromAreaNum = area1num; + walkReach->start = floor_bestStart + INSIDEUNITS_WALKSTART * floor_bestNormal; + walkReach->end = floor_bestEnd + INSIDEUNITS_WALKEND * floor_bestNormal; + walkReach->edgeNum = abs( floor_bestArea1FloorEdgeNum ); + walkReach->travelTime = 0; + if( area2->flags & AREA_CROUCH ) + { + walkReach->travelTime += file->settings.tt_startCrouching; + } + AddReachabilityToArea( walkReach, area1num ); + return true; + } + } + // + // Water Jumps + // + // --------- + // | + // ~~~~~~~~| + // | + // | higher than step height and water up to waterjump height -> TFL_WATERJUMP + // --------| + // + // ~~~~~~~~~~~~~~~~~~ + // --------- + // | + // | + // | + // | higher than step height and low water up to the step -> TFL_WATERJUMP + // --------| + // + // check for a waterjump reachability + if( water_foundReach ) + { + // get a test point a little bit towards area1 + testPoint = water_bestEnd - INSIDEUNITS * water_bestNormal; + // go down the maximum waterjump height + testPoint[2] -= file->settings.maxWaterJumpHeight; + // if there IS water the sv_maxwaterjump height below the bestend point + if( area1->flags & AREA_LIQUID ) + { + // don't create rediculous water jump reachabilities from areas very far below the water surface + if( water_bestDist < file->settings.maxWaterJumpHeight + 24 ) + { + // water jumping from or towards a crouch only areas is not possible + if( !( area1->flags & AREA_CROUCH ) && !( area2->flags & AREA_CROUCH ) ) + { + // create water jump reachability from area1 to area2 + waterJumpReach = new idReachability_WaterJump(); + waterJumpReach->travelType = TFL_WATERJUMP; + waterJumpReach->toAreaNum = area2num; + waterJumpReach->fromAreaNum = area1num; + waterJumpReach->start = water_bestStart; + waterJumpReach->end = water_bestEnd + INSIDEUNITS_WATERJUMP * water_bestNormal; + waterJumpReach->edgeNum = abs( floor_bestArea1FloorEdgeNum ); + waterJumpReach->travelTime = file->settings.tt_waterJump; + AddReachabilityToArea( waterJumpReach, area1num ); + return true; + } + } + } + } + // + // Barrier Jumps + // + // --------- + // | + // | + // | + // | higher than max step height lower than max barrier height -> TFL_BARRIERJUMP + // --------| + // + // --------- + // | + // | + // | + // ~~~~~~~~| higher than max step height lower than max barrier height + // --------| and a thin layer of water in the area to jump from -> TFL_BARRIERJUMP + // + // check for a barrier jump reachability + if( floor_foundReach ) + { + //if area2 is higher but lower than the maximum barrier jump height + if( floor_bestDist > 0 && floor_bestDist < file->settings.maxBarrierHeight ) + { + //if no water in area1 or a very thin layer of water on the ground + if( !water_foundReach || ( floor_bestDist - water_bestDist < 16 ) ) + { + // cannot perform a barrier jump towards or from a crouch area + if( !( area1->flags & AREA_CROUCH ) && !( area2->flags & AREA_CROUCH ) ) + { + // create barrier jump reachability from area1 to area2 + barrierJumpReach = new idReachability_BarrierJump(); + barrierJumpReach->travelType = TFL_BARRIERJUMP; + barrierJumpReach->toAreaNum = area2num; + barrierJumpReach->fromAreaNum = area1num; + barrierJumpReach->start = floor_bestStart + INSIDEUNITS_WALKSTART * floor_bestNormal; + barrierJumpReach->end = floor_bestEnd + INSIDEUNITS_WALKEND * floor_bestNormal; + barrierJumpReach->edgeNum = abs( floor_bestArea1FloorEdgeNum ); + barrierJumpReach->travelTime = file->settings.tt_barrierJump; + AddReachabilityToArea( barrierJumpReach, area1num ); + return true; + } + } + } + } + // + // Walk and Walk Off Ledge + // + // --------| + // | can walk or step back -> TFL_WALK + // --------- + // + // --------| + // | + // | + // | + // | cannot walk/step back -> TFL_WALKOFFLEDGE + // --------- + // + // --------| + // | + // |~~~~~~~~ + // | + // | cannot step back but can waterjump back -> TFL_WALKOFFLEDGE + // --------- FIXME: create TFL_WALK reach?? + // + // check for a walk or walk off ledge reachability + if( floor_foundReach ) + { + if( floor_bestDist < 0 ) + { + if( floor_bestDist > -file->settings.maxStepHeight ) + { + // create walk reachability from area1 to area2 + walkReach = new idReachability_Walk(); + walkReach->travelType = TFL_WALK; + walkReach->toAreaNum = area2num; + walkReach->fromAreaNum = area1num; + walkReach->start = floor_bestStart + INSIDEUNITS_WALKSTART * floor_bestNormal; + walkReach->end = floor_bestEnd + INSIDEUNITS_WALKEND * floor_bestNormal; + walkReach->edgeNum = abs( floor_bestArea1FloorEdgeNum ); + walkReach->travelTime = 1; + AddReachabilityToArea( walkReach, area1num ); + return true; + } + // if no maximum fall height set or less than the max + if( !file->settings.maxFallHeight || idMath::Fabs( floor_bestDist ) < file->settings.maxFallHeight ) + { + // trace a bounding box vertically to check for solids + floor_bestEnd += INSIDEUNITS * floor_bestNormal; + start = floor_bestEnd; + start[2] = floor_bestStart[2]; + end = floor_bestEnd; + end[2] += 4; + trace.areas = areas; + trace.maxAreas = sizeof( areas ) / sizeof( int ); + file->Trace( trace, start, end ); + // if the trace didn't start in solid and nothing was hit + if( trace.lastAreaNum && trace.fraction >= 1.0f ) + { + // the trace end point must be in the goal area + if( trace.lastAreaNum == area2num ) + { + // don't create reachability if going through a cluster portal + for( i = 0; i < trace.numAreas; i++ ) + { + if( AreaIsClusterPortal( trace.areas[i] ) ) + { + break; + } + } + if( i >= trace.numAreas ) + { + // create a walk off ledge reachability from area1 to area2 + walkOffLedgeReach = new idReachability_WalkOffLedge(); + walkOffLedgeReach->travelType = TFL_WALKOFFLEDGE; + walkOffLedgeReach->toAreaNum = area2num; + walkOffLedgeReach->fromAreaNum = area1num; + walkOffLedgeReach->start = floor_bestStart; + walkOffLedgeReach->end = floor_bestEnd; + walkOffLedgeReach->edgeNum = abs( floor_bestArea1FloorEdgeNum ); + walkOffLedgeReach->travelTime = file->settings.tt_startWalkOffLedge + idMath::Fabs( floor_bestDist ) * 50 / file->settings.gravityValue; + AddReachabilityToArea( walkOffLedgeReach, area1num ); + return true; + } + } + } + } + } + } + return false; +} + +/* +================ +idAASReach::Reachability_WalkOffLedge +================ +*/ +void idAASReach::Reachability_WalkOffLedge( int areaNum ) +{ + int i, j, faceNum, edgeNum, side, reachAreaNum, p, areas[10]; + aasArea_t* area; + aasFace_t* face; + aasEdge_t* edge; + idPlane* plane; + idVec3 v1, v2, mid, dir, testEnd; + idReachability_WalkOffLedge* reach; + aasTrace_t trace; + + if( !AreaHasFloor( areaNum ) || CanSwimInArea( areaNum ) ) + { + return; + } + + area = &file->areas[areaNum]; + + for( i = 0; i < area->numFaces; i++ ) + { + faceNum = file->faceIndex[area->firstFace + i]; + face = &file->faces[abs( faceNum )]; + + // face must be a floor face + if( !( face->flags & FACE_FLOOR ) ) + { + continue; + } + + for( j = 0; j < face->numEdges; j++ ) + { + + edgeNum = file->edgeIndex[face->firstEdge + j]; + edge = &file->edges[abs( edgeNum )]; + + //if ( !(edge->flags & EDGE_LEDGE) ) { + // continue; + //} + + side = edgeNum < 0; + + v1 = file->vertices[edge->vertexNum[side]]; + v2 = file->vertices[edge->vertexNum[!side]]; + + plane = &file->planeList[face->planeNum ^ INT32_SIGNBITSET( faceNum ) ]; + + // get the direction into the other area + dir = plane->Normal().Cross( v2 - v1 ); + dir.Normalize(); + + mid = ( v1 + v2 ) * 0.5f; + testEnd = mid + INSIDEUNITS_WALKEND * dir; + testEnd[2] -= file->settings.maxFallHeight + 1.0f; + trace.areas = areas; + trace.maxAreas = sizeof( areas ) / sizeof( int ); + file->Trace( trace, mid, testEnd ); + + reachAreaNum = trace.lastAreaNum; + if( !reachAreaNum || reachAreaNum == areaNum ) + { + continue; + } + if( idMath::Fabs( mid[2] - trace.endpos[2] ) > file->settings.maxFallHeight ) + { + continue; + } + if( !AreaHasFloor( reachAreaNum ) && !CanSwimInArea( reachAreaNum ) ) + { + continue; + } + if( ReachabilityExists( areaNum, reachAreaNum ) ) + { + continue; + } + // if not going through a cluster portal + for( p = 0; p < trace.numAreas; p++ ) + { + if( AreaIsClusterPortal( trace.areas[p] ) ) + { + break; + } + } + if( p < trace.numAreas ) + { + continue; + } + + reach = new idReachability_WalkOffLedge(); + reach->travelType = TFL_WALKOFFLEDGE; + reach->toAreaNum = reachAreaNum; + reach->fromAreaNum = areaNum; + reach->start = mid; + reach->end = trace.endpos; + reach->edgeNum = abs( edgeNum ); + reach->travelTime = file->settings.tt_startWalkOffLedge + idMath::Fabs( mid[2] - trace.endpos[2] ) * 50 / file->settings.gravityValue; + AddReachabilityToArea( reach, areaNum ); + } + } +} + +/* +================ +idAASReach::FlagReachableAreas +================ +*/ +void idAASReach::FlagReachableAreas( idAASFileLocal* file ) +{ + int i, numReachableAreas; + + numReachableAreas = 0; + for( i = 1; i < file->areas.Num(); i++ ) + { + + if( ( file->areas[i].flags & ( AREA_FLOOR | AREA_LADDER ) ) || + ( file->areas[i].contents & AREACONTENTS_WATER ) ) + { + file->areas[i].flags |= AREA_REACHABLE_WALK; + } + if( file->GetSettings().allowFlyReachabilities ) + { + file->areas[i].flags |= AREA_REACHABLE_FLY; + } + numReachableAreas++; + } + + common->Printf( "%6d reachable areas\n", numReachableAreas ); +} + +/* +================ +idAASReach::Build +================ +*/ +bool idAASReach::Build( const idMapFile* mapFile, idAASFileLocal* file ) +{ + int i, j, lastPercent, percent; + + this->mapFile = mapFile; + this->file = file; + numReachabilities = 0; + + common->Printf( "[Reachability]\n" ); + + // delete all existing reachabilities + file->DeleteReachabilities(); + + FlagReachableAreas( file ); + + for( i = 1; i < file->areas.Num(); i++ ) + { + if( !( file->areas[i].flags & AREA_REACHABLE_WALK ) ) + { + continue; + } + if( file->GetSettings().allowSwimReachabilities ) + { + Reachability_Swim( i ); + } + Reachability_EqualFloorHeight( i ); + } + + lastPercent = -1; + for( i = 1; i < file->areas.Num(); i++ ) + { + + if( !( file->areas[i].flags & AREA_REACHABLE_WALK ) ) + { + continue; + } + + for( j = 0; j < file->areas.Num(); j++ ) + { + if( i == j ) + { + continue; + } + + if( !( file->areas[j].flags & AREA_REACHABLE_WALK ) ) + { + continue; + } + + if( ReachabilityExists( i, j ) ) + { + continue; + } + if( Reachability_Step_Barrier_WaterJump_WalkOffLedge( i, j ) ) + { + continue; + } + } + + //Reachability_WalkOffLedge( i ); + + percent = 100 * i / file->areas.Num(); + if( percent > lastPercent ) + { + common->Printf( "\r%6d%%", percent ); + lastPercent = percent; + } + } + + if( file->GetSettings().allowFlyReachabilities ) + { + for( i = 1; i < file->areas.Num(); i++ ) + { + Reachability_Fly( i ); + } + } + + file->LinkReversedReachability(); + + common->Printf( "\r%6d reachabilities\n", numReachabilities ); + + return true; +} diff --git a/neo/tools/compilers/aas/AASReach.h b/neo/tools/compilers/aas/AASReach.h new file mode 100644 index 00000000..552f6bfe --- /dev/null +++ b/neo/tools/compilers/aas/AASReach.h @@ -0,0 +1,68 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __AASREACH_H__ +#define __AASREACH_H__ + +/* +=============================================================================== + + Reachabilities + +=============================================================================== +*/ + +class idAASReach +{ + +public: + bool Build( const idMapFile* mapFile, idAASFileLocal* file ); + +private: + const idMapFile* mapFile; + idAASFileLocal* file; + int numReachabilities; + bool allowSwimReachabilities; + bool allowFlyReachabilities; + +private: // reachability + void FlagReachableAreas( idAASFileLocal* file ); + bool ReachabilityExists( int fromAreaNum, int toAreaNum ); + bool CanSwimInArea( int areaNum ); + bool AreaHasFloor( int areaNum ); + bool AreaIsClusterPortal( int areaNum ); + void AddReachabilityToArea( idReachability* reach, int areaNum ); + void Reachability_Fly( int areaNum ); + void Reachability_Swim( int areaNum ); + void Reachability_EqualFloorHeight( int areaNum ); + bool Reachability_Step_Barrier_WaterJump_WalkOffLedge( int fromAreaNum, int toAreaNum ); + void Reachability_WalkOffLedge( int areaNum ); + +}; + +#endif /* !__AASREACH_H__ */ diff --git a/neo/tools/compilers/aas/Brush.cpp b/neo/tools/compilers/aas/Brush.cpp new file mode 100644 index 00000000..13a65b56 --- /dev/null +++ b/neo/tools/compilers/aas/Brush.cpp @@ -0,0 +1,1845 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "Brush.h" + +#define BRUSH_EPSILON 0.1f +#define BRUSH_PLANE_NORMAL_EPSILON 0.00001f +#define BRUSH_PLANE_DIST_EPSILON 0.01f + +#define OUTPUT_UPDATE_TIME 500 // update every 500 msec + +//#define OUTPUT_CHOP_STATS + +/* +============ +DisplayRealTimeString +============ +*/ +void DisplayRealTimeString( char* string, ... ) +{ + va_list argPtr; + char buf[MAX_STRING_CHARS]; + static int lastUpdateTime; + int time; + + time = Sys_Milliseconds(); + if( time > lastUpdateTime + OUTPUT_UPDATE_TIME ) + { + va_start( argPtr, string ); + vsprintf( buf, string, argPtr ); + va_end( argPtr ); + common->Printf( buf ); + lastUpdateTime = time; + } +} + + +//=============================================================== +// +// idBrushSide +// +//=============================================================== + +/* +============ +idBrushSide::idBrushSide +============ +*/ +idBrushSide::idBrushSide( void ) +{ + flags = 0; + planeNum = -1; + winding = NULL; +} + +/* +============ +idBrushSide::idBrushSide +============ +*/ +idBrushSide::idBrushSide( const idPlane& plane, int planeNum ) +{ + this->flags = 0; + this->plane = plane; + this->planeNum = planeNum; + this->winding = NULL; +} + +/* +============ +idBrushSide::~idBrushSide +============ +*/ +idBrushSide::~idBrushSide( void ) +{ + if( winding ) + { + delete winding; + } +} + +/* +============ +idBrushSide::Copy +============ +*/ +idBrushSide* idBrushSide::Copy( void ) const +{ + idBrushSide* side; + + side = new idBrushSide( plane, planeNum ); + side->flags = flags; + if( winding ) + { + side->winding = winding->Copy(); + } + else + { + side->winding = NULL; + } + return side; +} + +/* +============ +idBrushSide::Split +============ +*/ +int idBrushSide::Split( const idPlane& splitPlane, idBrushSide** front, idBrushSide** back ) const +{ + idWinding* frontWinding, *backWinding; + + assert( winding ); + + *front = *back = NULL; + + winding->Split( splitPlane, 0.0f, &frontWinding, &backWinding ); + + if( frontWinding ) + { + ( *front ) = new idBrushSide( plane, planeNum ); + ( *front )->winding = frontWinding; + ( *front )->flags = flags; + } + + if( backWinding ) + { + ( *back ) = new idBrushSide( plane, planeNum ); + ( *back )->winding = backWinding; + ( *back )->flags = flags; + } + + if( frontWinding && backWinding ) + { + return PLANESIDE_CROSS; + } + else if( frontWinding ) + { + return PLANESIDE_FRONT; + } + else + { + return PLANESIDE_BACK; + } +} + + +//=============================================================== +// +// idBrushSide +// +//=============================================================== + +/* +============ +idBrush::idBrush +============ +*/ +idBrush::idBrush( void ) +{ + contents = flags = 0; + bounds.Clear(); + sides.Clear(); + windingsValid = false; +} + + +/* +============ +idBrush::~idBrush +============ +*/ +idBrush::~idBrush( void ) +{ + for( int i = 0; i < sides.Num(); i++ ) + { + delete sides[i]; + } +} + +/* +============ +idBrush::RemoveSidesWithoutWinding +============ +*/ +bool idBrush::RemoveSidesWithoutWinding( void ) +{ + int i; + + for( i = 0; i < sides.Num(); i++ ) + { + + if( sides[i]->winding ) + { + continue; + } + + sides.RemoveIndex( i ); + i--; + } + + return ( sides.Num() >= 4 ); +} + +/* +============ +idBrush::CreateWindings +============ +*/ +bool idBrush::CreateWindings( void ) +{ + int i, j; + idBrushSide* side; + + bounds.Clear(); + for( i = 0; i < sides.Num(); i++ ) + { + side = sides[i]; + + if( side->winding ) + { + delete side->winding; + } + + side->winding = new idWinding( side->plane.Normal(), side->plane.Dist() ); + + for( j = 0; j < sides.Num() && side->winding; j++ ) + { + if( i == j ) + { + continue; + } + // keep the winding if on the clip plane + side->winding = side->winding->Clip( -sides[j]->plane, BRUSH_EPSILON, true ); + } + + if( side->winding ) + { + for( j = 0; j < side->winding->GetNumPoints(); j++ ) + { + bounds.AddPoint( ( *side->winding )[j].ToVec3() ); + } + } + } + + if( bounds[0][0] > bounds[1][0] ) + { + return false; + } + for( i = 0; i < 3; i++ ) + { + if( bounds[0][i] < MIN_WORLD_COORD || bounds[1][i] > MAX_WORLD_COORD ) + { + return false; + } + } + + windingsValid = true; + + return true; +} + +/* +============ +idBrush::BoundBrush +============ +*/ +void idBrush::BoundBrush( const idBrush* original ) +{ + int i, j; + idBrushSide* side; + idWinding* w; + + assert( windingsValid ); + + bounds.Clear(); + for( i = 0; i < sides.Num(); i++ ) + { + side = sides[i]; + + w = side->winding; + + if( !w ) + { + continue; + } + + for( j = 0; j < w->GetNumPoints(); j++ ) + { + bounds.AddPoint( ( *w )[j].ToVec3() ); + } + } + + if( bounds[0][0] > bounds[1][0] ) + { + if( original ) + { + idBrushMap* bm = new idBrushMap( "error_brush", "_original" ); + bm->WriteBrush( original ); + delete bm; + } + common->Error( "idBrush::BoundBrush: brush %d on entity %d without windings", primitiveNum, entityNum ); + } + + for( i = 0; i < 3; i++ ) + { + if( bounds[0][i] < MIN_WORLD_COORD || bounds[1][i] > MAX_WORLD_COORD ) + { + if( original ) + { + idBrushMap* bm = new idBrushMap( "error_brush", "_original" ); + bm->WriteBrush( original ); + delete bm; + } + common->Error( "idBrush::BoundBrush: brush %d on entity %d is unbounded", primitiveNum, entityNum ); + } + } +} + +/* +============ +idBrush::FromSides +============ +*/ +bool idBrush::FromSides( idList& sideList ) +{ + int i; + + for( i = 0; i < sideList.Num(); i++ ) + { + sides.Append( sideList[i] ); + } + + sideList.Clear(); + + return CreateWindings(); +} + +/* +============ +idBrush::FromWinding +============ +*/ +bool idBrush::FromWinding( const idWinding& w, const idPlane& windingPlane ) +{ + int i, j, bestAxis; + idPlane plane; + idVec3 normal, axialNormal; + + sides.Append( new idBrushSide( windingPlane, -1 ) ); + sides.Append( new idBrushSide( -windingPlane, -1 ) ); + + bestAxis = 0; + for( i = 1; i < 3; i++ ) + { + if( idMath::Fabs( windingPlane.Normal()[i] ) > idMath::Fabs( windingPlane.Normal()[bestAxis] ) ) + { + bestAxis = i; + } + } + axialNormal = vec3_origin; + if( windingPlane.Normal()[bestAxis] > 0.0f ) + { + axialNormal[bestAxis] = 1.0f; + } + else + { + axialNormal[bestAxis] = -1.0f; + } + + for( i = 0; i < w.GetNumPoints(); i++ ) + { + j = ( i + 1 ) % w.GetNumPoints(); + normal = ( w[j].ToVec3() - w[i].ToVec3() ).Cross( axialNormal ); + if( normal.Normalize() < 0.5f ) + { + continue; + } + plane.SetNormal( normal ); + plane.FitThroughPoint( w[j].ToVec3() ); + sides.Append( new idBrushSide( plane, -1 ) ); + } + + if( sides.Num() < 4 ) + { + for( i = 0; i < sides.Num(); i++ ) + { + delete sides[i]; + } + sides.Clear(); + return false; + } + + sides[0]->winding = w.Copy(); + windingsValid = true; + BoundBrush(); + + return true; +} + +/* +============ +idBrush::FromBounds +============ +*/ +bool idBrush::FromBounds( const idBounds& bounds ) +{ + int axis, dir; + idVec3 normal; + idPlane plane; + + for( axis = 0; axis < 3; axis++ ) + { + for( dir = -1; dir <= 1; dir += 2 ) + { + normal = vec3_origin; + normal[axis] = dir; + plane.SetNormal( normal ); + plane.SetDist( dir * bounds[( dir == 1 )][axis] ); + sides.Append( new idBrushSide( plane, -1 ) ); + } + } + + return CreateWindings(); +} + +/* +============ +idBrush::Transform +============ +*/ +void idBrush::Transform( const idVec3& origin, const idMat3& axis ) +{ + int i; + bool transformed = false; + + if( axis.IsRotated() ) + { + for( i = 0; i < sides.Num(); i++ ) + { + sides[i]->plane.RotateSelf( vec3_origin, axis ); + } + transformed = true; + } + if( origin != vec3_origin ) + { + for( i = 0; i < sides.Num(); i++ ) + { + sides[i]->plane.TranslateSelf( origin ); + } + transformed = true; + } + if( transformed ) + { + CreateWindings(); + } +} + +/* +============ +idBrush::GetVolume +============ +*/ +float idBrush::GetVolume( void ) const +{ + int i; + idWinding* w; + idVec3 corner; + float d, area, volume; + + // grab the first valid point as a corner + w = NULL; + for( i = 0; i < sides.Num(); i++ ) + { + w = sides[i]->winding; + if( w ) + { + break; + } + } + if( !w ) + { + return 0.0f; + } + corner = ( *w )[0].ToVec3(); + + // create tetrahedrons to all other sides + volume = 0.0f; + for( ; i < sides.Num(); i++ ) + { + w = sides[i]->winding; + if( !w ) + { + continue; + } + d = -( corner * sides[i]->plane.Normal() - sides[i]->plane.Dist() ); + area = w->GetArea(); + volume += d * area; + } + + return ( volume * ( 1.0f / 3.0f ) ); +} + +/* +============ +idBrush::Subtract +============ +*/ +bool idBrush::Subtract( const idBrush* b, idBrushList& list ) const +{ + int i; + idBrush* front, *back; + const idBrush* in; + + list.Clear(); + in = this; + for( i = 0; i < b->sides.Num() && in; i++ ) + { + + in->Split( b->sides[i]->plane, b->sides[i]->planeNum, &front, &back ); + + if( in != this ) + { + delete in; + } + if( front ) + { + list.AddToTail( front ); + } + in = back; + } + // if didn't really intersect + if( !in ) + { + list.Free(); + return false; + } + + delete in; + return true; +} + +/* +============ +idBrush::TryMerge +============ +*/ +bool idBrush::TryMerge( const idBrush* brush, const idPlaneSet& planeList ) +{ + int i, j, k, l, m, seperatingPlane; + const idBrush* brushes[2]; + const idWinding* w; + const idPlane* plane; + + // brush bounds should overlap + for( i = 0; i < 3; i++ ) + { + if( bounds[0][i] > brush->bounds[1][i] + 0.1f ) + { + return false; + } + if( bounds[1][i] < brush->bounds[0][i] - 0.1f ) + { + return false; + } + } + + // the brushes should share an opposite plane + seperatingPlane = -1; + for( i = 0; i < GetNumSides(); i++ ) + { + for( j = 0; j < brush->GetNumSides(); j++ ) + { + if( GetSide( i )->GetPlaneNum() == ( brush->GetSide( j )->GetPlaneNum() ^ 1 ) ) + { + // may only have one seperating plane + if( seperatingPlane != -1 ) + { + return false; + } + seperatingPlane = GetSide( i )->GetPlaneNum(); + break; + } + } + } + if( seperatingPlane == -1 ) + { + return false; + } + + brushes[0] = this; + brushes[1] = brush; + + for( i = 0; i < 2; i++ ) + { + + j = !i; + + for( k = 0; k < brushes[i]->GetNumSides(); k++ ) + { + + // if the brush side plane is the seprating plane + if( !( ( brushes[i]->GetSide( k )->GetPlaneNum() ^ seperatingPlane ) >> 1 ) ) + { + continue; + } + + plane = &brushes[i]->GetSide( k )->GetPlane(); + + // all the non seperating brush sides of the other brush should be at the back or on the plane + for( l = 0; l < brushes[j]->GetNumSides(); l++ ) + { + + w = brushes[j]->GetSide( l )->GetWinding(); + if( !w ) + { + continue; + } + + if( !( ( brushes[j]->GetSide( l )->GetPlaneNum() ^ seperatingPlane ) >> 1 ) ) + { + continue; + } + + for( m = 0; m < w->GetNumPoints(); m++ ) + { + if( plane->Distance( ( *w )[m].ToVec3() ) > 0.1f ) + { + return false; + } + } + } + } + } + + // add any sides from the other brush to this brush + for( i = 0; i < brush->GetNumSides(); i++ ) + { + for( j = 0; j < GetNumSides(); j++ ) + { + if( !( ( brush->GetSide( i )->GetPlaneNum() ^ GetSide( j )->GetPlaneNum() ) >> 1 ) ) + { + break; + } + } + if( j < GetNumSides() ) + { + sides[j]->flags &= brush->GetSide( i )->GetFlags(); + continue; + } + sides.Append( brush->GetSide( i )->Copy() ); + } + + // remove any side from this brush that is the opposite of a side of the other brush + for( i = 0; i < GetNumSides(); i++ ) + { + for( j = 0; j < brush->GetNumSides(); j++ ) + { + if( GetSide( i )->GetPlaneNum() == ( brush->GetSide( j )->GetPlaneNum() ^ 1 ) ) + { + break; + } + } + if( j < brush->GetNumSides() ) + { + delete sides[i]; + sides.RemoveIndex( i ); + i--; + continue; + } + } + + contents |= brush->contents; + + CreateWindings(); + BoundBrush(); + + return true; +} + +/* +============ +idBrush::Split +============ +*/ +int idBrush::Split( const idPlane& plane, int planeNum, idBrush** front, idBrush** back ) const +{ + int res, i, j; + idBrushSide* side, *frontSide, *backSide; + float dist, maxBack, maxFront, *maxBackWinding, *maxFrontWinding; + idWinding* w, *mid; + + assert( windingsValid ); + + if( front ) + { + *front = NULL; + } + if( back ) + { + *back = NULL; + } + + res = bounds.PlaneSide( plane, -BRUSH_EPSILON ); + if( res == PLANESIDE_FRONT ) + { + if( front ) + { + *front = Copy(); + } + return res; + } + if( res == PLANESIDE_BACK ) + { + if( back ) + { + *back = Copy(); + } + return res; + } + + maxBackWinding = ( float* ) _alloca16( sides.Num() * sizeof( float ) ); + maxFrontWinding = ( float* ) _alloca16( sides.Num() * sizeof( float ) ); + + maxFront = maxBack = 0.0f; + for( i = 0; i < sides.Num(); i++ ) + { + side = sides[i]; + + w = side->winding; + + if( !w ) + { + continue; + } + + maxBackWinding[i] = 10.0f; + maxFrontWinding[i] = -10.0f; + + for( j = 0; j < w->GetNumPoints(); j++ ) + { + + dist = plane.Distance( ( *w )[j].ToVec3() ); + if( dist > maxFrontWinding[i] ) + { + maxFrontWinding[i] = dist; + } + if( dist < maxBackWinding[i] ) + { + maxBackWinding[i] = dist; + } + } + + if( maxFrontWinding[i] > maxFront ) + { + maxFront = maxFrontWinding[i]; + } + if( maxBackWinding[i] < maxBack ) + { + maxBack = maxBackWinding[i]; + } + } + + if( maxFront < BRUSH_EPSILON ) + { + if( back ) + { + *back = Copy(); + } + return PLANESIDE_BACK; + } + + if( maxBack > -BRUSH_EPSILON ) + { + if( front ) + { + *front = Copy(); + } + return PLANESIDE_FRONT; + } + + mid = new idWinding( plane.Normal(), plane.Dist() ); + + for( i = 0; i < sides.Num() && mid; i++ ) + { + mid = mid->Clip( -sides[i]->plane, BRUSH_EPSILON, false ); + } + + if( mid ) + { + if( mid->IsTiny() ) + { + delete mid; + mid = NULL; + } + else if( mid->IsHuge() ) + { + // if the winding is huge then the brush is unbounded + common->Warning( "brush %d on entity %d is unbounded" + "( %1.2f %1.2f %1.2f )-( %1.2f %1.2f %1.2f )-( %1.2f %1.2f %1.2f )", primitiveNum, entityNum, + bounds[0][0], bounds[0][1], bounds[0][2], bounds[1][0], bounds[1][1], bounds[1][2], + bounds[1][0] - bounds[0][0], bounds[1][1] - bounds[0][1], bounds[1][2] - bounds[0][2] ); + delete mid; + mid = NULL; + } + } + + if( !mid ) + { + if( maxFront > - maxBack ) + { + if( front ) + { + *front = Copy(); + } + return PLANESIDE_FRONT; + } + else + { + if( back ) + { + *back = Copy(); + } + return PLANESIDE_BACK; + } + } + + if( !front && !back ) + { + delete mid; + return PLANESIDE_CROSS; + } + + *front = new idBrush(); + ( *front )->SetContents( contents ); + ( *front )->SetEntityNum( entityNum ); + ( *front )->SetPrimitiveNum( primitiveNum ); + *back = new idBrush(); + ( *back )->SetContents( contents ); + ( *back )->SetEntityNum( entityNum ); + ( *back )->SetPrimitiveNum( primitiveNum ); + + for( i = 0; i < sides.Num(); i++ ) + { + side = sides[i]; + + if( !side->winding ) + { + continue; + } + + // if completely at the front + if( maxBackWinding[i] >= BRUSH_EPSILON ) + { + ( *front )->sides.Append( side->Copy() ); + } + // if completely at the back + else if( maxFrontWinding[i] <= -BRUSH_EPSILON ) + { + ( *back )->sides.Append( side->Copy() ); + } + else + { + // split the side + side->Split( plane, &frontSide, &backSide ); + if( frontSide ) + { + ( *front )->sides.Append( frontSide ); + } + else if( maxFrontWinding[i] > -BRUSH_EPSILON ) + { + // favor an overconstrained brush + side = side->Copy(); + side->winding = side->winding->Clip( idPlane( plane.Normal(), ( plane.Dist() - ( BRUSH_EPSILON + 0.02f ) ) ), 0.01f, true ); + assert( side->winding ); + ( *front )->sides.Append( side ); + } + if( backSide ) + { + ( *back )->sides.Append( backSide ); + } + else if( maxBackWinding[i] < BRUSH_EPSILON ) + { + // favor an overconstrained brush + side = side->Copy(); + side->winding = side->winding->Clip( idPlane( -plane.Normal(), -( plane.Dist() + ( BRUSH_EPSILON + 0.02f ) ) ), 0.01f, true ); + assert( side->winding ); + ( *back )->sides.Append( side ); + } + } + } + + side = new idBrushSide( -plane, planeNum ^ 1 ); + side->winding = mid->Reverse(); + side->flags |= SFL_SPLIT; + ( *front )->sides.Append( side ); + ( *front )->windingsValid = true; + ( *front )->BoundBrush( this ); + + side = new idBrushSide( plane, planeNum ); + side->winding = mid; + side->flags |= SFL_SPLIT; + ( *back )->sides.Append( side ); + ( *back )->windingsValid = true; + ( *back )->BoundBrush( this ); + + return PLANESIDE_CROSS; +} + +/* +============ +idBrush::AddBevelsForAxialBox +============ +*/ +#define BRUSH_BEVEL_EPSILON 0.1f + +void idBrush::AddBevelsForAxialBox( void ) +{ + int axis, dir, i, j, k, l, order; + idBrushSide* side, *newSide; + idPlane plane; + idVec3 normal, vec; + idWinding* w, *w2; + float d, minBack; + + assert( windingsValid ); + + // 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 already present + for( i = 0; i < sides.Num(); i++ ) + { + if( dir > 0 ) + { + if( sides[i]->plane.Normal()[axis] >= 0.9999f ) + { + break; + } + } + else + { + if( sides[i]->plane.Normal()[axis] <= -0.9999f ) + { + break; + } + } + } + + if( i >= sides.Num() ) + { + normal = vec3_origin; + normal[axis] = dir; + plane.SetNormal( normal ); + plane.SetDist( dir * bounds[( dir == 1 )][axis] ); + newSide = new idBrushSide( plane, -1 ); + newSide->SetFlag( SFL_BEVEL ); + sides.Append( newSide ); + } + } + } + + // if the brush is pure axial we're done + if( sides.Num() == 6 ) + { + return; + } + + // test the non-axial plane edges + for( i = 0; i < sides.Num(); i++ ) + { + side = sides[i]; + w = side->winding; + if( !w ) + { + continue; + } + + for( j = 0; j < w->GetNumPoints(); j++ ) + { + k = ( j + 1 ) % w->GetNumPoints(); + vec = ( *w )[j].ToVec3() - ( *w )[k].ToVec3(); + if( vec.Normalize() < 0.5f ) + { + continue; + } + for( k = 0; k < 3; k++ ) + { + if( vec[k] == 1.0f || vec[k] == -1.0f || ( vec[k] == 0.0f && vec[( k + 1 ) % 3] == 0.0f ) ) + { + 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 + normal = vec3_origin; + normal[axis] = dir; + normal = vec.Cross( normal ); + if( normal.Normalize() < 0.5f ) + { + continue; + } + plane.SetNormal( normal ); + plane.FitThroughPoint( ( *w )[j].ToVec3() ); + + // if all the points on all the sides are + // behind this plane, it is a proper edge bevel + for( k = 0; k < sides.Num(); k++ ) + { + + // if this plane has allready been used, skip it + if( plane.Compare( sides[k]->plane, 0.001f, 0.1f ) ) + { + break; + } + + w2 = sides[k]->winding; + if( !w2 ) + { + continue; + } + minBack = 0.0f; + for( l = 0; l < w2->GetNumPoints(); l++ ) + { + d = plane.Distance( ( *w2 )[l].ToVec3() ); + if( d > BRUSH_BEVEL_EPSILON ) + { + break; // point at the front + } + if( d < minBack ) + { + minBack = d; + } + } + // if some point was at the front + if( l < w2->GetNumPoints() ) + { + break; + } + // if no points at the back then the winding is on the bevel plane + if( minBack > -BRUSH_BEVEL_EPSILON ) + { + break; + } + } + + if( k < sides.Num() ) + { + continue; // wasn't part of the outer hull + } + + // add this plane + newSide = new idBrushSide( plane, -1 ); + newSide->SetFlag( SFL_BEVEL ); + sides.Append( newSide ); + } + } + } + } +} + +/* +============ +idBrush::ExpandForAxialBox +============ +*/ +void idBrush::ExpandForAxialBox( const idBounds& bounds ) +{ + int i, j; + idBrushSide* side; + idVec3 v; + + AddBevelsForAxialBox(); + + for( i = 0; i < sides.Num(); i++ ) + { + side = sides[i]; + + for( j = 0; j < 3; j++ ) + { + if( side->plane.Normal()[j] > 0.0f ) + { + v[j] = bounds[0][j]; + } + else + { + v[j] = bounds[1][j]; + } + } + + side->plane.SetDist( side->plane.Dist() + v * -side->plane.Normal() ); + } + + if( !CreateWindings() ) + { + common->Error( "idBrush::ExpandForAxialBox: brush %d on entity %d imploded", primitiveNum, entityNum ); + } + + /* + // after expansion at least all non bevel sides should have a winding + for ( i = 0; i < sides.Num(); i++ ) { + side = sides[i]; + if ( !side->winding ) { + if ( !( side->flags & SFL_BEVEL ) ) { + int shit = 1; + } + } + } + */ +} + +/* +============ +idBrush::Copy +============ +*/ +idBrush* idBrush::Copy( void ) const +{ + int i; + idBrush* b; + + b = new idBrush(); + b->entityNum = entityNum; + b->primitiveNum = primitiveNum; + b->contents = contents; + b->windingsValid = windingsValid; + b->bounds = bounds; + for( i = 0; i < sides.Num(); i++ ) + { + b->sides.Append( sides[i]->Copy() ); + } + return b; +} + + +//=============================================================== +// +// idBrushList +// +//=============================================================== + +/* +============ +idBrushList::idBrushList +============ +*/ +idBrushList::idBrushList( void ) +{ + numBrushes = numBrushSides = 0; + head = tail = NULL; +} + +/* +============ +idBrushList::~idBrushList +============ +*/ +idBrushList::~idBrushList( void ) +{ +} + +/* +============ +idBrushList::GetBounds +============ +*/ +idBounds idBrushList::GetBounds( void ) const +{ + idBounds bounds; + idBrush* b; + + bounds.Clear(); + for( b = Head(); b; b = b->Next() ) + { + bounds += b->GetBounds(); + } + return bounds; +} + +/* +============ +idBrushList::AddToTail +============ +*/ +void idBrushList::AddToTail( idBrush* brush ) +{ + brush->next = NULL; + if( tail ) + { + tail->next = brush; + } + tail = brush; + if( !head ) + { + head = brush; + } + numBrushes++; + numBrushSides += brush->sides.Num(); +} + +/* +============ +idBrushList::AddToTail +============ +*/ +void idBrushList::AddToTail( idBrushList& list ) +{ + idBrush* brush, *next; + + for( brush = list.head; brush; brush = next ) + { + next = brush->next; + brush->next = NULL; + if( tail ) + { + tail->next = brush; + } + tail = brush; + if( !head ) + { + head = brush; + } + numBrushes++; + numBrushSides += brush->sides.Num(); + } + list.head = list.tail = NULL; + list.numBrushes = 0; +} + +/* +============ +idBrushList::AddToFront +============ +*/ +void idBrushList::AddToFront( idBrush* brush ) +{ + brush->next = head; + head = brush; + if( !tail ) + { + tail = brush; + } + numBrushes++; + numBrushSides += brush->sides.Num(); +} + +/* +============ +idBrushList::AddToFront +============ +*/ +void idBrushList::AddToFront( idBrushList& list ) +{ + idBrush* brush, *next; + + for( brush = list.head; brush; brush = next ) + { + next = brush->next; + brush->next = head; + head = brush; + if( !tail ) + { + tail = brush; + } + numBrushes++; + numBrushSides += brush->sides.Num(); + } + list.head = list.tail = NULL; + list.numBrushes = 0; +} + +/* +============ +idBrushList::Remove +============ +*/ +void idBrushList::Remove( idBrush* brush ) +{ + idBrush* b, *last; + + last = NULL; + for( b = head; b; b = b->next ) + { + if( b == brush ) + { + if( last ) + { + last->next = b->next; + } + else + { + head = b->next; + } + if( b == tail ) + { + tail = last; + } + numBrushes--; + numBrushSides -= brush->sides.Num(); + return; + } + last = b; + } +} + +/* +============ +idBrushList::Delete +============ +*/ +void idBrushList::Delete( idBrush* brush ) +{ + idBrush* b, *last; + + last = NULL; + for( b = head; b; b = b->next ) + { + if( b == brush ) + { + if( last ) + { + last->next = b->next; + } + else + { + head = b->next; + } + if( b == tail ) + { + tail = last; + } + numBrushes--; + numBrushSides -= b->sides.Num(); + delete b; + return; + } + last = b; + } +} + +/* +============ +idBrushList::Copy +============ +*/ +idBrushList* idBrushList::Copy( void ) const +{ + idBrush* brush; + idBrushList* list; + + list = new idBrushList; + + for( brush = head; brush; brush = brush->next ) + { + list->AddToTail( brush->Copy() ); + } + return list; +} + +/* +============ +idBrushList::Free +============ +*/ +void idBrushList::Free( void ) +{ + idBrush* brush, *next; + + for( brush = head; brush; brush = next ) + { + next = brush->next; + delete brush; + } + head = tail = NULL; + numBrushes = numBrushSides = 0; +} + +/* +============ +idBrushList::Split +============ +*/ +void idBrushList::Split( const idPlane& plane, int planeNum, idBrushList& frontList, idBrushList& backList, bool useBrushSavedPlaneSide ) +{ + idBrush* b, *front, *back; + + frontList.Clear(); + backList.Clear(); + + if( !useBrushSavedPlaneSide ) + { + for( b = head; b; b = b->next ) + { + b->Split( plane, planeNum, &front, &back ); + if( front ) + { + frontList.AddToTail( front ); + } + if( back ) + { + backList.AddToTail( back ); + } + } + return; + } + + for( b = head; b; b = b->next ) + { + if( b->savedPlaneSide & BRUSH_PLANESIDE_BOTH ) + { + b->Split( plane, planeNum, &front, &back ); + if( front ) + { + frontList.AddToTail( front ); + } + if( back ) + { + backList.AddToTail( back ); + } + } + else if( b->savedPlaneSide & BRUSH_PLANESIDE_FRONT ) + { + frontList.AddToTail( b->Copy() ); + } + else + { + backList.AddToTail( b->Copy() ); + } + } +} + +/* +============ +idBrushList::Chop +============ +*/ +void idBrushList::Chop( bool ( *ChopAllowed )( idBrush* b1, idBrush* b2 ) ) +{ + idBrush* b1, *b2, *next; + idBrushList sub1, sub2, keep; + int i, j, c1, c2; + idPlaneSet planeList; + +#ifdef OUTPUT_CHOP_STATS + common->Printf( "[Brush CSG]\n" ); + common->Printf( "%6d original brushes\n", this->Num() ); +#endif + + CreatePlaneList( planeList ); + + for( b1 = this->Head(); b1; b1 = this->Head() ) + { + + for( b2 = b1->next; b2; b2 = next ) + { + + next = b2->next; + + for( i = 0; i < 3; i++ ) + { + if( b1->bounds[0][i] >= b2->bounds[1][i] ) + { + break; + } + if( b1->bounds[1][i] <= b2->bounds[0][i] ) + { + break; + } + } + if( i < 3 ) + { + continue; + } + + for( i = 0; i < b1->GetNumSides(); i++ ) + { + for( j = 0; j < b2->GetNumSides(); j++ ) + { + if( b1->GetSide( i )->GetPlaneNum() == ( b2->GetSide( j )->GetPlaneNum() ^ 1 ) ) + { + // opposite planes, so not touching + break; + } + } + if( j < b2->GetNumSides() ) + { + break; + } + } + if( i < b1->GetNumSides() ) + { + continue; + } + + sub1.Clear(); + sub2.Clear(); + + c1 = 999999; + c2 = 999999; + + // if b2 may chop up b1 + if( !ChopAllowed || ChopAllowed( b2, b1 ) ) + { + if( !b1->Subtract( b2, sub1 ) ) + { + // didn't really intersect + continue; + } + if( sub1.IsEmpty() ) + { + // b1 is swallowed by b2 + this->Delete( b1 ); + break; + } + c1 = sub1.Num(); + } + + // if b1 may chop up b2 + if( !ChopAllowed || ChopAllowed( b1, b2 ) ) + { + if( !b2->Subtract( b1, sub2 ) ) + { + // didn't really intersect + continue; + } + if( sub2.IsEmpty() ) + { + // b2 is swallowed by b1 + sub1.Free(); + this->Delete( b2 ); + continue; + } + c2 = sub2.Num(); + } + + if( sub1.IsEmpty() && sub2.IsEmpty() ) + { + continue; + } + + // don't allow too much fragmentation + if( c1 > 2 && c2 > 2 ) + { + sub1.Free(); + sub2.Free(); + continue; + } + + if( c1 < c2 ) + { + sub2.Free(); + this->AddToTail( sub1 ); + this->Delete( b1 ); + break; + } + else + { + sub1.Free(); + this->AddToTail( sub2 ); + this->Delete( b2 ); + continue; + } + } + + if( !b2 ) + { + // b1 is no longer intersecting anything, so keep it + this->Remove( b1 ); + keep.AddToTail( b1 ); +#ifdef OUTPUT_CHOP_STATS + DisplayRealTimeString( "\r%6d", keep.numBrushes ); +#endif + } + } + + *this = keep; + +#ifdef OUTPUT_CHOP_STATS + common->Printf( "\r%6d output brushes\n", Num() ); +#endif +} + + +/* +============ +idBrushList::Merge +============ +*/ +void idBrushList::Merge( bool ( *MergeAllowed )( idBrush* b1, idBrush* b2 ) ) +{ + idPlaneSet planeList; + idBrush* b1, *b2, *nextb2; + int numMerges; + + common->Printf( "[Brush Merge]\n" ); + common->Printf( "%6d original brushes\n", Num() ); + + CreatePlaneList( planeList ); + + numMerges = 0; + for( b1 = Head(); b1; b1 = b1->next ) + { + + for( b2 = Head(); b2; b2 = nextb2 ) + { + nextb2 = b2->Next(); + + if( b2 == b1 ) + { + continue; + } + + if( MergeAllowed && !MergeAllowed( b1, b2 ) ) + { + continue; + } + + if( b1->TryMerge( b2, planeList ) ) + { + Delete( b2 ); + DisplayRealTimeString( "\r%6d", ++numMerges ); + nextb2 = Head(); + } + } + } + + common->Printf( "\r%6d brushes merged\n", numMerges ); +} + +/* +============ +idBrushList::SetFlagOnFacingBrushSides +============ +*/ +void idBrushList::SetFlagOnFacingBrushSides( const idPlane& plane, int flag ) +{ + int i; + idBrush* b; + const idWinding* w; + + for( b = head; b; b = b->next ) + { + if( idMath::Fabs( b->GetBounds().PlaneDistance( plane ) ) > 0.1f ) + { + continue; + } + for( i = 0; i < b->GetNumSides(); i++ ) + { + w = b->GetSide( i )->GetWinding(); + if( !w ) + { + if( b->GetSide( i )->GetPlane().Compare( plane, BRUSH_PLANE_NORMAL_EPSILON, BRUSH_PLANE_DIST_EPSILON ) ) + { + b->GetSide( i )->SetFlag( flag ); + } + continue; + } + if( w->PlaneSide( plane ) == SIDE_ON ) + { + b->GetSide( i )->SetFlag( flag ); + } + } + } +} + +/* +============ +idBrushList::CreatePlaneList +============ +*/ +void idBrushList::CreatePlaneList( idPlaneSet& planeList ) const +{ + int i; + idBrush* b; + idBrushSide* side; + + planeList.Resize( 512, 128 ); + for( b = Head(); b; b = b->Next() ) + { + for( i = 0; i < b->GetNumSides(); i++ ) + { + side = b->GetSide( i ); + side->SetPlaneNum( planeList.FindPlane( side->GetPlane(), BRUSH_PLANE_NORMAL_EPSILON, BRUSH_PLANE_DIST_EPSILON ) ); + } + } +} + +/* +============ +idBrushList::CreatePlaneList +============ +*/ +void idBrushList::WriteBrushMap( const idStr& fileName, const idStr& ext ) const +{ + idBrushMap* map; + + map = new idBrushMap( fileName, ext ); + map->WriteBrushList( *this ); + delete map; +} + + +//=============================================================== +// +// idBrushMap +// +//=============================================================== + +/* +============ +idBrushMap::idBrushMap +============ +*/ +idBrushMap::idBrushMap( const idStr& fileName, const idStr& ext ) +{ + idStr qpath; + + qpath = fileName; + qpath.StripFileExtension(); + qpath += ext; + qpath.SetFileExtension( "map" ); + + common->Printf( "writing %s...\n", qpath.c_str() ); + + fp = fileSystem->OpenFileWrite( qpath, "fs_devpath" ); + if( !fp ) + { + common->Error( "Couldn't open %s\n", qpath.c_str() ); + return; + } + + texture = "textures/washroom/btile01"; + + fp->WriteFloatString( "Version %1.2f\n", ( float ) CURRENT_MAP_VERSION ); + fp->WriteFloatString( "{\n" ); + fp->WriteFloatString( "\"classname\" \"worldspawn\"\n" ); + + brushCount = 0; +} + +/* +============ +idBrushMap::~idBrushMap +============ +*/ +idBrushMap::~idBrushMap( void ) +{ + if( !fp ) + { + return; + } + fp->WriteFloatString( "}\n" ); + fileSystem->CloseFile( fp ); +} + +/* +============ +idBrushMap::WriteBrush +============ +*/ +void idBrushMap::WriteBrush( const idBrush* brush ) +{ + int i; + idBrushSide* side; + + if( !fp ) + { + return; + } + + fp->WriteFloatString( "// primitive %d\n{\nbrushDef3\n{\n", brushCount++ ); + + for( i = 0; i < brush->GetNumSides(); i++ ) + { + side = brush->GetSide( i ); + fp->WriteFloatString( " ( %f %f %f %f ) ", side->GetPlane()[0], side->GetPlane()[1], side->GetPlane()[2], -side->GetPlane().Dist() ); + fp->WriteFloatString( "( ( 0.031250 0 0 ) ( 0 0.031250 0 ) ) %s 0 0 0\n", texture.c_str() ); + + } + fp->WriteFloatString( "}\n}\n" ); +} + +/* +============ +idBrushMap::WriteBrushList +============ +*/ +void idBrushMap::WriteBrushList( const idBrushList& brushList ) +{ + idBrush* b; + + if( !fp ) + { + return; + } + + for( b = brushList.Head(); b; b = b->Next() ) + { + WriteBrush( b ); + } +} diff --git a/neo/tools/compilers/aas/Brush.h b/neo/tools/compilers/aas/Brush.h new file mode 100644 index 00000000..a280425d --- /dev/null +++ b/neo/tools/compilers/aas/Brush.h @@ -0,0 +1,320 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __BRUSH_H__ +#define __BRUSH_H__ + +/* +=============================================================================== + + Brushes + +=============================================================================== +*/ + + +#define BRUSH_PLANESIDE_FRONT 1 +#define BRUSH_PLANESIDE_BACK 2 +#define BRUSH_PLANESIDE_BOTH ( BRUSH_PLANESIDE_FRONT | BRUSH_PLANESIDE_BACK ) +#define BRUSH_PLANESIDE_FACING 4 + +class idBrush; +class idBrushList; + +void DisplayRealTimeString( char* string, ... ) ID_STATIC_ATTRIBUTE_PRINTF( 1, 2 ); + + +//=============================================================== +// +// idBrushSide +// +//=============================================================== + +#define SFL_SPLIT 0x0001 +#define SFL_BEVEL 0x0002 +#define SFL_USED_SPLITTER 0x0004 +#define SFL_TESTED_SPLITTER 0x0008 + +class idBrushSide +{ + + friend class idBrush; + +public: + idBrushSide( void ); + idBrushSide( const idPlane& plane, int planeNum ); + ~idBrushSide( void ); + + int GetFlags( void ) const + { + return flags; + } + void SetFlag( int flag ) + { + flags |= flag; + } + void RemoveFlag( int flag ) + { + flags &= ~flag; + } + const idPlane& GetPlane( void ) const + { + return plane; + } + void SetPlaneNum( int num ) + { + planeNum = num; + } + int GetPlaneNum( void ) + { + return planeNum; + } + const idWinding* GetWinding( void ) const + { + return winding; + } + idBrushSide* Copy( void ) const; + int Split( const idPlane& splitPlane, idBrushSide** front, idBrushSide** back ) const; + +private: + int flags; + int planeNum; + idPlane plane; + idWinding* winding; +}; + + +//=============================================================== +// +// idBrush +// +//=============================================================== + +#define BFL_NO_VALID_SPLITTERS 0x0001 + +class idBrush +{ + + friend class idBrushList; + +public: + idBrush( void ); + ~idBrush( void ); + + int GetFlags( void ) const + { + return flags; + } + void SetFlag( int flag ) + { + flags |= flag; + } + void RemoveFlag( int flag ) + { + flags &= ~flag; + } + void SetEntityNum( int num ) + { + entityNum = num; + } + void SetPrimitiveNum( int num ) + { + primitiveNum = num; + } + void SetContents( int contents ) + { + this->contents = contents; + } + int GetContents( void ) const + { + return contents; + } + const idBounds& GetBounds( void ) const + { + return bounds; + } + float GetVolume( void ) const; + int GetNumSides( void ) const + { + return sides.Num(); + } + idBrushSide* GetSide( int i ) const + { + return sides[i]; + } + void SetPlaneSide( int s ) + { + planeSide = s; + } + void SavePlaneSide( void ) + { + savedPlaneSide = planeSide; + } + int GetSavedPlaneSide( void ) const + { + return savedPlaneSide; + } + bool FromSides( idList& sideList ); + bool FromWinding( const idWinding& w, const idPlane& windingPlane ); + bool FromBounds( const idBounds& bounds ); + void Transform( const idVec3& origin, const idMat3& axis ); + idBrush* Copy( void ) const; + bool TryMerge( const idBrush* brush, const idPlaneSet& planeList ); + // returns true if the brushes did intersect + bool Subtract( const idBrush* b, idBrushList& list ) const; + // split the brush into a front and back brush + int Split( const idPlane& plane, int planeNum, idBrush** front, idBrush** back ) const; + // expand the brush for an axial bounding box + void ExpandForAxialBox( const idBounds& bounds ); + // next brush in list + idBrush* Next( void ) const + { + return next; + } + +private: + mutable idBrush* next; // next brush in list + int entityNum; // entity number in editor + int primitiveNum; // primitive number in editor + int flags; // brush flags + bool windingsValid; // set when side windings are valid + int contents; // contents of brush + int planeSide; // side of a plane this brush is on + int savedPlaneSide; // saved plane side + idBounds bounds; // brush bounds + idList sides; // list with sides + +private: + bool CreateWindings( void ); + void BoundBrush( const idBrush* original = NULL ); + void AddBevelsForAxialBox( void ); + bool RemoveSidesWithoutWinding( void ); +}; + + +//=============================================================== +// +// idBrushList +// +//=============================================================== + +class idBrushList +{ +public: + idBrushList( void ); + ~idBrushList( void ); + + int Num( void ) const + { + return numBrushes; + } + int NumSides( void ) const + { + return numBrushSides; + } + idBrush* Head( void ) const + { + return head; + } + idBrush* Tail( void ) const + { + return tail; + } + void Clear( void ) + { + head = tail = NULL; + numBrushes = 0; + } + bool IsEmpty( void ) const + { + return ( numBrushes == 0 ); + } + idBounds GetBounds( void ) const; + // add brush to the tail of the list + void AddToTail( idBrush* brush ); + // add list to the tail of the list + void AddToTail( idBrushList& list ); + // add brush to the front of the list + void AddToFront( idBrush* brush ); + // add list to the front of the list + void AddToFront( idBrushList& list ); + // remove the brush from the list + void Remove( idBrush* brush ); + // remove the brush from the list and delete the brush + void Delete( idBrush* brush ); + // returns a copy of the brush list + idBrushList* Copy( void ) const; + // delete all brushes in the list + void Free( void ); + // split the brushes in the list into two lists + void Split( const idPlane& plane, int planeNum, idBrushList& frontList, idBrushList& backList, bool useBrushSavedPlaneSide = false ); + // chop away all brush overlap + void Chop( bool ( *ChopAllowed )( idBrush* b1, idBrush* b2 ) ); + // merge brushes + void Merge( bool ( *MergeAllowed )( idBrush* b1, idBrush* b2 ) ); + // set the given flag on all brush sides facing the plane + void SetFlagOnFacingBrushSides( const idPlane& plane, int flag ); + // get a list with planes for all brushes in the list + void CreatePlaneList( idPlaneSet& planeList ) const; + // write a brush map with the brushes in the list + void WriteBrushMap( const idStr& fileName, const idStr& ext ) const; + +private: + idBrush* head; + idBrush* tail; + int numBrushes; + int numBrushSides; +}; + + +//=============================================================== +// +// idBrushMap +// +//=============================================================== + +class idBrushMap +{ + +public: + idBrushMap( const idStr& fileName, const idStr& ext ); + ~idBrushMap( void ); + void SetTexture( const idStr& textureName ) + { + texture = textureName; + } + void WriteBrush( const idBrush* brush ); + void WriteBrushList( const idBrushList& brushList ); + +private: + idFile* fp; + idStr texture; + int brushCount; +}; + +#endif /* !__BRUSH_H__ */ diff --git a/neo/tools/compilers/aas/BrushBSP.cpp b/neo/tools/compilers/aas/BrushBSP.cpp new file mode 100644 index 00000000..5339145b --- /dev/null +++ b/neo/tools/compilers/aas/BrushBSP.cpp @@ -0,0 +1,2462 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "Brush.h" +#include "BrushBSP.h" + + +#define BSP_GRID_SIZE 512.0f +#define SPLITTER_EPSILON 0.1f +#define VERTEX_MELT_EPSILON 0.1f +#define VERTEX_MELT_HASH_SIZE 32 + +#define PORTAL_PLANE_NORMAL_EPSILON 0.00001f +#define PORTAL_PLANE_DIST_EPSILON 0.01f + +//#define OUPUT_BSP_STATS_PER_GRID_CELL + + +//=============================================================== +// +// idBrushBSPPortal +// +//=============================================================== + +/* +============ +idBrushBSPPortal::idBrushBSPPortal +============ +*/ +idBrushBSPPortal::idBrushBSPPortal( void ) +{ + planeNum = -1; + winding = NULL; + nodes[0] = nodes[1] = NULL; + next[0] = next[1] = NULL; + faceNum = 0; + flags = 0; +} + +/* +============ +idBrushBSPPortal::~idBrushBSPPortal +============ +*/ +idBrushBSPPortal::~idBrushBSPPortal( void ) +{ + if( winding ) + { + delete winding; + } +} + +/* +============ +idBrushBSPPortal::AddToNodes +============ +*/ +void idBrushBSPPortal::AddToNodes( idBrushBSPNode* front, idBrushBSPNode* back ) +{ + if( nodes[0] || nodes[1] ) + { + common->Error( "AddToNode: allready included" ); + } + + assert( front && back ); + + nodes[0] = front; + next[0] = front->portals; + front->portals = this; + + nodes[1] = back; + next[1] = back->portals; + back->portals = this; +} + +/* +============ +idBrushBSPPortal::RemoveFromNode +============ +*/ +void idBrushBSPPortal::RemoveFromNode( idBrushBSPNode* l ) +{ + idBrushBSPPortal** pp, *t; + + // remove reference to the current portal + pp = &l->portals; + while( 1 ) + { + t = *pp; + if( !t ) + { + common->Error( "idBrushBSPPortal::RemoveFromNode: portal not in node" ); + } + + if( t == this ) + { + break; + } + + if( t->nodes[0] == l ) + { + pp = &t->next[0]; + } + else if( t->nodes[1] == l ) + { + pp = &t->next[1]; + } + else + { + common->Error( "idBrushBSPPortal::RemoveFromNode: portal not bounding node" ); + } + } + + if( nodes[0] == l ) + { + *pp = next[0]; + nodes[0] = NULL; + } + else if( nodes[1] == l ) + { + *pp = next[1]; + nodes[1] = NULL; + } + else + { + common->Error( "idBrushBSPPortal::RemoveFromNode: mislinked portal" ); + } +} + +/* +============ +idBrushBSPPortal::Flip +============ +*/ +void idBrushBSPPortal::Flip( void ) +{ + idBrushBSPNode* frontNode, *backNode; + + frontNode = nodes[0]; + backNode = nodes[1]; + + if( frontNode ) + { + RemoveFromNode( frontNode ); + } + if( backNode ) + { + RemoveFromNode( backNode ); + } + AddToNodes( frontNode, backNode ); + + plane = -plane; + planeNum ^= 1; + winding->ReverseSelf(); +} + +/* +============ +idBrushBSPPortal::Split +============ +*/ +int idBrushBSPPortal::Split( const idPlane& splitPlane, idBrushBSPPortal** front, idBrushBSPPortal** back ) +{ + idWinding* frontWinding, *backWinding; + + ( *front ) = ( *back ) = NULL; + winding->Split( splitPlane, 0.1f, &frontWinding, &backWinding ); + if( frontWinding ) + { + ( *front ) = new idBrushBSPPortal(); + ( *front )->plane = plane; + ( *front )->planeNum = planeNum; + ( *front )->flags = flags; + ( *front )->winding = frontWinding; + } + if( backWinding ) + { + ( *back ) = new idBrushBSPPortal(); + ( *back )->plane = plane; + ( *back )->planeNum = planeNum; + ( *back )->flags = flags; + ( *back )->winding = backWinding; + } + + if( frontWinding && backWinding ) + { + return PLANESIDE_CROSS; + } + else if( frontWinding ) + { + return PLANESIDE_FRONT; + } + else + { + return PLANESIDE_BACK; + } +} + + +//=============================================================== +// +// idBrushBSPNode +// +//=============================================================== + +/* +============ +idBrushBSPNode::idBrushBSPNode +============ +*/ +idBrushBSPNode::idBrushBSPNode( void ) +{ + brushList.Clear(); + contents = 0; + flags = 0; + volume = NULL; + portals = NULL; + children[0] = children[1] = NULL; + areaNum = 0; + occupied = 0; +} + +/* +============ +idBrushBSPNode::~idBrushBSPNode +============ +*/ +idBrushBSPNode::~idBrushBSPNode( void ) +{ + idBrushBSPPortal* p; + + // delete brushes + brushList.Free(); + + // delete volume brush + if( volume ) + { + delete volume; + } + + // delete portals + for( p = portals; p; p = portals ) + { + p->RemoveFromNode( this ); + if( !p->nodes[0] && !p->nodes[1] ) + { + delete p; + } + } +} + +/* +============ +idBrushBSPNode::SetContentsFromBrushes +============ +*/ +void idBrushBSPNode::SetContentsFromBrushes( void ) +{ + idBrush* brush; + + contents = 0; + for( brush = brushList.Head(); brush; brush = brush->Next() ) + { + contents |= brush->GetContents(); + } +} + +/* +============ +idBrushBSPNode::GetPortalBounds +============ +*/ +idBounds idBrushBSPNode::GetPortalBounds( void ) +{ + int s, i; + idBrushBSPPortal* p; + idBounds bounds; + + bounds.Clear(); + for( p = portals; p; p = p->next[s] ) + { + s = ( p->nodes[1] == this ); + + for( i = 0; i < p->winding->GetNumPoints(); i++ ) + { + bounds.AddPoint( ( *p->winding )[i].ToVec3() ); + } + } + return bounds; +} + +/* +============ +idBrushBSPNode::TestLeafNode +============ +*/ +bool idBrushBSPNode::TestLeafNode( void ) +{ + int s, n; + float d; + idBrushBSPPortal* p; + idVec3 center; + idPlane plane; + + n = 0; + center = vec3_origin; + for( p = portals; p; p = p->next[s] ) + { + s = ( p->nodes[1] == this ); + center += p->winding->GetCenter(); + n++; + } + + center /= n; + + for( p = portals; p; p = p->next[s] ) + { + s = ( p->nodes[1] == this ); + if( s ) + { + plane = -p->GetPlane(); + } + else + { + plane = p->GetPlane(); + } + d = plane.Distance( center ); + if( d < 0.0f ) + { + return false; + } + } + return true; +} + +/* +============ +idBrushBSPNode::Split +============ +*/ +bool idBrushBSPNode::Split( const idPlane& splitPlane, int splitPlaneNum ) +{ + int s, i; + idWinding* mid; + idBrushBSPPortal* p, *midPortal, *newPortals[2]; + idBrushBSPNode* newNodes[2]; + + mid = new idWinding( splitPlane.Normal(), splitPlane.Dist() ); + + for( p = portals; p && mid; p = p->next[s] ) + { + s = ( p->nodes[1] == this ); + if( s ) + { + mid = mid->Clip( -p->plane, 0.1f, false ); + } + else + { + mid = mid->Clip( p->plane, 0.1f, false ); + } + } + + if( !mid ) + { + return false; + } + + // allocate two new nodes + for( i = 0; i < 2; i++ ) + { + newNodes[i] = new idBrushBSPNode(); + newNodes[i]->flags = flags; + newNodes[i]->contents = contents; + newNodes[i]->parent = this; + } + + // split all portals of the node + for( p = portals; p; p = portals ) + { + s = ( p->nodes[1] == this ); + p->Split( splitPlane, &newPortals[0], &newPortals[1] ); + for( i = 0; i < 2; i++ ) + { + if( newPortals[i] ) + { + if( s ) + { + newPortals[i]->AddToNodes( p->nodes[0], newNodes[i] ); + } + else + { + newPortals[i]->AddToNodes( newNodes[i], p->nodes[1] ); + } + } + } + p->RemoveFromNode( p->nodes[0] ); + p->RemoveFromNode( p->nodes[1] ); + delete p; + } + + // add seperating portal + midPortal = new idBrushBSPPortal(); + midPortal->plane = splitPlane; + midPortal->planeNum = splitPlaneNum; + midPortal->winding = mid; + midPortal->AddToNodes( newNodes[0], newNodes[1] ); + + // set new child nodes + children[0] = newNodes[0]; + children[1] = newNodes[1]; + plane = splitPlane; + + return true; +} + +/* +============ +idBrushBSPNode::PlaneSide +============ +*/ +int idBrushBSPNode::PlaneSide( const idPlane& plane, float epsilon ) const +{ + int s, side; + idBrushBSPPortal* p; + bool front, back; + + front = back = false; + for( p = portals; p; p = p->next[s] ) + { + s = ( p->nodes[1] == this ); + + side = p->winding->PlaneSide( plane, epsilon ); + if( side == SIDE_CROSS || side == SIDE_ON ) + { + return side; + } + if( side == SIDE_FRONT ) + { + if( back ) + { + return SIDE_CROSS; + } + front = true; + } + if( side == SIDE_BACK ) + { + if( front ) + { + return SIDE_CROSS; + } + back = true; + } + } + + if( front ) + { + return SIDE_FRONT; + } + return SIDE_BACK; +} + +/* +============ +idBrushBSPNode::RemoveFlagFlood +============ +*/ +void idBrushBSPNode::RemoveFlagFlood( int flag ) +{ + int s; + idBrushBSPPortal* p; + + RemoveFlag( flag ); + + for( p = GetPortals(); p; p = p->Next( s ) ) + { + s = ( p->GetNode( 1 ) == this ); + + if( !( p->GetNode( !s )->GetFlags() & flag ) ) + { + continue; + } + + p->GetNode( !s )->RemoveFlagFlood( flag ); + } +} + +/* +============ +idBrushBSPNode::RemoveFlagRecurse +============ +*/ +void idBrushBSPNode::RemoveFlagRecurse( int flag ) +{ + RemoveFlag( flag ); + if( children[0] ) + { + children[0]->RemoveFlagRecurse( flag ); + } + if( children[1] ) + { + children[1]->RemoveFlagRecurse( flag ); + } +} + +/* +============ +idBrushBSPNode::RemoveFlagRecurseFlood +============ +*/ +void idBrushBSPNode::RemoveFlagRecurseFlood( int flag ) +{ + RemoveFlag( flag ); + if( !children[0] && !children[1] ) + { + RemoveFlagFlood( flag ); + } + else + { + if( children[0] ) + { + children[0]->RemoveFlagRecurseFlood( flag ); + } + if( children[1] ) + { + children[1]->RemoveFlagRecurseFlood( flag ); + } + } +} + + +//=============================================================== +// +// idBrushBSP +// +//=============================================================== + +/* +============ +idBrushBSP::idBrushBSP +============ +*/ +idBrushBSP::idBrushBSP( void ) +{ + root = outside = NULL; + numSplits = numPrunedSplits = 0; + brushMapContents = 0; + brushMap = NULL; +} + +/* +============ +idBrushBSP::~idBrushBSP +============ +*/ +idBrushBSP::~idBrushBSP( void ) +{ + + RemoveMultipleLeafNodeReferences_r( root ); + Free_r( root ); + + if( outside ) + { + delete outside; + } +} + +/* +============ +idBrushBSP::RemoveMultipleLeafNodeReferences_r +============ +*/ +void idBrushBSP::RemoveMultipleLeafNodeReferences_r( idBrushBSPNode* node ) +{ + if( !node ) + { + return; + } + + if( node->children[0] ) + { + if( node->children[0]->parent != node ) + { + node->children[0] = NULL; + } + else + { + RemoveMultipleLeafNodeReferences_r( node->children[0] ); + } + } + if( node->children[1] ) + { + if( node->children[1]->parent != node ) + { + node->children[1] = NULL; + } + else + { + RemoveMultipleLeafNodeReferences_r( node->children[1] ); + } + } +} + +/* +============ +idBrushBSP::Free_r +============ +*/ +void idBrushBSP::Free_r( idBrushBSPNode* node ) +{ + if( !node ) + { + return; + } + + Free_r( node->children[0] ); + Free_r( node->children[1] ); + + delete node; +} + +/* +============ +idBrushBSP::IsValidSplitter +============ +*/ +ID_INLINE bool idBrushBSP::IsValidSplitter( const idBrushSide* side ) +{ + return !( side->GetFlags() & ( SFL_SPLIT | SFL_USED_SPLITTER ) ); +} + +/* +============ +idBrushBSP::BrushSplitterStats +============ +*/ +typedef struct splitterStats_s +{ + int numFront; // number of brushes at the front of the splitter + int numBack; // number of brushes at the back of the splitter + int numSplits; // number of brush sides split by the splitter + int numFacing; // number of brushes facing this splitter + int epsilonBrushes; // number of tiny brushes this splitter would create +} splitterStats_t; + +int idBrushBSP::BrushSplitterStats( const idBrush* brush, int planeNum, const idPlaneSet& planeList, bool* testedPlanes, struct splitterStats_s& stats ) +{ + int i, j, num, s, lastNumSplits; + const idPlane* plane; + const idWinding* w; + float d, d_front, d_back, brush_front, brush_back; + + plane = &planeList[planeNum]; + + // get the plane side for the brush bounds + s = brush->GetBounds().PlaneSide( *plane, SPLITTER_EPSILON ); + if( s == PLANESIDE_FRONT ) + { + stats.numFront++; + return BRUSH_PLANESIDE_FRONT; + } + if( s == PLANESIDE_BACK ) + { + stats.numBack++; + return BRUSH_PLANESIDE_BACK; + } + + // if the brush actually uses the planenum, we can tell the side for sure + for( i = 0; i < brush->GetNumSides(); i++ ) + { + num = brush->GetSide( i )->GetPlaneNum(); + + if( !( ( num ^ planeNum ) >> 1 ) ) + { + if( num == planeNum ) + { + stats.numBack++; + stats.numFacing++; + return ( BRUSH_PLANESIDE_BACK | BRUSH_PLANESIDE_FACING ); + } + if( num == ( planeNum ^ 1 ) ) + { + stats.numFront++; + stats.numFacing++; + return ( BRUSH_PLANESIDE_FRONT | BRUSH_PLANESIDE_FACING ); + } + } + } + + lastNumSplits = stats.numSplits; + brush_front = brush_back = 0.0f; + for( i = 0; i < brush->GetNumSides(); i++ ) + { + + if( !IsValidSplitter( brush->GetSide( i ) ) ) + { + continue; + } + + j = brush->GetSide( i )->GetPlaneNum(); + if( testedPlanes[j] || testedPlanes[j ^ 1] ) + { + continue; + } + + w = brush->GetSide( i )->GetWinding(); + if( !w ) + { + continue; + } + d_front = d_back = 0.0f; + for( j = 0; j < w->GetNumPoints(); j++ ) + { + d = plane->Distance( ( *w )[j].ToVec3() ); + if( d > d_front ) + { + d_front = d; + } + else if( d < d_back ) + { + d_back = d; + } + } + if( d_front > SPLITTER_EPSILON && d_back < -SPLITTER_EPSILON ) + { + stats.numSplits++; + } + if( d_front > brush_front ) + { + brush_front = d_front; + } + else if( d_back < brush_back ) + { + brush_back = d_back; + } + } + + // if brush sides are split and the brush only pokes one unit through the plane + if( stats.numSplits > lastNumSplits && ( brush_front < 1.0f || brush_back > -1.0f ) ) + { + stats.epsilonBrushes++; + } + + return BRUSH_PLANESIDE_BOTH; +} + +/* +============ +idBrushBSP::FindSplitter +============ +*/ +int idBrushBSP::FindSplitter( idBrushBSPNode* node, const idPlaneSet& planeList, bool* testedPlanes, struct splitterStats_s& bestStats ) +{ + int i, planeNum, bestSplitter, value, bestValue, f, numBrushSides; + idBrush* brush, *b; + splitterStats_t stats; + + memset( testedPlanes, 0, planeList.Num() * sizeof( bool ) ); + + bestSplitter = -1; + bestValue = -99999999; + for( brush = node->brushList.Head(); brush; brush = brush->Next() ) + { + + if( brush->GetFlags() & BFL_NO_VALID_SPLITTERS ) + { + continue; + } + + for( i = 0; i < brush->GetNumSides(); i++ ) + { + + if( !IsValidSplitter( brush->GetSide( i ) ) ) + { + continue; + } + + planeNum = brush->GetSide( i )->GetPlaneNum(); + + if( testedPlanes[planeNum] || testedPlanes[planeNum ^ 1] ) + { + continue; + } + + testedPlanes[planeNum] = testedPlanes[planeNum ^ 1] = true; + + if( node->volume->Split( planeList[planeNum], planeNum, NULL, NULL ) != PLANESIDE_CROSS ) + { + continue; + } + + memset( &stats, 0, sizeof( stats ) ); + + f = 15 + 5 * ( brush->GetSide( i )->GetPlane().Type() < PLANETYPE_TRUEAXIAL ); + numBrushSides = node->brushList.NumSides(); + + for( b = node->brushList.Head(); b; b = b->Next() ) + { + + // if the brush has no valid splitters left + if( b->GetFlags() & BFL_NO_VALID_SPLITTERS ) + { + b->SetPlaneSide( BRUSH_PLANESIDE_BOTH ); + } + else + { + b->SetPlaneSide( BrushSplitterStats( b, planeNum, planeList, testedPlanes, stats ) ); + } + + numBrushSides -= b->GetNumSides(); + // best value we can get using this plane as a splitter + value = f * ( stats.numFacing + numBrushSides ) - 10 * stats.numSplits - stats.epsilonBrushes * 1000; + // if the best value for this plane can't get any better than the best value we have + if( value < bestValue ) + { + break; + } + } + + if( b ) + { + continue; + } + + value = f * stats.numFacing - 10 * stats.numSplits - abs( stats.numFront - stats.numBack ) - stats.epsilonBrushes * 1000; + + if( value > bestValue ) + { + bestValue = value; + bestSplitter = planeNum; + bestStats = stats; + + for( b = node->brushList.Head(); b; b = b->Next() ) + { + b->SavePlaneSide(); + } + } + } + } + + return bestSplitter; +} + +/* +============ +idBrushBSP::SetSplitterUsed +============ +*/ +void idBrushBSP::SetSplitterUsed( idBrushBSPNode* node, int planeNum ) +{ + int i, numValidBrushSplitters; + idBrush* brush; + + for( brush = node->brushList.Head(); brush; brush = brush->Next() ) + { + if( !( brush->GetSavedPlaneSide() & BRUSH_PLANESIDE_FACING ) ) + { + continue; + } + numValidBrushSplitters = 0; + for( i = 0; i < brush->GetNumSides(); i++ ) + { + + if( !( ( brush->GetSide( i )->GetPlaneNum() ^ planeNum ) >> 1 ) ) + { + brush->GetSide( i )->SetFlag( SFL_USED_SPLITTER ); + } + else if( IsValidSplitter( brush->GetSide( i ) ) ) + { + numValidBrushSplitters++; + } + } + if( numValidBrushSplitters == 0 ) + { + brush->SetFlag( BFL_NO_VALID_SPLITTERS ); + } + } +} + +/* +============ +idBrushBSP::BuildBrushBSP_r +============ +*/ +idBrushBSPNode* idBrushBSP::BuildBrushBSP_r( idBrushBSPNode* node, const idPlaneSet& planeList, bool* testedPlanes, int skipContents ) +{ + int planeNum; + splitterStats_t bestStats; + + planeNum = FindSplitter( node, planeList, testedPlanes, bestStats ); + + // if no split plane found this is a leaf node + if( planeNum == -1 ) + { + + node->SetContentsFromBrushes(); + + if( brushMap && ( node->contents & brushMapContents ) ) + { + brushMap->WriteBrush( node->volume ); + } + + // free node memory + node->brushList.Free(); + delete node->volume; + node->volume = NULL; + + node->children[0] = node->children[1] = NULL; + return node; + } + + numSplits++; + numGridCellSplits++; + + // mark all brush sides on the split plane as used + SetSplitterUsed( node, planeNum ); + + // set node split plane + node->plane = planeList[planeNum]; + + // allocate children + node->children[0] = new idBrushBSPNode(); + node->children[1] = new idBrushBSPNode(); + + // split node volume and brush list for children + node->volume->Split( node->plane, -1, &node->children[0]->volume, &node->children[1]->volume ); + node->brushList.Split( node->plane, -1, node->children[0]->brushList, node->children[1]->brushList, true ); + node->children[0]->parent = node->children[1]->parent = node; + + // free node memory + node->brushList.Free(); + delete node->volume; + node->volume = NULL; + + // process children + node->children[0] = BuildBrushBSP_r( node->children[0], planeList, testedPlanes, skipContents ); + node->children[1] = BuildBrushBSP_r( node->children[1], planeList, testedPlanes, skipContents ); + + // if both children contain the skip contents + if( node->children[0]->contents & node->children[1]->contents & skipContents ) + { + node->contents = node->children[0]->contents | node->children[1]->contents; + delete node->children[0]; + delete node->children[1]; + node->children[0] = node->children[1] = NULL; + numSplits--; + numGridCellSplits--; + } + + return node; +} + +/* +============ +idBrushBSP::ProcessGridCell +============ +*/ +idBrushBSPNode* idBrushBSP::ProcessGridCell( idBrushBSPNode* node, int skipContents ) +{ + idPlaneSet planeList; + bool* testedPlanes; + +#ifdef OUPUT_BSP_STATS_PER_GRID_CELL + common->Printf( "[Grid Cell %d]\n", ++numGridCells ); + common->Printf( "%6d brushes\n", node->brushList.Num() ); +#endif + + numGridCellSplits = 0; + + // chop away all brush overlap + node->brushList.Chop( BrushChopAllowed ); + + // merge brushes if possible + //node->brushList.Merge( BrushMergeAllowed ); + + // create a list with planes for this grid cell + node->brushList.CreatePlaneList( planeList ); + +#ifdef OUPUT_BSP_STATS_PER_GRID_CELL + common->Printf( "[Grid Cell BSP]\n" ); +#endif + + testedPlanes = new bool[planeList.Num()]; + + BuildBrushBSP_r( node, planeList, testedPlanes, skipContents ); + + delete testedPlanes; + +#ifdef OUPUT_BSP_STATS_PER_GRID_CELL + common->Printf( "\r%6d splits\n", numGridCellSplits ); +#endif + + return node; +} + +/* +============ +idBrushBSP::BuildGrid_r +============ +*/ +void idBrushBSP::BuildGrid_r( idList& gridCells, idBrushBSPNode* node ) +{ + int axis; + float dist; + idBounds bounds; + idVec3 normal, halfSize; + + if( !node->brushList.Num() ) + { + delete node->volume; + node->volume = NULL; + node->children[0] = node->children[1] = NULL; + return; + } + + bounds = node->volume->GetBounds(); + halfSize = ( bounds[1] - bounds[0] ) * 0.5f; + for( axis = 0; axis < 3; axis++ ) + { + if( halfSize[axis] > BSP_GRID_SIZE ) + { + dist = BSP_GRID_SIZE * ( floor( ( bounds[0][axis] + halfSize[axis] ) / BSP_GRID_SIZE ) + 1 ); + } + else + { + dist = BSP_GRID_SIZE * ( floor( bounds[0][axis] / BSP_GRID_SIZE ) + 1 ); + } + if( dist > bounds[0][axis] + 1.0f && dist < bounds[1][axis] - 1.0f ) + { + break; + } + } + if( axis >= 3 ) + { + gridCells.Append( node ); + return; + } + + numSplits++; + + normal = vec3_origin; + normal[axis] = 1.0f; + node->plane.SetNormal( normal ); + node->plane.SetDist( ( int ) dist ); + + // allocate children + node->children[0] = new idBrushBSPNode(); + node->children[1] = new idBrushBSPNode(); + + // split volume and brush list for children + node->volume->Split( node->plane, -1, &node->children[0]->volume, &node->children[1]->volume ); + node->brushList.Split( node->plane, -1, node->children[0]->brushList, node->children[1]->brushList ); + node->children[0]->brushList.SetFlagOnFacingBrushSides( node->plane, SFL_USED_SPLITTER ); + node->children[1]->brushList.SetFlagOnFacingBrushSides( node->plane, SFL_USED_SPLITTER ); + node->children[0]->parent = node->children[1]->parent = node; + + // free node memory + node->brushList.Free(); + delete node->volume; + node->volume = NULL; + + // process children + BuildGrid_r( gridCells, node->children[0] ); + BuildGrid_r( gridCells, node->children[1] ); +} + +/* +============ +idBrushBSP::Build +============ +*/ +void idBrushBSP::Build( idBrushList brushList, int skipContents, + bool ( *ChopAllowed )( idBrush* b1, idBrush* b2 ), + bool ( *MergeAllowed )( idBrush* b1, idBrush* b2 ) ) +{ + + int i; + idList gridCells; + + common->Printf( "[Brush BSP]\n" ); + common->Printf( "%6d brushes\n", brushList.Num() ); + + BrushChopAllowed = ChopAllowed; + BrushMergeAllowed = MergeAllowed; + + numGridCells = 0; + treeBounds = brushList.GetBounds(); + root = new idBrushBSPNode(); + root->brushList = brushList; + root->volume = new idBrush(); + root->volume->FromBounds( treeBounds ); + root->parent = NULL; + + BuildGrid_r( gridCells, root ); + + common->Printf( "\r%6d grid cells\n", gridCells.Num() ); + +#ifdef OUPUT_BSP_STATS_PER_GRID_CELL + for( i = 0; i < gridCells.Num(); i++ ) + { + ProcessGridCell( gridCells[i], skipContents ); + } +#else + common->Printf( "\r%6d %%", 0 ); + for( i = 0; i < gridCells.Num(); i++ ) + { + DisplayRealTimeString( "\r%6d", i * 100 / gridCells.Num() ); + ProcessGridCell( gridCells[i], skipContents ); + } + common->Printf( "\r%6d %%\n", 100 ); +#endif + + common->Printf( "\r%6d splits\n", numSplits ); + + if( brushMap ) + { + delete brushMap; + } +} + +/* +============ +idBrushBSP::WriteBrushMap +============ +*/ +void idBrushBSP::WriteBrushMap( const idStr& fileName, const idStr& ext, int contents ) +{ + brushMap = new idBrushMap( fileName, ext ); + brushMapContents = contents; +} + +/* +============ +idBrushBSP::PruneTree_r +============ +*/ +void idBrushBSP::PruneTree_r( idBrushBSPNode* node, int contents ) +{ + int i, s; + idBrushBSPNode* nodes[2]; + idBrushBSPPortal* p, *nextp; + + if( !node->children[0] || !node->children[1] ) + { + return; + } + + PruneTree_r( node->children[0], contents ); + PruneTree_r( node->children[1], contents ); + + if( ( node->children[0]->contents & node->children[1]->contents & contents ) ) + { + + node->contents = node->children[0]->contents | node->children[1]->contents; + // move all child portals to parent + for( i = 0; i < 2; i++ ) + { + for( p = node->children[i]->portals; p; p = nextp ) + { + s = ( p->nodes[1] == node->children[i] ); + nextp = p->next[s]; + nodes[s] = node; + nodes[!s] = p->nodes[!s]; + p->RemoveFromNode( p->nodes[0] ); + p->RemoveFromNode( p->nodes[1] ); + if( nodes[!s] == node->children[!i] ) + { + delete p; // portal seperates both children + } + else + { + p->AddToNodes( nodes[0], nodes[1] ); + } + } + } + + delete node->children[0]; + delete node->children[1]; + node->children[0] = NULL; + node->children[1] = NULL; + + numPrunedSplits++; + } +} + +/* +============ +idBrushBSP::PruneTree +============ +*/ +void idBrushBSP::PruneTree( int contents ) +{ + numPrunedSplits = 0; + common->Printf( "[Prune BSP]\n" ); + PruneTree_r( root, contents ); + common->Printf( "%6d splits pruned\n", numPrunedSplits ); +} + +/* +============ +idBrushBSP::BaseWindingForNode +============ +*/ +#define BASE_WINDING_EPSILON 0.001f + +idWinding* idBrushBSP::BaseWindingForNode( idBrushBSPNode* node ) +{ + idWinding* w; + idBrushBSPNode* n; + + w = new idWinding( node->plane.Normal(), node->plane.Dist() ); + + // clip by all the parents + for( n = node->parent; n && w; n = n->parent ) + { + + if( n->children[0] == node ) + { + // take front + w = w->Clip( n->plane, BASE_WINDING_EPSILON ); + } + else + { + // take back + w = w->Clip( -n->plane, BASE_WINDING_EPSILON ); + } + node = n; + } + + return w; +} + +/* +============ +idBrushBSP::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 idBrushBSP::MakeNodePortal( idBrushBSPNode* node ) +{ + idBrushBSPPortal* newPortal, *p; + idWinding* w; + 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; + w = w->Clip( p->plane, 0.1f ); + } + else if( p->nodes[1] == node ) + { + side = 1; + w = w->Clip( -p->plane, 0.1f ); + } + else + { + common->Error( "MakeNodePortal: mislinked portal" ); + } + } + + if( !w ) + { + return; + } + + if( w->IsTiny() ) + { + delete w; + return; + } + + newPortal = new idBrushBSPPortal(); + newPortal->plane = node->plane; + newPortal->winding = w; + newPortal->AddToNodes( node->children[0], node->children[1] ); +} + +/* +============ +idBrushBSP::SplitNodePortals + + Move or split the portals that bound the node so that the node's children have portals instead of node. +============ +*/ +#define SPLIT_WINDING_EPSILON 0.001f + +void idBrushBSP::SplitNodePortals( idBrushBSPNode* node ) +{ + int side = 0; + idBrushBSPPortal* p, *nextPortal, *newPortal; + idBrushBSPNode* f, *b, *otherNode; + idPlane* plane; + idWinding* frontWinding, *backWinding; + + plane = &node->plane; + f = node->children[0]; + b = node->children[1]; + + for( p = node->portals; p; p = nextPortal ) + { + if( p->nodes[0] == node ) + { + side = 0; + } + else if( p->nodes[1] == node ) + { + side = 1; + } + else + { + common->Error( "idBrushBSP::SplitNodePortals: mislinked portal" ); + } + nextPortal = p->next[side]; + + otherNode = p->nodes[!side]; + p->RemoveFromNode( p->nodes[0] ); + p->RemoveFromNode( p->nodes[1] ); + + // cut the portal into two portals, one on each side of the cut plane + p->winding->Split( *plane, SPLIT_WINDING_EPSILON, &frontWinding, &backWinding ); + + if( frontWinding && frontWinding->IsTiny() ) + { + delete frontWinding; + frontWinding = NULL; + //tinyportals++; + } + + if( backWinding && backWinding->IsTiny() ) + { + delete backWinding; + backWinding = NULL; + //tinyportals++; + } + + if( !frontWinding && !backWinding ) + { + // tiny windings on both sides + continue; + } + + if( !frontWinding ) + { + delete backWinding; + if( side == 0 ) + { + p->AddToNodes( b, otherNode ); + } + else + { + p->AddToNodes( otherNode, b ); + } + continue; + } + if( !backWinding ) + { + delete frontWinding; + if( side == 0 ) + { + p->AddToNodes( f, otherNode ); + } + else + { + p->AddToNodes( otherNode, f ); + } + continue; + } + + // the winding is split + newPortal = new idBrushBSPPortal(); + *newPortal = *p; + newPortal->winding = backWinding; + delete p->winding; + p->winding = frontWinding; + + if( side == 0 ) + { + p->AddToNodes( f, otherNode ); + newPortal->AddToNodes( b, otherNode ); + } + else + { + p->AddToNodes( otherNode, f ); + newPortal->AddToNodes( otherNode, b ); + } + } + + node->portals = NULL; +} + +/* +============ +idBrushBSP::MakeTreePortals_r +============ +*/ +void idBrushBSP::MakeTreePortals_r( idBrushBSPNode* node ) +{ + int i; + idBounds bounds; + + numPortals++; + DisplayRealTimeString( "\r%6d", numPortals ); + + bounds = node->GetPortalBounds(); + + if( bounds[0][0] >= bounds[1][0] ) + { + //common->Warning( "node without volume" ); + } + + for( i = 0; i < 3; i++ ) + { + if( bounds[0][i] < MIN_WORLD_COORD || bounds[1][i] > MAX_WORLD_COORD ) + { + common->Warning( "node with unbounded volume" ); + break; + } + } + + if( !node->children[0] || !node->children[1] ) + { + return; + } + + MakeNodePortal( node ); + SplitNodePortals( node ); + + MakeTreePortals_r( node->children[0] ); + MakeTreePortals_r( node->children[1] ); +} + +/* +============ +idBrushBSP::MakeOutsidePortals +============ +*/ +void idBrushBSP::MakeOutsidePortals( void ) +{ + int i, j, n; + idBounds bounds; + idBrushBSPPortal* p, *portals[6]; + idVec3 normal; + idPlane planes[6]; + + // pad with some space so there will never be null volume leaves + bounds = treeBounds.Expand( 32 ); + + for( i = 0; i < 3; i++ ) + { + if( bounds[0][i] > bounds[1][i] ) + { + common->Error( "empty BSP tree" ); + } + } + + outside = new idBrushBSPNode(); + outside->parent = NULL; + outside->children[0] = outside->children[1] = NULL; + outside->brushList.Clear(); + outside->portals = NULL; + outside->contents = 0; + + for( i = 0; i < 3; i++ ) + { + for( j = 0; j < 2; j++ ) + { + + p = new idBrushBSPPortal(); + normal = vec3_origin; + normal[i] = j ? -1 : 1; + p->plane.SetNormal( normal ); + p->plane.SetDist( j ? -bounds[j][i] : bounds[j][i] ); + p->winding = new idWinding( p->plane.Normal(), p->plane.Dist() ); + p->AddToNodes( root, outside ); + + n = j * 3 + i; + portals[n] = p; + } + } + + // clip the base windings with all the other planes + for( i = 0; i < 6; i++ ) + { + for( j = 0; j < 6; j++ ) + { + if( j == i ) + { + continue; + } + portals[i]->winding = portals[i]->winding->Clip( portals[j]->plane, ON_EPSILON ); + } + } +} + +/* +============ +idBrushBSP::Portalize +============ +*/ +void idBrushBSP::Portalize( void ) +{ + common->Printf( "[Portalize BSP]\n" ); + common->Printf( "%6d nodes\n", ( numSplits - numPrunedSplits ) * 2 + 1 ); + numPortals = 0; + MakeOutsidePortals(); + MakeTreePortals_r( root ); + common->Printf( "\r%6d nodes portalized\n", numPortals ); +} + +/* +============= +LeakFile + +Finds the shortest possible chain of portals that +leads from the outside leaf to a specific occupied leaf. +============= +*/ +void idBrushBSP::LeakFile( const idStr& fileName ) +{ + int count, next, s; + idVec3 mid; + idFile* lineFile; + idBrushBSPNode* node, *nextNode = NULL; + idBrushBSPPortal* p, *nextPortal = NULL; + idStr qpath, name; + + if( !outside->occupied ) + { + return; + } + + qpath = fileName; + qpath.SetFileExtension( "lin" ); + + common->Printf( "writing %s...\n", qpath.c_str() ); + + lineFile = fileSystem->OpenFileWrite( qpath, "fs_devpath" ); + if( !lineFile ) + { + common->Error( "Couldn't open %s\n", qpath.c_str() ); + return; + } + + count = 0; + node = outside; + while( node->occupied > 1 ) + { + + // find the best portal exit + next = node->occupied; + for( 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; + mid = nextPortal->winding->GetCenter(); + lineFile->Printf( "%f %f %f\n", mid[0], mid[1], mid[2] ); + count++; + } + + // add the origin of the entity from which the leak was found + lineFile->Printf( "%f %f %f\n", leakOrigin[0], leakOrigin[1], leakOrigin[2] ); + + fileSystem->CloseFile( lineFile ); +} + +/* +============ +idBrushBSP::FloodThroughPortals_r +============ +*/ +void idBrushBSP::FloodThroughPortals_r( idBrushBSPNode* node, int contents, int depth ) +{ + idBrushBSPPortal* p; + int s; + + if( node->occupied ) + { + common->Error( "FloodThroughPortals_r: node already occupied\n" ); + } + if( !node ) + { + common->Error( "FloodThroughPortals_r: NULL node\n" ); + } + + node->occupied = depth; + + for( p = node->portals; p; p = p->next[s] ) + { + s = ( p->nodes[1] == node ); + + // if the node at the other side of the portal is removed + if( !p->nodes[!s] ) + { + continue; + } + + // if the node at the other side of the portal is occupied already + if( p->nodes[!s]->occupied ) + { + continue; + } + + // can't flood through the portal if it has the seperating contents at the other side + if( p->nodes[!s]->contents & contents ) + { + continue; + } + + // flood recursively through the current portal + FloodThroughPortals_r( p->nodes[!s], contents, depth + 1 ); + } +} + +/* +============ +idBrushBSP::FloodFromOrigin +============ +*/ +bool idBrushBSP::FloodFromOrigin( const idVec3& origin, int contents ) +{ + idBrushBSPNode* node; + + //find the leaf to start in + node = root; + while( node->children[0] && node->children[1] ) + { + + if( node->plane.Side( origin ) == PLANESIDE_BACK ) + { + node = node->children[1]; + } + else + { + node = node->children[0]; + } + } + + if( !node ) + { + return false; + } + + // if inside the inside/outside seperating contents + if( node->contents & contents ) + { + return false; + } + + // if the node is already occupied + if( node->occupied ) + { + return false; + } + + FloodThroughPortals_r( node, contents, 1 ); + + return true; +} + +/* +============ +idBrushBSP::FloodFromEntities + + Marks all nodes that can be reached by entites. +============ +*/ +bool idBrushBSP::FloodFromEntities( const idMapFile* mapFile, int contents, const idStrList& classNames ) +{ + int i, j; + bool inside; + idVec3 origin; + idMapEntity* mapEnt; + idStr classname; + + inside = false; + outside->occupied = 0; + + // skip the first entity which is assumed to be the worldspawn + for( i = 1; i < mapFile->GetNumEntities(); i++ ) + { + + mapEnt = mapFile->GetEntity( i ); + + if( !mapEnt->epairs.GetVector( "origin", "", origin ) ) + { + continue; + } + + if( !mapEnt->epairs.GetString( "classname", "", classname ) ) + { + continue; + } + + for( j = 0; j < classNames.Num(); j++ ) + { + if( classname.Icmp( classNames[j] ) == 0 ) + { + break; + } + } + + if( j >= classNames.Num() ) + { + continue; + } + + origin[2] += 1; + + // nudge around a little + if( FloodFromOrigin( origin, contents ) ) + { + inside = true; + } + + if( outside->occupied ) + { + leakOrigin = origin; + break; + } + } + + if( !inside ) + { + common->Warning( "no entities inside" ); + } + else if( outside->occupied ) + { + common->Warning( "reached outside from entity %d (%s)", i, classname.c_str() ); + } + + return ( inside && !outside->occupied ); +} + +/* +============ +idBrushBSP::RemoveOutside_r +============ +*/ +void idBrushBSP::RemoveOutside_r( idBrushBSPNode* node, int contents ) +{ + + if( !node ) + { + return; + } + + if( node->children[0] || node->children[1] ) + { + RemoveOutside_r( node->children[0], contents ); + RemoveOutside_r( node->children[1], contents ); + return; + } + + if( !node->occupied ) + { + if( !( node->contents & contents ) ) + { + outsideLeafNodes++; + node->contents |= contents; + } + else + { + solidLeafNodes++; + } + } + else + { + insideLeafNodes++; + } +} + +/* +============ +idBrushBSP::RemoveOutside +============ +*/ +bool idBrushBSP::RemoveOutside( const idMapFile* mapFile, int contents, const idStrList& classNames ) +{ + common->Printf( "[Remove Outside]\n" ); + + solidLeafNodes = outsideLeafNodes = insideLeafNodes = 0; + + if( !FloodFromEntities( mapFile, contents, classNames ) ) + { + return false; + } + + RemoveOutside_r( root, contents ); + + common->Printf( "%6d solid leaf nodes\n", solidLeafNodes ); + common->Printf( "%6d outside leaf nodes\n", outsideLeafNodes ); + common->Printf( "%6d inside leaf nodes\n", insideLeafNodes ); + + //PruneTree( contents ); + + return true; +} + +/* +============ +idBrushBSP::SetPortalPlanes_r +============ +*/ +void idBrushBSP::SetPortalPlanes_r( idBrushBSPNode* node, idPlaneSet& planeList ) +{ + int s; + idBrushBSPPortal* p; + + if( !node ) + { + return; + } + + for( p = node->portals; p; p = p->next[s] ) + { + s = ( p->nodes[1] == node ); + if( p->planeNum == -1 ) + { + p->planeNum = planeList.FindPlane( p->plane, PORTAL_PLANE_NORMAL_EPSILON, PORTAL_PLANE_DIST_EPSILON ); + } + } + SetPortalPlanes_r( node->children[0], planeList ); + SetPortalPlanes_r( node->children[1], planeList ); +} + +/* +============ +idBrushBSP::SetPortalPlanes + + give all portals a plane number +============ +*/ +void idBrushBSP::SetPortalPlanes( void ) +{ + SetPortalPlanes_r( root, portalPlanes ); +} + +/* +============ +idBrushBSP::MergeLeafNodePortals +============ +*/ +void idBrushBSP::MergeLeafNodePortals( idBrushBSPNode* node, int skipContents ) +{ + int s1, s2; + bool foundPortal; + idBrushBSPPortal* p1, *p2, *nextp1, *nextp2; + idWinding* newWinding, *reverse; + + // pass 1: merge all portals that seperate the same leaf nodes + for( p1 = node->GetPortals(); p1; p1 = nextp1 ) + { + s1 = ( p1->GetNode( 1 ) == node ); + nextp1 = p1->Next( s1 ); + + for( p2 = nextp1; p2; p2 = nextp2 ) + { + s2 = ( p2->GetNode( 1 ) == node ); + nextp2 = p2->Next( s2 ); + + // if both portals seperate the same leaf nodes + if( p1->nodes[!s1] == p2->nodes[!s2] ) + { + + // add the winding of p2 to the winding of p1 + p1->winding->AddToConvexHull( p2->winding, p1->plane.Normal() ); + + // delete p2 + p2->RemoveFromNode( p2->nodes[0] ); + p2->RemoveFromNode( p2->nodes[1] ); + delete p2; + + numMergedPortals++; + + nextp1 = node->GetPortals(); + break; + } + } + } + + // pass 2: merge all portals in the same plane if they all have the skip contents at the other side + for( p1 = node->GetPortals(); p1; p1 = nextp1 ) + { + s1 = ( p1->GetNode( 1 ) == node ); + nextp1 = p1->Next( s1 ); + + if( !( p1->nodes[!s1]->contents & skipContents ) ) + { + continue; + } + + // test if all portals in this plane have the skip contents at the other side + foundPortal = false; + for( p2 = node->GetPortals(); p2; p2 = nextp2 ) + { + s2 = ( p2->GetNode( 1 ) == node ); + nextp2 = p2->Next( s2 ); + + if( p2 == p1 || ( p2->planeNum & ~1 ) != ( p1->planeNum & ~1 ) ) + { + continue; + } + foundPortal = true; + if( !( p2->nodes[!s2]->contents & skipContents ) ) + { + break; + } + } + + // if all portals in this plane have the skip contents at the other side + if( !p2 && foundPortal ) + { + for( p2 = node->GetPortals(); p2; p2 = nextp2 ) + { + s2 = ( p2->GetNode( 1 ) == node ); + nextp2 = p2->Next( s2 ); + + if( p2 == p1 || ( p2->planeNum & ~1 ) != ( p1->planeNum & ~1 ) ) + { + continue; + } + + // add the winding of p2 to the winding of p1 + p1->winding->AddToConvexHull( p2->winding, p1->plane.Normal() ); + + // delete p2 + p2->RemoveFromNode( p2->nodes[0] ); + p2->RemoveFromNode( p2->nodes[1] ); + delete p2; + + numMergedPortals++; + } + nextp1 = node->GetPortals(); + } + } + + // pass 3: try to merge portals in the same plane that have the skip contents at the other side + for( p1 = node->GetPortals(); p1; p1 = nextp1 ) + { + s1 = ( p1->GetNode( 1 ) == node ); + nextp1 = p1->Next( s1 ); + + if( !( p1->nodes[!s1]->contents & skipContents ) ) + { + continue; + } + + for( p2 = nextp1; p2; p2 = nextp2 ) + { + s2 = ( p2->GetNode( 1 ) == node ); + nextp2 = p2->Next( s2 ); + + if( !( p2->nodes[!s2]->contents & skipContents ) ) + { + continue; + } + + if( ( p2->planeNum & ~1 ) != ( p1->planeNum & ~1 ) ) + { + continue; + } + + // try to merge the two portal windings + if( p2->planeNum == p1->planeNum ) + { + newWinding = p1->winding->TryMerge( *p2->winding, p1->plane.Normal() ); + } + else + { + reverse = p2->winding->Reverse(); + newWinding = p1->winding->TryMerge( *reverse, p1->plane.Normal() ); + delete reverse; + } + + // if successfully merged + if( newWinding ) + { + + // replace the winding of the first portal + delete p1->winding; + p1->winding = newWinding; + + // delete p2 + p2->RemoveFromNode( p2->nodes[0] ); + p2->RemoveFromNode( p2->nodes[1] ); + delete p2; + + numMergedPortals++; + + nextp1 = node->GetPortals(); + break; + } + } + } +} + +/* +============ +idBrushBSP::MergePortals_r +============ +*/ +void idBrushBSP::MergePortals_r( idBrushBSPNode* node, int skipContents ) +{ + + if( !node ) + { + return; + } + + if( node->contents & skipContents ) + { + return; + } + + if( !node->children[0] && !node->children[1] ) + { + MergeLeafNodePortals( node, skipContents ); + return; + } + + MergePortals_r( node->children[0], skipContents ); + MergePortals_r( node->children[1], skipContents ); +} + +/* +============ +idBrushBSP::MergePortals +============ +*/ +void idBrushBSP::MergePortals( int skipContents ) +{ + numMergedPortals = 0; + common->Printf( "[Merge Portals]\n" ); + SetPortalPlanes(); + MergePortals_r( root, skipContents ); + common->Printf( "%6d portals merged\n", numMergedPortals ); +} + +/* +============ +idBrushBSP::PruneMergedTree_r +============ +*/ +void idBrushBSP::PruneMergedTree_r( idBrushBSPNode* node ) +{ + int i; + idBrushBSPNode* leafNode; + + if( !node ) + { + return; + } + + PruneMergedTree_r( node->children[0] ); + PruneMergedTree_r( node->children[1] ); + + for( i = 0; i < 2; i++ ) + { + if( node->children[i] ) + { + leafNode = node->children[i]->children[0]; + if( leafNode && leafNode == node->children[i]->children[1] ) + { + if( leafNode->parent == node->children[i] ) + { + leafNode->parent = node; + } + delete node->children[i]; + node->children[i] = leafNode; + } + } + } +} + +/* +============ +idBrushBSP::UpdateTreeAfterMerge_r +============ +*/ +void idBrushBSP::UpdateTreeAfterMerge_r( idBrushBSPNode* node, const idBounds& bounds, idBrushBSPNode* oldNode, idBrushBSPNode* newNode ) +{ + + if( !node ) + { + return; + } + + if( !node->children[0] && !node->children[1] ) + { + return; + } + + if( node->children[0] == oldNode ) + { + node->children[0] = newNode; + } + if( node->children[1] == oldNode ) + { + node->children[1] = newNode; + } + + switch( bounds.PlaneSide( node->plane, 2.0f ) ) + { + case PLANESIDE_FRONT: + UpdateTreeAfterMerge_r( node->children[0], bounds, oldNode, newNode ); + break; + case PLANESIDE_BACK: + UpdateTreeAfterMerge_r( node->children[1], bounds, oldNode, newNode ); + break; + default: + UpdateTreeAfterMerge_r( node->children[0], bounds, oldNode, newNode ); + UpdateTreeAfterMerge_r( node->children[1], bounds, oldNode, newNode ); + break; + } +} + +/* +============ +idBrushBSP::TryMergeLeafNodes + + NOTE: multiple brances of the BSP tree might point to the same leaf node after merging +============ +*/ +bool idBrushBSP::TryMergeLeafNodes( idBrushBSPPortal* portal, int side ) +{ + int i, j, k, s1, s2, s; + idBrushBSPNode* nodes[2], *node1, *node2; + idBrushBSPPortal* p1, *p2, *p, *nextp; + idPlane plane; + idWinding* w; + idBounds bounds, b; + + nodes[0] = node1 = portal->nodes[side]; + nodes[1] = node2 = portal->nodes[!side]; + + // check if the merged node would still be convex + for( i = 0; i < 2; i++ ) + { + + j = !i; + + for( p1 = nodes[i]->portals; p1; p1 = p1->next[s1] ) + { + s1 = ( p1->nodes[1] == nodes[i] ); + + if( p1->nodes[!s1] == nodes[j] ) + { + continue; + } + + if( s1 ) + { + plane = -p1->plane; + } + else + { + plane = p1->plane; + } + + // all the non seperating portals of the other node should be at the front or on the plane + for( p2 = nodes[j]->portals; p2; p2 = p2->next[s2] ) + { + s2 = ( p2->nodes[1] == nodes[j] ); + + if( p2->nodes[!s2] == nodes[i] ) + { + continue; + } + + w = p2->winding; + for( k = 0; k < w->GetNumPoints(); k++ ) + { + if( plane.Distance( ( *w )[k].ToVec3() ) < -0.1f ) + { + return false; + } + } + } + } + } + + // remove all portals that seperate the two nodes + for( p = node1->portals; p; p = nextp ) + { + s = ( p->nodes[1] == node1 ); + nextp = p->next[s]; + + if( p->nodes[!s] == node2 ) + { + p->RemoveFromNode( p->nodes[0] ); + p->RemoveFromNode( p->nodes[1] ); + delete p; + } + } + + // move all portals of node2 to node1 + for( p = node2->portals; p; p = node2->portals ) + { + s = ( p->nodes[1] == node2 ); + + nodes[s] = node1; + nodes[!s] = p->nodes[!s]; + p->RemoveFromNode( p->nodes[0] ); + p->RemoveFromNode( p->nodes[1] ); + p->AddToNodes( nodes[0], nodes[1] ); + } + + // get bounds for the new node + bounds.Clear(); + for( p = node1->portals; p; p = p->next[s] ) + { + s = ( p->nodes[1] == node1 ); + p->GetWinding()->GetBounds( b ); + bounds += b; + } + + // replace every reference to node2 by a reference to node1 + UpdateTreeAfterMerge_r( root, bounds, node2, node1 ); + + delete node2; + + return true; +} + +/* +============ +idBrushBSP::MeltFloor_r + + flood through portals touching the bounds to find all vertices that might be inside the bounds +============ +*/ +void idBrushBSP::MeltFlood_r( idBrushBSPNode* node, int skipContents, idBounds& bounds, idVectorSet& vertexList ) +{ + int s1, i; + idBrushBSPPortal* p1; + idBounds b; + const idWinding* w; + + node->SetFlag( NODE_VISITED ); + + for( p1 = node->GetPortals(); p1; p1 = p1->Next( s1 ) ) + { + s1 = ( p1->GetNode( 1 ) == node ); + + if( p1->GetNode( !s1 )->GetFlags() & NODE_VISITED ) + { + continue; + } + + w = p1->GetWinding(); + + for( i = 0; i < w->GetNumPoints(); i++ ) + { + if( bounds.ContainsPoint( ( *w )[i].ToVec3() ) ) + { + vertexList.FindVector( ( *w )[i].ToVec3(), VERTEX_MELT_EPSILON ); + } + } + } + + for( p1 = node->GetPortals(); p1; p1 = p1->Next( s1 ) ) + { + s1 = ( p1->GetNode( 1 ) == node ); + + if( p1->GetNode( !s1 )->GetFlags() & NODE_VISITED ) + { + continue; + } + + if( p1->GetNode( !s1 )->GetContents() & skipContents ) + { + continue; + } + + w = p1->GetWinding(); + w->GetBounds( b ); + + if( !bounds.IntersectsBounds( b ) ) + { + continue; + } + + MeltFlood_r( p1->GetNode( !s1 ), skipContents, bounds, vertexList ); + } +} + +/* +============ +idBrushBSP::MeltLeafNodePortals +============ +*/ +void idBrushBSP::MeltLeafNodePortals( idBrushBSPNode* node, int skipContents, idVectorSet& vertexList ) +{ + int s1, i; + idBrushBSPPortal* p1; + idBounds bounds; + + if( node->GetFlags() & NODE_DONE ) + { + return; + } + + node->SetFlag( NODE_DONE ); + + // melt things together + for( p1 = node->GetPortals(); p1; p1 = p1->Next( s1 ) ) + { + s1 = ( p1->GetNode( 1 ) == node ); + + if( p1->GetNode( !s1 )->GetFlags() & NODE_DONE ) + { + continue; + } + + p1->winding->GetBounds( bounds ); + bounds.ExpandSelf( 2 * VERTEX_MELT_HASH_SIZE * VERTEX_MELT_EPSILON ); + vertexList.Init( bounds[0], bounds[1], VERTEX_MELT_HASH_SIZE, 128 ); + + // get all vertices to be considered + MeltFlood_r( node, skipContents, bounds, vertexList ); + node->RemoveFlagFlood( NODE_VISITED ); + + for( i = 0; i < vertexList.Num(); i++ ) + { + if( p1->winding->InsertPointIfOnEdge( vertexList[i], p1->plane, 0.1f ) ) + { + numInsertedPoints++; + } + } + } + DisplayRealTimeString( "\r%6d", numInsertedPoints ); +} + +/* +============ +idBrushBSP::MeltPortals_r +============ +*/ +void idBrushBSP::MeltPortals_r( idBrushBSPNode* node, int skipContents, idVectorSet& vertexList ) +{ + if( !node ) + { + return; + } + + if( node->contents & skipContents ) + { + return; + } + + if( !node->children[0] && !node->children[1] ) + { + MeltLeafNodePortals( node, skipContents, vertexList ); + return; + } + + MeltPortals_r( node->children[0], skipContents, vertexList ); + MeltPortals_r( node->children[1], skipContents, vertexList ); +} + +/* +============ +idBrushBSP::RemoveLeafNodeColinearPoints +============ +*/ +void idBrushBSP::RemoveLeafNodeColinearPoints( idBrushBSPNode* node ) +{ + int s1; + idBrushBSPPortal* p1; + + // remove colinear points + for( p1 = node->GetPortals(); p1; p1 = p1->Next( s1 ) ) + { + s1 = ( p1->GetNode( 1 ) == node ); + p1->winding->RemoveColinearPoints( p1->plane.Normal(), 0.1f ); + } +} + +/* +============ +idBrushBSP::RemoveColinearPoints_r +============ +*/ +void idBrushBSP::RemoveColinearPoints_r( idBrushBSPNode* node, int skipContents ) +{ + if( !node ) + { + return; + } + + if( node->contents & skipContents ) + { + return; + } + + if( !node->children[0] && !node->children[1] ) + { + RemoveLeafNodeColinearPoints( node ); + return; + } + + RemoveColinearPoints_r( node->children[0], skipContents ); + RemoveColinearPoints_r( node->children[1], skipContents ); +} + +/* +============ +idBrushBSP::MeltPortals +============ +*/ +void idBrushBSP::MeltPortals( int skipContents ) +{ + idVectorSet vertexList; + + numInsertedPoints = 0; + common->Printf( "[Melt Portals]\n" ); + RemoveColinearPoints_r( root, skipContents ); + MeltPortals_r( root, skipContents, vertexList ); + root->RemoveFlagRecurse( NODE_DONE ); + common->Printf( "\r%6d points inserted\n", numInsertedPoints ); +} diff --git a/neo/tools/compilers/aas/BrushBSP.h b/neo/tools/compilers/aas/BrushBSP.h new file mode 100644 index 00000000..a0139654 --- /dev/null +++ b/neo/tools/compilers/aas/BrushBSP.h @@ -0,0 +1,302 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __BRUSHBSP_H__ +#define __BRUSHBSP_H__ + +/* +=============================================================================== + + BrushBSP + +=============================================================================== +*/ + +class idBrushBSP; +class idBrushBSPNode; +class idBrushBSPPortal; + + +//=============================================================== +// +// idBrushBSPPortal +// +//=============================================================== + +class idBrushBSPPortal +{ + + friend class idBrushBSP; + friend class idBrushBSPNode; + +public: + idBrushBSPPortal( void ); + ~idBrushBSPPortal( void ); + void AddToNodes( idBrushBSPNode* front, idBrushBSPNode* back ); + void RemoveFromNode( idBrushBSPNode* l ); + void Flip( void ); + int Split( const idPlane& splitPlane, idBrushBSPPortal** front, idBrushBSPPortal** back ); + idWinding* GetWinding( void ) const + { + return winding; + } + const idPlane& GetPlane( void ) const + { + return plane; + } + void SetFaceNum( int num ) + { + faceNum = num; + } + int GetFaceNum( void ) const + { + return faceNum; + } + int GetFlags( void ) const + { + return flags; + } + void SetFlag( int flag ) + { + flags |= flag; + } + void RemoveFlag( int flag ) + { + flags &= ~flag; + } + idBrushBSPPortal* Next( int side ) const + { + return next[side]; + } + idBrushBSPNode* GetNode( int side ) const + { + return nodes[side]; + } + +private: + idPlane plane; // portal plane + int planeNum; // number of plane this portal is on + idWinding* winding; // portal winding + idBrushBSPNode* nodes[2]; // nodes this portal seperates + idBrushBSPPortal* next[2]; // next portal in list for both nodes + int flags; // portal flags + int faceNum; // number of the face created for this portal +}; + + +//=============================================================== +// +// idBrushBSPNode +// +//=============================================================== + +#define NODE_VISITED BIT(30) +#define NODE_DONE BIT(31) + +class idBrushBSPNode +{ + + friend class idBrushBSP; + friend class idBrushBSPPortal; + +public: + idBrushBSPNode( void ); + ~idBrushBSPNode( void ); + void SetContentsFromBrushes( void ); + idBounds GetPortalBounds( void ); + idBrushBSPNode* GetChild( int index ) const + { + return children[index]; + } + idBrushBSPNode* GetParent( void ) const + { + return parent; + } + void SetContents( int contents ) + { + this->contents = contents; + } + int GetContents( void ) const + { + return contents; + } + const idPlane& GetPlane( void ) const + { + return plane; + } + idBrushBSPPortal* GetPortals( void ) const + { + return portals; + } + void SetAreaNum( int num ) + { + areaNum = num; + } + int GetAreaNum( void ) const + { + return areaNum; + } + int GetFlags( void ) const + { + return flags; + } + void SetFlag( int flag ) + { + flags |= flag; + } + void RemoveFlag( int flag ) + { + flags &= ~flag; + } + bool TestLeafNode( void ); + // remove the flag from nodes found by flooding through portals to nodes with the flag set + void RemoveFlagFlood( int flag ); + // recurse down the tree and remove the flag from all visited nodes + void RemoveFlagRecurse( int flag ); + // first recurse down the tree and flood from there + void RemoveFlagRecurseFlood( int flag ); + // returns side of the plane the node is on + int PlaneSide( const idPlane& plane, float epsilon = ON_EPSILON ) const; + // split the leaf node with a plane + bool Split( const idPlane& splitPlane, int splitPlaneNum ); + + +private: + idPlane plane; // split plane if this is not a leaf node + idBrush* volume; // node volume + int contents; // node contents + idBrushList brushList; // list with brushes for this node + idBrushBSPNode* parent; // parent of this node + idBrushBSPNode* children[2]; // both are NULL if this is a leaf node + idBrushBSPPortal* portals; // portals of this node + int flags; // node flags + int areaNum; // number of the area created for this node + int occupied; // true when portal is occupied +}; + + +//=============================================================== +// +// idBrushBSP +// +//=============================================================== + +class idBrushBSP +{ + +public: + idBrushBSP( void ); + ~idBrushBSP( void ); + // build a bsp tree from a set of brushes + void Build( idBrushList brushList, int skipContents, + bool ( *ChopAllowed )( idBrush* b1, idBrush* b2 ), + bool ( *MergeAllowed )( idBrush* b1, idBrush* b2 ) ); + // remove splits in subspaces with the given contents + void PruneTree( int contents ); + // portalize the bsp tree + void Portalize( void ); + // remove subspaces outside the map not reachable by entities + bool RemoveOutside( const idMapFile* mapFile, int contents, const idStrList& classNames ); + // write file with a trace going through a leak + void LeakFile( const idStr& fileName ); + // try to merge portals + void MergePortals( int skipContents ); + // try to merge the two leaf nodes at either side of the portal + bool TryMergeLeafNodes( idBrushBSPPortal* portal, int side ); + void PruneMergedTree_r( idBrushBSPNode* node ); + // melt portal windings + void MeltPortals( int skipContents ); + // write a map file with a brush for every leaf node that has the given contents + void WriteBrushMap( const idStr& fileName, const idStr& ext, int contents ); + // bounds for the whole tree + const idBounds& GetTreeBounds( void ) const + { + return treeBounds; + } + // root node of the tree + idBrushBSPNode* GetRootNode( void ) const + { + return root; + } + +private: + idBrushBSPNode* root; + idBrushBSPNode* outside; + idBounds treeBounds; + idPlaneSet portalPlanes; + int numGridCells; + int numSplits; + int numGridCellSplits; + int numPrunedSplits; + int numPortals; + int solidLeafNodes; + int outsideLeafNodes; + int insideLeafNodes; + int numMergedPortals; + int numInsertedPoints; + idVec3 leakOrigin; + int brushMapContents; + idBrushMap* brushMap; + + bool ( *BrushChopAllowed )( idBrush* b1, idBrush* b2 ); + bool ( *BrushMergeAllowed )( idBrush* b1, idBrush* b2 ); + +private: + void RemoveMultipleLeafNodeReferences_r( idBrushBSPNode* node ); + void Free_r( idBrushBSPNode* node ); + void IncreaseNumSplits( void ); + bool IsValidSplitter( const idBrushSide* side ); + int BrushSplitterStats( const idBrush* brush, int planeNum, const idPlaneSet& planeList, bool* testedPlanes, struct splitterStats_s& stats ); + int FindSplitter( idBrushBSPNode* node, const idPlaneSet& planeList, bool* testedPlanes, struct splitterStats_s& bestStats ); + void SetSplitterUsed( idBrushBSPNode* node, int planeNum ); + idBrushBSPNode* BuildBrushBSP_r( idBrushBSPNode* node, const idPlaneSet& planeList, bool* testedPlanes, int skipContents ); + idBrushBSPNode* ProcessGridCell( idBrushBSPNode* node, int skipContents ); + void BuildGrid_r( idList& gridCells, idBrushBSPNode* node ); + void PruneTree_r( idBrushBSPNode* node, int contents ); + void MakeOutsidePortals( void ); + idWinding* BaseWindingForNode( idBrushBSPNode* node ); + void MakeNodePortal( idBrushBSPNode* node ); + void SplitNodePortals( idBrushBSPNode* node ); + void MakeTreePortals_r( idBrushBSPNode* node ); + void FloodThroughPortals_r( idBrushBSPNode* node, int contents, int depth ); + bool FloodFromOrigin( const idVec3& origin, int contents ); + bool FloodFromEntities( const idMapFile* mapFile, int contents, const idStrList& classNames ); + void RemoveOutside_r( idBrushBSPNode* node, int contents ); + void SetPortalPlanes_r( idBrushBSPNode* node, idPlaneSet& planeList ); + void SetPortalPlanes( void ); + void MergePortals_r( idBrushBSPNode* node, int skipContents ); + void MergeLeafNodePortals( idBrushBSPNode* node, int skipContents ); + void UpdateTreeAfterMerge_r( idBrushBSPNode* node, const idBounds& bounds, idBrushBSPNode* oldNode, idBrushBSPNode* newNode ); + void RemoveLeafNodeColinearPoints( idBrushBSPNode* node ); + void RemoveColinearPoints_r( idBrushBSPNode* node, int skipContents ); + void MeltFlood_r( idBrushBSPNode* node, int skipContents, idBounds& bounds, idVectorSet& vertexList ); + void MeltLeafNodePortals( idBrushBSPNode* node, int skipContents, idVectorSet& vertexList ); + void MeltPortals_r( idBrushBSPNode* node, int skipContents, idVectorSet& vertexList ); +}; + +#endif /* !__BRUSHBSP_H__ */ diff --git a/neo/tools/compilers/compiler_public.h b/neo/tools/compilers/compiler_public.h new file mode 100644 index 00000000..300784bc --- /dev/null +++ b/neo/tools/compilers/compiler_public.h @@ -0,0 +1,48 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __COMPILER_PUBLIC_H__ +#define __COMPILER_PUBLIC_H__ + +/* +=============================================================================== + + Compilers for map, aas, etc. processing. + +=============================================================================== +*/ + +// map processing (also see SuperOptimizeOccluders in tr_local.h) +void Dmap_f( const idCmdArgs& args ); + +// AAS file compiler +void RunAAS_f( const idCmdArgs& args ); +void RunAASDir_f( const idCmdArgs& args ); +void RunReach_f( const idCmdArgs& args ); + +#endif /* !__COMPILER_PUBLIC_H__ */ diff --git a/neo/tools/compilers/dmap/dmap.cpp b/neo/tools/compilers/dmap/dmap.cpp new file mode 100644 index 00000000..5ef55245 --- /dev/null +++ b/neo/tools/compilers/dmap/dmap.cpp @@ -0,0 +1,462 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "dmap.h" + +dmapGlobals_t dmapGlobals; + +/* +============ +ProcessModel +============ +*/ +bool ProcessModel( uEntity_t* e, bool floodFill ) +{ + bspface_t* faces; + + // build a bsp tree using all of the sides + // of all of the structural brushes + faces = MakeStructuralBspFaceList( e->primitives ); + e->tree = FaceBSP( faces ); + + // create portals at every leaf intersection + // to allow flood filling + MakeTreePortals( e->tree ); + + // classify the leafs as opaque or areaportal + FilterBrushesIntoTree( e ); + + // see if the bsp is completely enclosed + if( floodFill && !dmapGlobals.noFlood ) + { + if( FloodEntities( e->tree ) ) + { + // set the outside leafs to opaque + FillOutside( e ); + } + else + { + common->Printf( "**********************\n" ); + common->Warning( "******* leaked *******" ); + common->Printf( "**********************\n" ); + LeakFile( e->tree ); + // bail out here. If someone really wants to + // process a map that leaks, they should use + // -noFlood + return false; + } + } + + // get minimum convex hulls for each visible side + // this must be done before creating area portals, + // because the visible hull is used as the portal + ClipSidesByTree( e ); + + // determine areas before clipping tris into the + // tree, so tris will never cross area boundaries + FloodAreas( e ); + + // we now have a BSP tree with solid and non-solid leafs marked with areas + // all primitives will now be clipped into this, throwing away + // fragments in the solid areas + PutPrimitivesInAreas( e ); + + // now build shadow volumes for the lights and split + // the optimize lists by the light beam trees + // so there won't be unneeded overdraw in the static + // case + Prelight( e ); + + // optimizing is a superset of fixing tjunctions + if( !dmapGlobals.noOptimize ) + { + OptimizeEntity( e ); + } + else if( !dmapGlobals.noTJunc ) + { + FixEntityTjunctions( e ); + } + + // now fix t junctions across areas + FixGlobalTjunctions( e ); + + return true; +} + +/* +============ +ProcessModels +============ +*/ +bool ProcessModels( void ) +{ + bool oldVerbose; + uEntity_t* entity; + + oldVerbose = dmapGlobals.verbose; + + for( dmapGlobals.entityNum = 0 ; dmapGlobals.entityNum < dmapGlobals.num_entities ; dmapGlobals.entityNum++ ) + { + + entity = &dmapGlobals.uEntities[dmapGlobals.entityNum]; + if( !entity->primitives ) + { + continue; + } + + common->Printf( "############### entity %i ###############\n", dmapGlobals.entityNum ); + + // if we leaked, stop without any more processing + if( !ProcessModel( entity, ( bool )( dmapGlobals.entityNum == 0 ) ) ) + { + return false; + } + + // we usually don't want to see output for submodels unless + // something strange is going on + if( !dmapGlobals.verboseentities ) + { + dmapGlobals.verbose = false; + } + } + + dmapGlobals.verbose = oldVerbose; + + return true; +} + +/* +============ +DmapHelp +============ +*/ +void DmapHelp( void ) +{ + common->Printf( + + "Usage: dmap [options] mapfile\n" + "Options:\n" + "noCurves = don't process curves\n" + "noCM = don't create collision map\n" + "noAAS = don't create AAS files\n" + + ); +} + +/* +============ +ResetDmapGlobals +============ +*/ +void ResetDmapGlobals( void ) +{ + dmapGlobals.mapFileBase[0] = '\0'; + dmapGlobals.dmapFile = NULL; + dmapGlobals.mapPlanes.Clear(); + dmapGlobals.num_entities = 0; + dmapGlobals.uEntities = NULL; + dmapGlobals.entityNum = 0; + dmapGlobals.mapLights.Clear(); + dmapGlobals.verbose = false; + dmapGlobals.glview = false; + dmapGlobals.noOptimize = false; + dmapGlobals.verboseentities = false; + dmapGlobals.noCurves = false; + dmapGlobals.fullCarve = false; + dmapGlobals.noModelBrushes = false; + dmapGlobals.noTJunc = false; + dmapGlobals.nomerge = false; + dmapGlobals.noFlood = false; + dmapGlobals.noClipSides = false; + dmapGlobals.noLightCarve = false; + dmapGlobals.noShadow = false; + dmapGlobals.shadowOptLevel = SO_NONE; + dmapGlobals.drawBounds.Clear(); + dmapGlobals.drawflag = false; + dmapGlobals.totalShadowTriangles = 0; + dmapGlobals.totalShadowVerts = 0; +} + +/* +============ +Dmap +============ +*/ +void Dmap( const idCmdArgs& args ) +{ + int i; + int start, end; + char path[1024]; + idStr passedName; + bool leaked = false; + bool noCM = false; + bool noAAS = false; + + ResetDmapGlobals(); + + if( args.Argc() < 2 ) + { + DmapHelp(); + return; + } + + common->Printf( "---- dmap ----\n" ); + + dmapGlobals.fullCarve = true; + dmapGlobals.shadowOptLevel = SO_MERGE_SURFACES; // create shadows by merging all surfaces, but no super optimization +// dmapGlobals.shadowOptLevel = SO_CLIP_OCCLUDERS; // remove occluders that are completely covered +// dmapGlobals.shadowOptLevel = SO_SIL_OPTIMIZE; +// dmapGlobals.shadowOptLevel = SO_CULL_OCCLUDED; + + dmapGlobals.noLightCarve = true; + + for( i = 1 ; i < args.Argc() ; i++ ) + { + const char* s; + + s = args.Argv( i ); + if( s[0] == '-' ) + { + s++; + if( s[0] == '\0' ) + { + continue; + } + } + + if( !idStr::Icmp( s, "glview" ) ) + { + dmapGlobals.glview = true; + } + else if( !idStr::Icmp( s, "v" ) ) + { + common->Printf( "verbose = true\n" ); + dmapGlobals.verbose = true; + } + else if( !idStr::Icmp( s, "draw" ) ) + { + common->Printf( "drawflag = true\n" ); + dmapGlobals.drawflag = true; + } + else if( !idStr::Icmp( s, "noFlood" ) ) + { + common->Printf( "noFlood = true\n" ); + dmapGlobals.noFlood = true; + } + else if( !idStr::Icmp( s, "noLightCarve" ) ) + { + common->Printf( "noLightCarve = true\n" ); + dmapGlobals.noLightCarve = true; + } + else if( !idStr::Icmp( s, "lightCarve" ) ) + { + common->Printf( "noLightCarve = false\n" ); + dmapGlobals.noLightCarve = false; + } + else if( !idStr::Icmp( s, "noOpt" ) ) + { + common->Printf( "noOptimize = true\n" ); + dmapGlobals.noOptimize = true; + } + else if( !idStr::Icmp( s, "verboseentities" ) ) + { + common->Printf( "verboseentities = true\n" ); + dmapGlobals.verboseentities = true; + } + else if( !idStr::Icmp( s, "noCurves" ) ) + { + common->Printf( "noCurves = true\n" ); + dmapGlobals.noCurves = true; + } + else if( !idStr::Icmp( s, "noModels" ) ) + { + common->Printf( "noModels = true\n" ); + dmapGlobals.noModelBrushes = true; + } + else if( !idStr::Icmp( s, "noClipSides" ) ) + { + common->Printf( "noClipSides = true\n" ); + dmapGlobals.noClipSides = true; + } + else if( !idStr::Icmp( s, "noCarve" ) ) + { + common->Printf( "noCarve = true\n" ); + dmapGlobals.fullCarve = false; + } + else if( !idStr::Icmp( s, "shadowOpt" ) ) + { + dmapGlobals.shadowOptLevel = ( shadowOptLevel_t )atoi( args.Argv( i + 1 ) ); + common->Printf( "shadowOpt = %i\n", dmapGlobals.shadowOptLevel ); + i += 1; + } + else if( !idStr::Icmp( s, "noTjunc" ) ) + { + // triangle optimization won't work properly without tjunction fixing + common->Printf( "noTJunc = true\n" ); + dmapGlobals.noTJunc = true; + dmapGlobals.noOptimize = true; + common->Printf( "forcing noOptimize = true\n" ); + } + else if( !idStr::Icmp( s, "noCM" ) ) + { + noCM = true; + common->Printf( "noCM = true\n" ); + } + else if( !idStr::Icmp( s, "noAAS" ) ) + { + noAAS = true; + common->Printf( "noAAS = true\n" ); + } + else + { + break; + } + } + + if( i >= args.Argc() ) + { + common->Error( "usage: dmap [options] mapfile" ); + } + + passedName = args.Argv( i ); // may have an extension + passedName.BackSlashesToSlashes(); + if( passedName.Icmpn( "maps/", 4 ) != 0 ) + { + passedName = "maps/" + passedName; + } + + idStr stripped = passedName; + stripped.StripFileExtension(); + idStr::Copynz( dmapGlobals.mapFileBase, stripped, sizeof( dmapGlobals.mapFileBase ) ); + + bool region = false; + // if this isn't a regioned map, delete the last saved region map + if( passedName.Right( 4 ) != ".reg" ) + { + sprintf( path, "%s.reg", dmapGlobals.mapFileBase ); + fileSystem->RemoveFile( path ); + } + else + { + region = true; + } + + + passedName = stripped; + + // delete any old line leak files + sprintf( path, "%s.lin", dmapGlobals.mapFileBase ); + fileSystem->RemoveFile( path ); + + // delete any old generated binary proc files + idStr generated = va( "generated/%s.bproc", dmapGlobals.mapFileBase ); + fileSystem->RemoveFile( generated.c_str() ); + + // + // start from scratch + // + start = Sys_Milliseconds(); + + if( !LoadDMapFile( passedName ) ) + { + return; + } + + if( ProcessModels() ) + { + WriteOutputFile(); + } + else + { + leaked = true; + } + + FreeDMapFile(); + + common->Printf( "%i total shadow triangles\n", dmapGlobals.totalShadowTriangles ); + common->Printf( "%i total shadow verts\n", dmapGlobals.totalShadowVerts ); + + end = Sys_Milliseconds(); + common->Printf( "-----------------------\n" ); + common->Printf( "%5.0f seconds for dmap\n", ( end - start ) * 0.001f ); + + if( !leaked ) + { + + if( !noCM ) + { + + // make sure the collision model manager is not used by the game + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + + // create the collision map + start = Sys_Milliseconds(); + + collisionModelManager->LoadMap( dmapGlobals.dmapFile ); + collisionModelManager->FreeMap(); + + end = Sys_Milliseconds(); + common->Printf( "-------------------------------------\n" ); + common->Printf( "%5.0f seconds to create collision map\n", ( end - start ) * 0.001f ); + } + + if( !noAAS && !region ) + { + // create AAS files + RunAAS_f( args ); + } + } + + // free the common .map representation + delete dmapGlobals.dmapFile; + + // clear the map plane list + dmapGlobals.mapPlanes.Clear(); +} + +/* +============ +Dmap_f +============ +*/ +void Dmap_f( const idCmdArgs& args ) +{ + + common->ClearWarnings( "running dmap" ); + + // refresh the screen each time we print so it doesn't look + // like it is hung + common->SetRefreshOnPrint( true ); + Dmap( args ); + common->SetRefreshOnPrint( false ); + + common->PrintWarnings(); +} diff --git a/neo/tools/compilers/dmap/dmap.h b/neo/tools/compilers/dmap/dmap.h new file mode 100644 index 00000000..9e5a15e9 --- /dev/null +++ b/neo/tools/compilers/dmap/dmap.h @@ -0,0 +1,508 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../../../renderer/tr_local.h" + + +typedef struct primitive_s +{ + struct primitive_s* next; + + // only one of these will be non-NULL + struct bspbrush_s* brush; + struct mapTri_s* tris; +} primitive_t; + + +typedef struct +{ + struct optimizeGroup_s* groups; + // we might want to add other fields later +} uArea_t; + +typedef struct +{ + idMapEntity* mapEntity; // points into mapFile_t data + + idVec3 origin; + primitive_t* primitives; + struct tree_s* tree; + + int numAreas; + uArea_t* areas; +} uEntity_t; + + +// chains of mapTri_t are the general unit of processing +typedef struct mapTri_s +{ + struct mapTri_s* next; + + const idMaterial* material; + void* mergeGroup; // we want to avoid merging triangles + // from different fixed groups, like guiSurfs and mirrors + int planeNum; // not set universally, just in some areas + + idDrawVert v[3]; + const struct hashVert_s* hashVert[3]; + struct optVertex_s* optVert[3]; +} mapTri_t; + + +typedef struct +{ + int width, height; + idDrawVert* verts; +} mesh_t; + + +#define MAX_PATCH_SIZE 32 + +#define PLANENUM_LEAF -1 + +typedef struct parseMesh_s +{ + struct parseMesh_s* next; + mesh_t mesh; + const idMaterial* material; +} parseMesh_t; + +typedef struct bspface_s +{ + struct bspface_s* next; + int planenum; + bool portal; // all portals will be selected before + // any non-portals + bool checked; // used by SelectSplitPlaneNum() + idWinding* w; +} bspface_t; + +typedef struct +{ + idVec4 v[2]; // the offset value will always be in the 0.0 to 1.0 range +} textureVectors_t; + +typedef struct side_s +{ + int planenum; + + const idMaterial* material; + textureVectors_t texVec; + + idWinding* winding; // only clipped to the other sides of the brush + idWinding* visibleHull; // also clipped to the solid parts of the world +} side_t; + + +typedef struct bspbrush_s +{ + struct bspbrush_s* next; + struct bspbrush_s* original; // chopped up brushes will reference the originals + + int entitynum; // editor numbering for messages + int brushnum; // editor numbering for messages + + const idMaterial* contentShader; // one face's shader will determine the volume attributes + + int contents; + bool opaque; + int outputNumber; // set when the brush is written to the file list + + idBounds bounds; + int numsides; + side_t sides[6]; // variably sized +} uBrush_t; + + +typedef struct drawSurfRef_s +{ + struct drawSurfRef_s* nextRef; + int outputNumber; +} drawSurfRef_t; + + +typedef struct node_s +{ + // both leafs and nodes + int planenum; // -1 = leaf node + struct node_s* parent; + idBounds bounds; // valid after portalization + + // nodes only + side_t* side; // the side that created the node + struct node_s* children[2]; + int nodeNumber; // set after pruning + + // leafs only + bool opaque; // view can never be inside + + uBrush_t* brushlist; // fragments of all brushes in this leaf + // needed for FindSideForPortal + + int area; // determined by flood filling up to areaportals + int occupied; // 1 or greater can reach entity + uEntity_t* occupant; // for leak file testing + + struct uPortal_s* portals; // also on nodes during construction +} node_t; + + +typedef struct uPortal_s +{ + idPlane plane; + node_t* onnode; // NULL = outside box + node_t* nodes[2]; // [0] = front side of plane + struct uPortal_s* next[2]; + idWinding* winding; +} uPortal_t; + +// a tree_t is created by FaceBSP() +typedef struct tree_s +{ + node_t* headnode; + node_t outside_node; + idBounds bounds; +} tree_t; + +#define MAX_QPATH 256 // max length of a game pathname + +typedef struct +{ + idRenderLightLocal def; + char name[MAX_QPATH]; // for naming the shadow volume surface and interactions + srfTriangles_t* shadowTris; +} mapLight_t; + +#define MAX_GROUP_LIGHTS 16 + +typedef struct optimizeGroup_s +{ + struct optimizeGroup_s* nextGroup; + + idBounds bounds; // set in CarveGroupsByLight + + // all of these must match to add a triangle to the triList + bool smoothed; // curves will never merge with brushes + int planeNum; + int areaNum; + const idMaterial* material; + int numGroupLights; + mapLight_t* groupLights[MAX_GROUP_LIGHTS]; // lights effecting this list + void* mergeGroup; // if this differs (guiSurfs, mirrors, etc), the + // groups will not be combined into model surfaces + // after optimization + textureVectors_t texVec; + + bool surfaceEmited; + + mapTri_t* triList; + mapTri_t* regeneratedTris; // after each island optimization + idVec3 axis[2]; // orthogonal to the plane, so optimization can be 2D +} optimizeGroup_t; + +// all primitives from the map are added to optimzeGroups, creating new ones as needed +// each optimizeGroup is then split into the map areas, creating groups in each area +// each optimizeGroup is then divided by each light, creating more groups +// the final list of groups is then tjunction fixed against all groups, then optimized internally +// multiple optimizeGroups will be merged together into .proc surfaces, but no further optimization +// is done on them + + +//============================================================================= + +// dmap.cpp + +typedef enum +{ + SO_NONE, // 0 + SO_MERGE_SURFACES, // 1 + SO_CULL_OCCLUDED, // 2 + SO_CLIP_OCCLUDERS, // 3 + SO_CLIP_SILS, // 4 + SO_SIL_OPTIMIZE // 5 +} shadowOptLevel_t; + +typedef struct +{ + // mapFileBase will contain the qpath without any extension: "maps/test_box" + char mapFileBase[1024]; + + idMapFile* dmapFile; + + idPlaneSet mapPlanes; + + int num_entities; + uEntity_t* uEntities; + + int entityNum; + + idList mapLights; + + bool verbose; + + bool glview; + bool noOptimize; + bool verboseentities; + bool noCurves; + bool fullCarve; + bool noModelBrushes; + bool noTJunc; + bool nomerge; + bool noFlood; + bool noClipSides; // don't cut sides by solid leafs, use the entire thing + bool noLightCarve; // extra triangle subdivision by light frustums + shadowOptLevel_t shadowOptLevel; + bool noShadow; // don't create optimized shadow volumes + + idBounds drawBounds; + bool drawflag; + + int totalShadowTriangles; + int totalShadowVerts; +} dmapGlobals_t; + +extern dmapGlobals_t dmapGlobals; + +int FindFloatPlane( const idPlane& plane, bool* fixedDegeneracies = NULL ); + + +//============================================================================= + +// brush.cpp + +#ifndef CLIP_EPSILON +#define CLIP_EPSILON 0.1f +#endif + +#define PSIDE_FRONT 1 +#define PSIDE_BACK 2 +#define PSIDE_BOTH (PSIDE_FRONT|PSIDE_BACK) +#define PSIDE_FACING 4 + +int CountBrushList( uBrush_t* brushes ); +uBrush_t* AllocBrush( int numsides ); +void FreeBrush( uBrush_t* brushes ); +void FreeBrushList( uBrush_t* brushes ); +uBrush_t* CopyBrush( uBrush_t* brush ); +void DrawBrushList( uBrush_t* brush ); +void PrintBrush( uBrush_t* brush ); +bool BoundBrush( uBrush_t* brush ); +bool CreateBrushWindings( uBrush_t* brush ); +uBrush_t* BrushFromBounds( const idBounds& bounds ); +float BrushVolume( uBrush_t* brush ); +void WriteBspBrushMap( const char* name, uBrush_t* list ); + +void FilterBrushesIntoTree( uEntity_t* e ); + +void SplitBrush( uBrush_t* brush, int planenum, uBrush_t** front, uBrush_t** back ); +node_t* AllocNode( void ); + + +//============================================================================= + +// map.cpp + +bool LoadDMapFile( const char* filename ); +void FreeOptimizeGroupList( optimizeGroup_t* groups ); +void FreeDMapFile( void ); + +//============================================================================= + +// draw.cpp -- draw debug views either directly, or through glserv.exe + +void Draw_ClearWindow( void ); +void DrawWinding( const idWinding* w ); +void DrawAuxWinding( const idWinding* w ); + +void DrawLine( idVec3 v1, idVec3 v2, int color ); + +void GLS_BeginScene( void ); +void GLS_Winding( const idWinding* w, int code ); +void GLS_Triangle( const mapTri_t* tri, int code ); +void GLS_EndScene( void ); + + + +//============================================================================= + +// portals.cpp + +#define MAX_INTER_AREA_PORTALS 1024 + +typedef struct +{ + int area0, area1; + side_t* side; +} interAreaPortal_t; + +extern interAreaPortal_t interAreaPortals[MAX_INTER_AREA_PORTALS]; +extern int numInterAreaPortals; + +bool FloodEntities( tree_t* tree ); +void FillOutside( uEntity_t* e ); +void FloodAreas( uEntity_t* e ); +void MakeTreePortals( tree_t* tree ); +void FreePortal( uPortal_t* p ); + +//============================================================================= + +// glfile.cpp -- write a debug file to be viewd with glview.exe + +void OutputWinding( idWinding* w, idFile* glview ); +void WriteGLView( tree_t* tree, char* source ); + +//============================================================================= + +// leakfile.cpp + +void LeakFile( tree_t* tree ); + +//============================================================================= + +// facebsp.cpp + +tree_t* AllocTree( void ); + +void FreeTree( tree_t* tree ); + +void FreeTree_r( node_t* node ); +void FreeTreePortals_r( node_t* node ); + + +bspface_t* MakeStructuralBspFaceList( primitive_t* list ); +bspface_t* MakeVisibleBspFaceList( primitive_t* list ); +tree_t* FaceBSP( bspface_t* list ); + +//============================================================================= + +// surface.cpp + +mapTri_t* CullTrisInOpaqueLeafs( mapTri_t* triList, tree_t* tree ); +void ClipSidesByTree( uEntity_t* e ); +void SplitTrisToSurfaces( mapTri_t* triList, tree_t* tree ); +void PutPrimitivesInAreas( uEntity_t* e ); +void Prelight( uEntity_t* e ); + +//============================================================================= + +// tritjunction.cpp + +struct hashVert_s* GetHashVert( idVec3& v ); +void HashTriangles( optimizeGroup_t* groupList ); +void FreeTJunctionHash( void ); +int CountGroupListTris( const optimizeGroup_t* groupList ); +void FixEntityTjunctions( uEntity_t* e ); +void FixAreaGroupsTjunctions( optimizeGroup_t* groupList ); +void FixGlobalTjunctions( uEntity_t* e ); + +//============================================================================= + +// optimize.cpp -- trianlge mesh reoptimization + +// the shadow volume optimizer call internal optimizer routines, normal triangles +// will just be done by OptimizeEntity() + + +typedef struct optVertex_s +{ + idDrawVert v; + idVec3 pv; // projected against planar axis, third value is 0 + struct optEdge_s* edges; + struct optVertex_s* islandLink; + bool addedToIsland; + bool emited; // when regenerating triangles +} optVertex_t; + +typedef struct optEdge_s +{ + optVertex_t* v1, *v2; + struct optEdge_s* islandLink; + bool addedToIsland; + bool created; // not one of the original edges + bool combined; // combined from two or more colinear edges + struct optTri_s* frontTri, *backTri; + struct optEdge_s* v1link, *v2link; +} optEdge_t; + +typedef struct optTri_s +{ + struct optTri_s* next; + idVec3 midpoint; + optVertex_t* v[3]; + bool filled; +} optTri_t; + +typedef struct +{ + optimizeGroup_t* group; + optVertex_t* verts; + optEdge_t* edges; + optTri_t* tris; +} optIsland_t; + + +void OptimizeEntity( uEntity_t* e ); +void OptimizeGroupList( optimizeGroup_t* groupList ); + +//============================================================================= + +// tritools.cpp + +mapTri_t* AllocTri( void ); +void FreeTri( mapTri_t* tri ); +int CountTriList( const mapTri_t* list ); +mapTri_t* MergeTriLists( mapTri_t* a, mapTri_t* b ); +mapTri_t* CopyTriList( const mapTri_t* a ); +void FreeTriList( mapTri_t* a ); +mapTri_t* CopyMapTri( const mapTri_t* tri ); +float MapTriArea( const mapTri_t* tri ); +mapTri_t* RemoveBadTris( const mapTri_t* tri ); +void BoundTriList( const mapTri_t* list, idBounds& b ); +void DrawTri( const mapTri_t* tri ); +void FlipTriList( mapTri_t* tris ); +void TriVertsFromOriginal( mapTri_t* tri, const mapTri_t* original ); +void PlaneForTri( const mapTri_t* tri, idPlane& plane ); +idWinding* WindingForTri( const mapTri_t* tri ); +mapTri_t* WindingToTriList( const idWinding* w, const mapTri_t* originalTri ); +void ClipTriList( const mapTri_t* list, const idPlane& plane, float epsilon, mapTri_t** front, mapTri_t** back ); + +//============================================================================= + +// output.cpp + +srfTriangles_t* ShareMapTriVerts( const mapTri_t* tris ); +void WriteOutputFile( void ); + +//============================================================================= + +// shadowopt.cpp + +srfTriangles_t* CreateLightShadow( optimizeGroup_t* shadowerGroups, const mapLight_t* light ); +void FreeBeamTree( struct beamTree_s* beamTree ); + +void CarveTriByBeamTree( const struct beamTree_s* beamTree, const mapTri_t* tri, mapTri_t** lit, mapTri_t** unLit ); diff --git a/neo/tools/compilers/dmap/facebsp.cpp b/neo/tools/compilers/dmap/facebsp.cpp new file mode 100644 index 00000000..970fe454 --- /dev/null +++ b/neo/tools/compilers/dmap/facebsp.cpp @@ -0,0 +1,563 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "dmap.h" + +int c_faceLeafs; + + +extern int c_nodes; + +void RemovePortalFromNode( uPortal_t* portal, node_t* l ); + +node_t* NodeForPoint( node_t* node, idVec3 origin ) +{ + float d; + + while( node->planenum != PLANENUM_LEAF ) + { + idPlane& plane = dmapGlobals.mapPlanes[node->planenum]; + d = plane.Distance( origin ); + if( d >= 0 ) + { + node = node->children[0]; + } + else + { + node = node->children[1]; + } + } + + return node; +} + + + +/* +============= +FreeTreePortals_r +============= +*/ +void FreeTreePortals_r( node_t* node ) +{ + uPortal_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 ) +{ + // free children + if( node->planenum != PLANENUM_LEAF ) + { + FreeTree_r( node->children[0] ); + FreeTree_r( node->children[1] ); + } + + // free brushes + FreeBrushList( node->brushlist ); + + // free the node + c_nodes--; + Mem_Free( node ); +} + + +/* +============= +FreeTree +============= +*/ +void FreeTree( tree_t* tree ) +{ + if( !tree ) + { + return; + } + FreeTreePortals_r( tree->headnode ); + FreeTree_r( tree->headnode ); + Mem_Free( tree ); +} + +//=============================================================== + +void PrintTree_r( node_t* node, int depth ) +{ + int i; + uBrush_t* bb; + + for( i = 0 ; i < depth ; i++ ) + common->Printf( " " ); + if( node->planenum == PLANENUM_LEAF ) + { + if( !node->brushlist ) + common->Printf( "NULL\n" ); + else + { + for( bb = node->brushlist ; bb ; bb = bb->next ) + common->Printf( "%i ", bb->original->brushnum ); + common->Printf( "\n" ); + } + return; + } + + idPlane& plane = dmapGlobals.mapPlanes[node->planenum]; + common->Printf( "#%i (%5.2f %5.2f %5.2f %5.2f)\n", node->planenum, + plane[0], plane[1], plane[2], plane[3] ); + PrintTree_r( node->children[0], depth + 1 ); + PrintTree_r( node->children[1], depth + 1 ); +} + +/* +================ +AllocBspFace +================ +*/ +bspface_t* AllocBspFace( void ) +{ + bspface_t* f; + + f = ( bspface_t* )Mem_Alloc( sizeof( *f ), TAG_TOOLS ); + memset( f, 0, sizeof( *f ) ); + + return f; +} + +/* +================ +FreeBspFace +================ +*/ +void FreeBspFace( bspface_t* f ) +{ + if( f->w ) + { + delete f->w; + } + Mem_Free( f ); +} + + +/* +================ +SelectSplitPlaneNum +================ +*/ +#define BLOCK_SIZE 1024 +int SelectSplitPlaneNum( node_t* node, bspface_t* list ) +{ + bspface_t* split; + bspface_t* check; + bspface_t* bestSplit; + int splits, facing, front, back; + int side; + idPlane* mapPlane; + int value, bestValue; + idPlane plane; + int planenum; + bool havePortals; + float dist; + idVec3 halfSize; + + // if it is crossing a 1k block boundary, force a split + // this prevents epsilon problems from extending an + // arbitrary distance across the map + + halfSize = ( node->bounds[1] - node->bounds[0] ) * 0.5f; + for( int axis = 0; axis < 3; axis++ ) + { + if( halfSize[axis] > BLOCK_SIZE ) + { + dist = BLOCK_SIZE * ( floor( ( node->bounds[0][axis] + halfSize[axis] ) / BLOCK_SIZE ) + 1.0f ); + } + else + { + dist = BLOCK_SIZE * ( floor( node->bounds[0][axis] / BLOCK_SIZE ) + 1.0f ); + } + if( dist > node->bounds[0][axis] + 1.0f && dist < node->bounds[1][axis] - 1.0f ) + { + plane[0] = plane[1] = plane[2] = 0.0f; + plane[axis] = 1.0f; + plane[3] = -dist; + planenum = FindFloatPlane( plane ); + return planenum; + } + } + + // pick one of the face planes + // if we have any portal faces at all, only + // select from them, otherwise select from + // all faces + bestValue = -999999; + bestSplit = list; + + havePortals = false; + for( split = list ; split ; split = split->next ) + { + split->checked = false; + if( split->portal ) + { + havePortals = true; + } + } + + for( split = list ; split ; split = split->next ) + { + if( split->checked ) + { + continue; + } + if( havePortals != split->portal ) + { + continue; + } + mapPlane = &dmapGlobals.mapPlanes[ split->planenum ]; + splits = 0; + facing = 0; + front = 0; + back = 0; + for( check = list ; check ; check = check->next ) + { + if( check->planenum == split->planenum ) + { + facing++; + check->checked = true; // won't need to test this plane again + continue; + } + side = check->w->PlaneSide( *mapPlane ); + if( side == SIDE_CROSS ) + { + splits++; + } + else if( side == SIDE_FRONT ) + { + front++; + } + else if( side == SIDE_BACK ) + { + back++; + } + } + value = 5 * facing - 5 * splits; // - abs(front-back); + if( mapPlane->Type() < PLANETYPE_TRUEAXIAL ) + { + value += 5; // axial is better + } + + if( value > bestValue ) + { + bestValue = value; + bestSplit = split; + } + } + + if( bestValue == -999999 ) + { + return -1; + } + + return bestSplit->planenum; +} + +/* +================ +BuildFaceTree_r +================ +*/ +void BuildFaceTree_r( node_t* node, bspface_t* list ) +{ + bspface_t* split; + bspface_t* next; + int side; + bspface_t* newFace; + bspface_t* childLists[2]; + idWinding* frontWinding, *backWinding; + int i; + int splitPlaneNum; + + splitPlaneNum = SelectSplitPlaneNum( node, list ); + // if we don't have any more faces, this is a node + if( splitPlaneNum == -1 ) + { + node->planenum = PLANENUM_LEAF; + c_faceLeafs++; + return; + } + + // partition the list + node->planenum = splitPlaneNum; + idPlane& plane = dmapGlobals.mapPlanes[ splitPlaneNum ]; + childLists[0] = NULL; + childLists[1] = NULL; + for( split = list ; split ; split = next ) + { + next = split->next; + + if( split->planenum == node->planenum ) + { + FreeBspFace( split ); + continue; + } + + side = split->w->PlaneSide( plane ); + + if( side == SIDE_CROSS ) + { + split->w->Split( plane, CLIP_EPSILON * 2, &frontWinding, &backWinding ); + if( frontWinding ) + { + newFace = AllocBspFace(); + newFace->w = frontWinding; + newFace->next = childLists[0]; + newFace->planenum = split->planenum; + childLists[0] = newFace; + } + if( backWinding ) + { + newFace = AllocBspFace(); + newFace->w = backWinding; + newFace->next = childLists[1]; + newFace->planenum = split->planenum; + childLists[1] = newFace; + } + FreeBspFace( split ); + } + else if( side == SIDE_FRONT ) + { + split->next = childLists[0]; + childLists[0] = split; + } + else if( side == SIDE_BACK ) + { + split->next = childLists[1]; + childLists[1] = split; + } + } + + + // recursively process children + for( i = 0 ; i < 2 ; i++ ) + { + node->children[i] = AllocNode(); + node->children[i]->parent = node; + node->children[i]->bounds = node->bounds; + } + + // split the bounds if we have a nice axial plane + for( i = 0 ; i < 3 ; i++ ) + { + if( idMath::Fabs( plane[i] - 1.0 ) < 0.001 ) + { + node->children[0]->bounds[0][i] = plane.Dist(); + node->children[1]->bounds[1][i] = plane.Dist(); + break; + } + } + + for( i = 0 ; i < 2 ; i++ ) + { + BuildFaceTree_r( node->children[i], childLists[i] ); + } +} + + +/* +================ +FaceBSP + +List will be freed before returning +================ +*/ +tree_t* FaceBSP( bspface_t* list ) +{ + tree_t* tree; + bspface_t* face; + int i; + int count; + int start, end; + + start = Sys_Milliseconds(); + + common->Printf( "--- FaceBSP ---\n" ); + + tree = AllocTree(); + + count = 0; + tree->bounds.Clear(); + for( face = list ; face ; face = face->next ) + { + count++; + for( i = 0 ; i < face->w->GetNumPoints() ; i++ ) + { + tree->bounds.AddPoint( ( *face->w )[i].ToVec3() ); + } + } + common->Printf( "%5i faces\n", count ); + + tree->headnode = AllocNode(); + tree->headnode->bounds = tree->bounds; + c_faceLeafs = 0; + + BuildFaceTree_r( tree->headnode, list ); + + common->Printf( "%5i leafs\n", c_faceLeafs ); + + end = Sys_Milliseconds(); + + common->Printf( "%5.1f seconds faceBsp\n", ( end - start ) / 1000.0 ); + + return tree; +} + +//========================================================================== + +/* +================= +MakeStructuralBspFaceList +================= +*/ +bspface_t* MakeStructuralBspFaceList( primitive_t* list ) +{ + uBrush_t* b; + int i; + side_t* s; + idWinding* w; + bspface_t* f, *flist; + + flist = NULL; + for( ; list ; list = list->next ) + { + b = list->brush; + if( !b ) + { + continue; + } + if( !b->opaque && !( b->contents & CONTENTS_AREAPORTAL ) ) + { + continue; + } + for( i = 0 ; i < b->numsides ; i++ ) + { + s = &b->sides[i]; + w = s->winding; + if( !w ) + { + continue; + } + if( ( b->contents & CONTENTS_AREAPORTAL ) && !( s->material->GetContentFlags() & CONTENTS_AREAPORTAL ) ) + { + continue; + } + f = AllocBspFace(); + if( s->material->GetContentFlags() & CONTENTS_AREAPORTAL ) + { + f->portal = true; + } + f->w = w->Copy(); + f->planenum = s->planenum & ~1; + f->next = flist; + flist = f; + } + } + + return flist; +} + +/* +================= +MakeVisibleBspFaceList +================= +*/ +bspface_t* MakeVisibleBspFaceList( primitive_t* list ) +{ + uBrush_t* b; + int i; + side_t* s; + idWinding* w; + bspface_t* f, *flist; + + flist = NULL; + for( ; list ; list = list->next ) + { + b = list->brush; + if( !b ) + { + continue; + } + if( !b->opaque && !( b->contents & CONTENTS_AREAPORTAL ) ) + { + continue; + } + for( i = 0 ; i < b->numsides ; i++ ) + { + s = &b->sides[i]; + w = s->visibleHull; + if( !w ) + { + continue; + } + f = AllocBspFace(); + if( s->material->GetContentFlags() & CONTENTS_AREAPORTAL ) + { + f->portal = true; + } + f->w = w->Copy(); + f->planenum = s->planenum & ~1; + f->next = flist; + flist = f; + } + } + + return flist; +} + diff --git a/neo/tools/compilers/dmap/gldraw.cpp b/neo/tools/compilers/dmap/gldraw.cpp new file mode 100644 index 00000000..29e71044 --- /dev/null +++ b/neo/tools/compilers/dmap/gldraw.cpp @@ -0,0 +1,320 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "dmap.h" + +#if 0 +#include +#include +#include +//#include + +#define WIN_SIZE 1024 + +void Draw_ClearWindow( void ) +{ + + if( !dmapGlobals.drawflag ) + { + return; + } + + glDrawBuffer( GL_FRONT ); + + GL_Set2D(); + + glClearColor( 0.5, 0.5, 0.5, 0 ); + glClear( GL_COLOR_BUFFER_BIT ); + +#if 0 + int w, h, g; + float mx, my; + + w = ( dmapGlobals.drawBounds.b[1][0] - dmapGlobals.drawBounds.b[0][0] ); + h = ( dmapGlobals.drawBounds.b[1][1] - dmapGlobals.drawBounds.b[0][1] ); + + mx = dmapGlobals.drawBounds.b[0][0] + w / 2; + my = dmapGlobals.drawBounds.b[1][1] + h / 2; + + g = w > h ? w : h; + + glLoadIdentity(); + gluPerspective( 90, 1, 2, 16384 ); + gluLookAt( mx, my, draw_maxs[2] + g / 2, mx , my, draw_maxs[2], 0, 1, 0 ); +#else + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + glOrtho( dmapGlobals.drawBounds[0][0], dmapGlobals.drawBounds[1][0], + dmapGlobals.drawBounds[0][1], dmapGlobals.drawBounds[1][1], + -1, 1 ); + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); +#endif + glColor3f( 0, 0, 0 ); +// glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + glDisable( GL_DEPTH_TEST ); +// glEnable (GL_BLEND); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + +#if 0 +//glColor4f (1,0,0,0.5); +// glBegin( GL_LINE_LOOP ); + glBegin( GL_QUADS ); + + glVertex2f( dmapGlobals.drawBounds.b[0][0] + 20, dmapGlobals.drawBounds.b[0][1] + 20 ); + glVertex2f( dmapGlobals.drawBounds.b[1][0] - 20, dmapGlobals.drawBounds.b[0][1] + 20 ); + glVertex2f( dmapGlobals.drawBounds.b[1][0] - 20, dmapGlobals.drawBounds.b[1][1] - 20 ); + glVertex2f( dmapGlobals.drawBounds.b[0][0] + 20, dmapGlobals.drawBounds.b[1][1] - 20 ); + + glEnd(); +#endif + + glFlush(); + +} + +void Draw_SetRed( void ) +{ + if( !dmapGlobals.drawflag ) + return; + + glColor3f( 1, 0, 0 ); +} + +void Draw_SetGrey( void ) +{ + if( !dmapGlobals.drawflag ) + return; + + glColor3f( 0.5f, 0.5f, 0.5f ); +} + +void Draw_SetBlack( void ) +{ + if( !dmapGlobals.drawflag ) + return; + + glColor3f( 0.0f, 0.0f, 0.0f ); +} + +void DrawWinding( const idWinding* w ) +{ + int i; + + if( !dmapGlobals.drawflag ) + return; + + glColor3f( 0.3f, 0.0f, 0.0f ); + glBegin( GL_POLYGON ); + for( i = 0; i < w->GetNumPoints(); i++ ) + glVertex3f( ( *w )[i][0], ( *w )[i][1], ( *w )[i][2] ); + glEnd(); + + glColor3f( 1, 0, 0 ); + glBegin( GL_LINE_LOOP ); + for( i = 0; i < w->GetNumPoints(); i++ ) + glVertex3f( ( *w )[i][0], ( *w )[i][1], ( *w )[i][2] ); + glEnd(); + + glFlush(); +} + +void DrawAuxWinding( const idWinding* w ) +{ + int i; + + if( !dmapGlobals.drawflag ) + return; + + glColor3f( 0.0f, 0.3f, 0.0f ); + glBegin( GL_POLYGON ); + for( i = 0; i < w->GetNumPoints(); i++ ) + glVertex3f( ( *w )[i][0], ( *w )[i][1], ( *w )[i][2] ); + glEnd(); + + glColor3f( 0.0f, 1.0f, 0.0f ); + glBegin( GL_LINE_LOOP ); + for( i = 0; i < w->GetNumPoints(); i++ ) + glVertex3f( ( *w )[i][0], ( *w )[i][1], ( *w )[i][2] ); + glEnd(); + + glFlush(); +} + +void DrawLine( idVec3 v1, idVec3 v2, int color ) +{ + if( !dmapGlobals.drawflag ) + return; + + switch( color ) + { + case 0: + glColor3f( 0, 0, 0 ); + break; + case 1: + glColor3f( 0, 0, 1 ); + break; + case 2: + glColor3f( 0, 1, 0 ); + break; + case 3: + glColor3f( 0, 1, 1 ); + break; + case 4: + glColor3f( 1, 0, 0 ); + break; + case 5: + glColor3f( 1, 0, 1 ); + break; + case 6: + glColor3f( 1, 1, 0 ); + break; + case 7: + glColor3f( 1, 1, 1 ); + break; + } + + + glBegin( GL_LINES ); + + glVertex3fv( v1.ToFloatPtr() ); + glVertex3fv( v2.ToFloatPtr() ); + + glEnd(); + glFlush(); +} + +//============================================================ + +#define GLSERV_PORT 25001 + +bool wins_init; +int draw_socket; + +void GLS_BeginScene( void ) +{ + WSADATA winsockdata; + WORD wVersionRequested; + struct sockaddr_in address; + int r; + + if( !wins_init ) + { + wins_init = true; + + wVersionRequested = MAKEWORD( 1, 1 ); + + r = WSAStartup( MAKEWORD( 1, 1 ), &winsockdata ); + + if( r ) + common->Error( "Winsock initialization failed." ); + + } + + // connect a socket to the server + + draw_socket = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP ); + if( draw_socket == -1 ) + common->Error( "draw_socket failed" ); + + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl( INADDR_LOOPBACK ); + address.sin_port = GLSERV_PORT; + r = connect( draw_socket, ( struct sockaddr* )&address, sizeof( address ) ); + if( r == -1 ) + { + closesocket( draw_socket ); + draw_socket = 0; + } +} + +void GLS_Winding( const idWinding* w, int code ) +{ + byte buf[1024]; + int i, j; + + if( !draw_socket ) + return; + + ( ( int* )buf )[0] = w->GetNumPoints(); + ( ( int* )buf )[1] = code; + for( i = 0; i < w->GetNumPoints(); i++ ) + for( j = 0 ; j < 3 ; j++ ) + ( ( float* )buf )[2 + i * 3 + j] = ( *w )[i][j]; + + send( draw_socket, ( const char* )buf, w->GetNumPoints() * 12 + 8, 0 ); +} + +void GLS_Triangle( const mapTri_t* tri, int code ) +{ + idWinding w; + + w.SetNumPoints( 3 ); + w[0] = tri->v[0].xyz; + w[1] = tri->v[1].xyz; + w[2] = tri->v[2].xyz; + GLS_Winding( &w, code ); +} + +void GLS_EndScene( void ) +{ + closesocket( draw_socket ); + draw_socket = 0; +} + +#else + +void Draw_ClearWindow( void ) +{ +} + +void DrawWinding( const idWinding* w ) +{ +} + +void DrawAuxWinding( const idWinding* w ) +{ +} + +void GLS_Winding( const idWinding* w, int code ) +{ +} + +void GLS_BeginScene( void ) +{ +} + +void GLS_EndScene( void ) +{ +} + +#endif diff --git a/neo/tools/compilers/dmap/glfile.cpp b/neo/tools/compilers/dmap/glfile.cpp new file mode 100644 index 00000000..721374a7 --- /dev/null +++ b/neo/tools/compilers/dmap/glfile.cpp @@ -0,0 +1,165 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "dmap.h" + +int c_glfaces; + +int PortalVisibleSides( uPortal_t* p ) +{ + int fcon, bcon; + + if( !p->onnode ) + return 0; // outside + + fcon = p->nodes[0]->opaque; + bcon = p->nodes[1]->opaque; + + // same contents never create a face + if( fcon == bcon ) + return 0; + + if( !fcon ) + return 1; + if( !bcon ) + return 2; + return 0; +} + +void OutputWinding( idWinding* w, idFile* glview ) +{ + static int level = 128; + float light; + int i; + + glview->WriteFloatString( "%i\n", w->GetNumPoints() ); + level += 28; + light = ( level & 255 ) / 255.0; + for( i = 0; i < w->GetNumPoints(); i++ ) + { + glview->WriteFloatString( "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n", + ( *w )[i][0], + ( *w )[i][1], + ( *w )[i][2], + light, + light, + light ); + } + glview->WriteFloatString( "\n" ); +} + +/* +============= +OutputPortal +============= +*/ +void OutputPortal( uPortal_t* p, idFile* glview ) +{ + idWinding* w; + int sides; + + sides = PortalVisibleSides( p ); + if( !sides ) + { + return; + } + + c_glfaces++; + + w = p->winding; + + if( sides == 2 ) // back side + { + w = w->Reverse(); + } + + OutputWinding( w, glview ); + + if( sides == 2 ) + { + delete w; + } +} + +/* +============= +WriteGLView_r +============= +*/ +void WriteGLView_r( node_t* node, idFile* glview ) +{ + uPortal_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]; + } + } +} + +/* +============= +WriteGLView +============= +*/ +void WriteGLView( tree_t* tree, char* source ) +{ + idFile* glview; + + c_glfaces = 0; + common->Printf( "Writing %s\n", source ); + + glview = fileSystem->OpenExplicitFileWrite( source ); + if( !glview ) + { + common->Error( "Couldn't open %s", source ); + } + WriteGLView_r( tree->headnode, glview ); + fileSystem->CloseFile( glview ); + + common->Printf( "%5i c_glfaces\n", c_glfaces ); +} + diff --git a/neo/tools/compilers/dmap/leakfile.cpp b/neo/tools/compilers/dmap/leakfile.cpp new file mode 100644 index 00000000..8eecb943 --- /dev/null +++ b/neo/tools/compilers/dmap/leakfile.cpp @@ -0,0 +1,113 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "dmap.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 ) +{ + idVec3 mid; + FILE* linefile; + idStr filename; + idStr ospath; + node_t* node; + int count; + + if( !tree->outside_node.occupied ) + return; + + common->Printf( "--- LeakFile ---\n" ); + + // + // write the points to the file + // + sprintf( filename, "%s.lin", dmapGlobals.mapFileBase ); + ospath = fileSystem->RelativePathToOSPath( filename ); + linefile = fopen( ospath, "w" ); + if( !linefile ) + { + common->Error( "Couldn't open %s\n", filename.c_str() ); + } + + count = 0; + node = &tree->outside_node; + while( node->occupied > 1 ) + { + int next; + uPortal_t* p, *nextportal = NULL; + node_t* nextnode = NULL; + int s; + + // find the best portal exit + next = node->occupied; + for( 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; + mid = nextportal->winding->GetCenter(); + fprintf( linefile, "%f %f %f\n", mid[0], mid[1], mid[2] ); + count++; + } + // add the occupant center + node->occupant->mapEntity->epairs.GetVector( "origin", "", mid ); + + fprintf( linefile, "%f %f %f\n", mid[0], mid[1], mid[2] ); + common->Printf( "%5i point linefile\n", count + 1 ); + + fclose( linefile ); +} + diff --git a/neo/tools/compilers/dmap/map.cpp b/neo/tools/compilers/dmap/map.cpp new file mode 100644 index 00000000..ab496939 --- /dev/null +++ b/neo/tools/compilers/dmap/map.cpp @@ -0,0 +1,676 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "dmap.h" + +/* + + After parsing, there will be a list of entities that each has + a list of primitives. + + Primitives are either brushes, triangle soups, or model references. + + Curves are tesselated to triangle soups at load time, but model + references are + Brushes will have + + brushes, each of which has a side definition. + +*/ + +// +// private declarations +// + +#define MAX_BUILD_SIDES 300 + +static int entityPrimitive; // to track editor brush numbers +static int c_numMapPatches; +static int c_areaportals; + +static uEntity_t* uEntity; + +// brushes are parsed into a temporary array of sides, +// which will have duplicates removed before the final brush is allocated +static uBrush_t* buildBrush; + + +#define NORMAL_EPSILON 0.00001f +#define DIST_EPSILON 0.01f + + +/* +=========== +FindFloatPlane +=========== +*/ +int FindFloatPlane( const idPlane& plane, bool* fixedDegeneracies ) +{ + idPlane p = plane; + bool fixed = p.FixDegeneracies( DIST_EPSILON ); + if( fixed && fixedDegeneracies ) + { + *fixedDegeneracies = true; + } + return dmapGlobals.mapPlanes.FindPlane( p, NORMAL_EPSILON, DIST_EPSILON ); +} + +/* +=========== +SetBrushContents + +The contents on all sides of a brush should be the same +Sets contentsShader, contents, opaque +=========== +*/ +static void SetBrushContents( uBrush_t* b ) +{ + int contents, c2; + side_t* s; + int i; + bool mixed; + + s = &b->sides[0]; + contents = s->material->GetContentFlags(); + + b->contentShader = s->material; + mixed = false; + + // a brush is only opaque if all sides are opaque + b->opaque = true; + + for( i = 1 ; i < b->numsides ; i++, s++ ) + { + s = &b->sides[i]; + + if( !s->material ) + { + continue; + } + + c2 = s->material->GetContentFlags(); + if( c2 != contents ) + { + mixed = true; + contents |= c2; + } + + if( s->material->Coverage() != MC_OPAQUE ) + { + b->opaque = false; + } + } + + if( contents & CONTENTS_AREAPORTAL ) + { + c_areaportals++; + } + + b->contents = contents; +} + + +//============================================================================ + +/* +=============== +FreeBuildBrush +=============== +*/ +static void FreeBuildBrush( void ) +{ + int i; + + for( i = 0 ; i < buildBrush->numsides ; i++ ) + { + if( buildBrush->sides[i].winding ) + { + delete buildBrush->sides[i].winding; + } + } + buildBrush->numsides = 0; +} + +/* +=============== +FinishBrush + +Produces a final brush based on the buildBrush->sides array +and links it to the current entity +=============== +*/ +static uBrush_t* FinishBrush( void ) +{ + uBrush_t* b; + primitive_t* prim; + + // create windings for sides and bounds for brush + if( !CreateBrushWindings( buildBrush ) ) + { + // don't keep this brush + FreeBuildBrush(); + return NULL; + } + + if( buildBrush->contents & CONTENTS_AREAPORTAL ) + { + if( dmapGlobals.num_entities != 1 ) + { + common->Printf( "Entity %i, Brush %i: areaportals only allowed in world\n" + , dmapGlobals.num_entities - 1, entityPrimitive ); + FreeBuildBrush(); + return NULL; + } + } + + // keep it + b = CopyBrush( buildBrush ); + + FreeBuildBrush(); + + b->entitynum = dmapGlobals.num_entities - 1; + b->brushnum = entityPrimitive; + + b->original = b; + + prim = ( primitive_t* )Mem_Alloc( sizeof( *prim ), TAG_TOOLS ); + memset( prim, 0, sizeof( *prim ) ); + prim->next = uEntity->primitives; + uEntity->primitives = prim; + + prim->brush = b; + + return b; +} + +/* +================= +RemoveDuplicateBrushPlanes + +Returns false if the brush has a mirrored set of planes, +meaning it encloses no volume. +Also removes planes without any normal +================= +*/ +static bool RemoveDuplicateBrushPlanes( uBrush_t* b ) +{ + int i, j, k; + side_t* sides; + + sides = b->sides; + + for( i = 1 ; i < b->numsides ; i++ ) + { + + // check for a degenerate plane + if( sides[i].planenum == -1 ) + { + common->Printf( "Entity %i, Brush %i: degenerate plane\n" + , b->entitynum, b->brushnum ); + // remove it + for( k = i + 1 ; k < b->numsides ; k++ ) + { + sides[k - 1] = sides[k]; + } + b->numsides--; + i--; + continue; + } + + // check for duplication and mirroring + for( j = 0 ; j < i ; j++ ) + { + if( sides[i].planenum == sides[j].planenum ) + { + common->Printf( "Entity %i, Brush %i: duplicate plane\n" + , b->entitynum, b->brushnum ); + // remove the second duplicate + for( k = i + 1 ; k < b->numsides ; k++ ) + { + sides[k - 1] = sides[k]; + } + b->numsides--; + i--; + break; + } + + if( sides[i].planenum == ( sides[j].planenum ^ 1 ) ) + { + // mirror plane, brush is invalid + common->Printf( "Entity %i, Brush %i: mirrored plane\n" + , b->entitynum, b->brushnum ); + return false; + } + } + } + return true; +} + + +/* +================= +ParseBrush +================= +*/ +static void ParseBrush( const idMapBrush* mapBrush, int primitiveNum ) +{ + uBrush_t* b; + side_t* s; + const idMapBrushSide* ms; + int i; + bool fixedDegeneracies = false; + + buildBrush->entitynum = dmapGlobals.num_entities - 1; + buildBrush->brushnum = entityPrimitive; + buildBrush->numsides = mapBrush->GetNumSides(); + for( i = 0 ; i < mapBrush->GetNumSides() ; i++ ) + { + s = &buildBrush->sides[i]; + ms = mapBrush->GetSide( i ); + + memset( s, 0, sizeof( *s ) ); + s->planenum = FindFloatPlane( ms->GetPlane(), &fixedDegeneracies ); + s->material = declManager->FindMaterial( ms->GetMaterial() ); + ms->GetTextureVectors( s->texVec.v ); + // remove any integral shift, which will help with grouping + s->texVec.v[0][3] -= floor( s->texVec.v[0][3] ); + s->texVec.v[1][3] -= floor( s->texVec.v[1][3] ); + } + + // if there are mirrored planes, the entire brush is invalid + if( !RemoveDuplicateBrushPlanes( buildBrush ) ) + { + return; + } + + // get the content for the entire brush + SetBrushContents( buildBrush ); + + b = FinishBrush(); + if( !b ) + { + return; + } + + if( fixedDegeneracies && dmapGlobals.verboseentities ) + { + common->Warning( "brush %d has degenerate plane equations", primitiveNum ); + } +} + +/* +================ +ParseSurface +================ +*/ +static void ParseSurface( const idMapPatch* patch, const idSurface* surface, const idMaterial* material ) +{ + int i; + mapTri_t* tri; + primitive_t* prim; + + prim = ( primitive_t* )Mem_Alloc( sizeof( *prim ), TAG_TOOLS ); + memset( prim, 0, sizeof( *prim ) ); + prim->next = uEntity->primitives; + uEntity->primitives = prim; + + for( i = 0; i < surface->GetNumIndexes(); i += 3 ) + { + tri = AllocTri(); + tri->v[2] = ( *surface )[surface->GetIndexes()[i + 0]]; + tri->v[1] = ( *surface )[surface->GetIndexes()[i + 2]]; + tri->v[0] = ( *surface )[surface->GetIndexes()[i + 1]]; + tri->material = material; + tri->next = prim->tris; + prim->tris = tri; + } + + // set merge groups if needed, to prevent multiple sides from being + // merged into a single surface in the case of gui shaders, mirrors, and autosprites + if( material->IsDiscrete() ) + { + for( tri = prim->tris ; tri ; tri = tri->next ) + { + tri->mergeGroup = ( void* )patch; + } + } +} + +/* +================ +ParsePatch +================ +*/ +static void ParsePatch( const idMapPatch* patch, int primitiveNum ) +{ + const idMaterial* mat; + + if( dmapGlobals.noCurves ) + { + return; + } + + c_numMapPatches++; + + mat = declManager->FindMaterial( patch->GetMaterial() ); + + idSurface_Patch* cp = new idSurface_Patch( *patch ); + + if( patch->GetExplicitlySubdivided() ) + { + cp->SubdivideExplicit( patch->GetHorzSubdivisions(), patch->GetVertSubdivisions(), true ); + } + else + { + cp->Subdivide( DEFAULT_CURVE_MAX_ERROR, DEFAULT_CURVE_MAX_ERROR, DEFAULT_CURVE_MAX_LENGTH, true ); + } + + ParseSurface( patch, cp, mat ); + + delete cp; +} + +/* +================ +ProcessMapEntity +================ +*/ +static bool ProcessMapEntity( idMapEntity* mapEnt ) +{ + idMapPrimitive* prim; + + uEntity = &dmapGlobals.uEntities[dmapGlobals.num_entities]; + memset( uEntity, 0, sizeof( *uEntity ) ); + uEntity->mapEntity = mapEnt; + dmapGlobals.num_entities++; + + for( entityPrimitive = 0; entityPrimitive < mapEnt->GetNumPrimitives(); entityPrimitive++ ) + { + prim = mapEnt->GetPrimitive( entityPrimitive ); + + if( prim->GetType() == idMapPrimitive::TYPE_BRUSH ) + { + ParseBrush( static_cast( prim ), entityPrimitive ); + } + else if( prim->GetType() == idMapPrimitive::TYPE_PATCH ) + { + ParsePatch( static_cast( prim ), entityPrimitive ); + } + } + + // never put an origin on the world, even if the editor left one there + if( dmapGlobals.num_entities != 1 ) + { + uEntity->mapEntity->epairs.GetVector( "origin", "", uEntity->origin ); + } + + return true; +} + +//=================================================================== + +/* +============== +CreateMapLight + +============== +*/ +static void CreateMapLight( const idMapEntity* mapEnt ) +{ + mapLight_t* light; + bool dynamic; + + // designers can add the "noPrelight" flag to signal that + // the lights will move around, so we don't want + // to bother chopping up the surfaces under it or creating + // shadow volumes + mapEnt->epairs.GetBool( "noPrelight", "0", dynamic ); + if( dynamic ) + { + return; + } + + light = new mapLight_t; + light->name[0] = '\0'; + light->shadowTris = NULL; + + // parse parms exactly as the game do + // use the game's epair parsing code so + // we can use the same renderLight generation + gameEdit->ParseSpawnArgsToRenderLight( &mapEnt->epairs, &light->def.parms ); + + R_DeriveLightData( &light->def ); + + // get the name for naming the shadow surfaces + const char* name; + + mapEnt->epairs.GetString( "name", "", &name ); + + idStr::Copynz( light->name, name, sizeof( light->name ) ); + if( !light->name[0] ) + { + common->Error( "Light at (%f,%f,%f) didn't have a name", + light->def.parms.origin[0], light->def.parms.origin[1], light->def.parms.origin[2] ); + } +#if 0 + // use the renderer code to get the bounding planes for the light + // based on all the parameters + R_RenderLightFrustum( light->parms, light->frustum ); + light->lightShader = light->parms.shader; +#endif + + dmapGlobals.mapLights.Append( light ); + +} + +/* +============== +CreateMapLights + +============== +*/ +static void CreateMapLights( const idMapFile* dmapFile ) +{ + int i; + const idMapEntity* mapEnt; + const char* value; + + for( i = 0 ; i < dmapFile->GetNumEntities() ; i++ ) + { + mapEnt = dmapFile->GetEntity( i ); + mapEnt->epairs.GetString( "classname", "", &value ); + if( !idStr::Icmp( value, "light" ) ) + { + CreateMapLight( mapEnt ); + } + + } + +} + +/* +================ +LoadDMapFile +================ +*/ +bool LoadDMapFile( const char* filename ) +{ + primitive_t* prim; + idBounds mapBounds; + int brushes, triSurfs; + int i; + int size; + + common->Printf( "--- LoadDMapFile ---\n" ); + common->Printf( "loading %s\n", filename ); + + // load and parse the map file into canonical form + dmapGlobals.dmapFile = new idMapFile(); + if( !dmapGlobals.dmapFile->Parse( filename ) ) + { + delete dmapGlobals.dmapFile; + dmapGlobals.dmapFile = NULL; + common->Warning( "Couldn't load map file: '%s'", filename ); + return false; + } + + dmapGlobals.mapPlanes.Clear(); + dmapGlobals.mapPlanes.SetGranularity( 1024 ); + + // process the canonical form into utility form + dmapGlobals.num_entities = 0; + c_numMapPatches = 0; + c_areaportals = 0; + + size = dmapGlobals.dmapFile->GetNumEntities() * sizeof( dmapGlobals.uEntities[0] ); + dmapGlobals.uEntities = ( uEntity_t* )Mem_Alloc( size, TAG_TOOLS ); + memset( dmapGlobals.uEntities, 0, size ); + + // allocate a very large temporary brush for building + // the brushes as they are loaded + buildBrush = AllocBrush( MAX_BUILD_SIDES ); + + for( i = 0 ; i < dmapGlobals.dmapFile->GetNumEntities() ; i++ ) + { + ProcessMapEntity( dmapGlobals.dmapFile->GetEntity( i ) ); + } + + CreateMapLights( dmapGlobals.dmapFile ); + + brushes = 0; + triSurfs = 0; + + mapBounds.Clear(); + for( prim = dmapGlobals.uEntities[0].primitives ; prim ; prim = prim->next ) + { + if( prim->brush ) + { + brushes++; + mapBounds.AddBounds( prim->brush->bounds ); + } + else if( prim->tris ) + { + triSurfs++; + } + } + + common->Printf( "%5i total world brushes\n", brushes ); + common->Printf( "%5i total world triSurfs\n", triSurfs ); + common->Printf( "%5i patches\n", c_numMapPatches ); + common->Printf( "%5i entities\n", dmapGlobals.num_entities ); + common->Printf( "%5i planes\n", dmapGlobals.mapPlanes.Num() ); + common->Printf( "%5i areaportals\n", c_areaportals ); + common->Printf( "size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", mapBounds[0][0], mapBounds[0][1], mapBounds[0][2], + mapBounds[1][0], mapBounds[1][1], mapBounds[1][2] ); + + return true; +} + +/* +================ +FreeOptimizeGroupList +================ +*/ +void FreeOptimizeGroupList( optimizeGroup_t* groups ) +{ + optimizeGroup_t* next; + + for( ; groups ; groups = next ) + { + next = groups->nextGroup; + FreeTriList( groups->triList ); + Mem_Free( groups ); + } +} + +/* +================ +FreeDMapFile +================ +*/ +void FreeDMapFile( void ) +{ + int i, j; + + FreeBrush( buildBrush ); + buildBrush = NULL; + + // free the entities and brushes + for( i = 0 ; i < dmapGlobals.num_entities ; i++ ) + { + uEntity_t* ent; + primitive_t* prim, *nextPrim; + + ent = &dmapGlobals.uEntities[i]; + + FreeTree( ent->tree ); + + // free primitives + for( prim = ent->primitives ; prim ; prim = nextPrim ) + { + nextPrim = prim->next; + if( prim->brush ) + { + FreeBrush( prim->brush ); + } + if( prim->tris ) + { + FreeTriList( prim->tris ); + } + Mem_Free( prim ); + } + + // free area surfaces + if( ent->areas ) + { + for( j = 0 ; j < ent->numAreas ; j++ ) + { + uArea_t* area; + + area = &ent->areas[j]; + FreeOptimizeGroupList( area->groups ); + + } + Mem_Free( ent->areas ); + } + } + + Mem_Free( dmapGlobals.uEntities ); + + dmapGlobals.num_entities = 0; + + // free the map lights + for( i = 0; i < dmapGlobals.mapLights.Num(); i++ ) + { + R_FreeLightDefDerivedData( &dmapGlobals.mapLights[i]->def ); + } + dmapGlobals.mapLights.DeleteContents( true ); +} diff --git a/neo/tools/compilers/dmap/optimize.cpp b/neo/tools/compilers/dmap/optimize.cpp new file mode 100644 index 00000000..0f469154 --- /dev/null +++ b/neo/tools/compilers/dmap/optimize.cpp @@ -0,0 +1,2223 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +//#pragma optimize( "", off ) + +#include "dmap.h" +#ifdef WIN32 +#include +#include +#endif + +/* + + New vertexes will be created where edges cross. + + optimization requires an accurate t junction fixer. + + + +*/ + +idBounds optBounds; + +#define MAX_OPT_VERTEXES 0x10000 +int numOptVerts; +optVertex_t optVerts[MAX_OPT_VERTEXES]; + +#define MAX_OPT_EDGES 0x40000 +static int numOptEdges; +static optEdge_t optEdges[MAX_OPT_EDGES]; + +static bool IsTriangleValid( const optVertex_t* v1, const optVertex_t* v2, const optVertex_t* v3 ); +static bool IsTriangleDegenerate( const optVertex_t* v1, const optVertex_t* v2, const optVertex_t* v3 ); + +static idRandom orandom; + +/* +============== +ValidateEdgeCounts +============== +*/ +static void ValidateEdgeCounts( optIsland_t* island ) +{ + optVertex_t* vert; + optEdge_t* e; + int c; + + for( vert = island->verts ; vert ; vert = vert->islandLink ) + { + c = 0; + for( e = vert->edges ; e ; ) + { + c++; + if( e->v1 == vert ) + { + e = e->v1link; + } + else if( e->v2 == vert ) + { + e = e->v2link; + } + else + { + common->Error( "ValidateEdgeCounts: mislinked" ); + } + } + if( c != 2 && c != 0 ) + { + // this can still happen at diamond intersections +// common->Printf( "ValidateEdgeCounts: %i edges\n", c ); + } + } +} + + +/* +==================== +AllocEdge +==================== +*/ +static optEdge_t* AllocEdge( void ) +{ + optEdge_t* e; + + if( numOptEdges == MAX_OPT_EDGES ) + { + common->Error( "MAX_OPT_EDGES" ); + } + e = &optEdges[ numOptEdges ]; + numOptEdges++; + memset( e, 0, sizeof( *e ) ); + + return e; +} + +/* +==================== +RemoveEdgeFromVert +==================== +*/ +static void RemoveEdgeFromVert( optEdge_t* e1, optVertex_t* vert ) +{ + optEdge_t** prev; + optEdge_t* e; + + if( !vert ) + { + return; + } + prev = &vert->edges; + while( *prev ) + { + e = *prev; + if( e == e1 ) + { + if( e1->v1 == vert ) + { + *prev = e1->v1link; + } + else if( e1->v2 == vert ) + { + *prev = e1->v2link; + } + else + { + common->Error( "RemoveEdgeFromVert: vert not found" ); + } + return; + } + + if( e->v1 == vert ) + { + prev = &e->v1link; + } + else if( e->v2 == vert ) + { + prev = &e->v2link; + } + else + { + common->Error( "RemoveEdgeFromVert: vert not found" ); + } + } +} + +/* +==================== +UnlinkEdge +==================== +*/ +static void UnlinkEdge( optEdge_t* e, optIsland_t* island ) +{ + optEdge_t** prev; + + RemoveEdgeFromVert( e, e->v1 ); + RemoveEdgeFromVert( e, e->v2 ); + + for( prev = &island->edges ; *prev ; prev = &( *prev )->islandLink ) + { + if( *prev == e ) + { + *prev = e->islandLink; + return; + } + } + + common->Error( "RemoveEdgeFromIsland: couldn't free edge" ); +} + + +/* +==================== +LinkEdge +==================== +*/ +static void LinkEdge( optEdge_t* e ) +{ + e->v1link = e->v1->edges; + e->v1->edges = e; + + e->v2link = e->v2->edges; + e->v2->edges = e; +} + +/* +================ +FindOptVertex +================ +*/ +static optVertex_t* FindOptVertex( idDrawVert* v, optimizeGroup_t* opt ) +{ + int i; + float x, y; + optVertex_t* vert; + + // deal with everything strictly as 2D + x = v->xyz * opt->axis[0]; + y = v->xyz * opt->axis[1]; + + // should we match based on the t-junction fixing hash verts? + for( i = 0 ; i < numOptVerts ; i++ ) + { + if( optVerts[i].pv[0] == x && optVerts[i].pv[1] == y ) + { + return &optVerts[i]; + } + } + + if( numOptVerts >= MAX_OPT_VERTEXES ) + { + common->Error( "MAX_OPT_VERTEXES" ); + return NULL; + } + + numOptVerts++; + + vert = &optVerts[i]; + memset( vert, 0, sizeof( *vert ) ); + vert->v = *v; + vert->pv[0] = x; + vert->pv[1] = y; + vert->pv[2] = 0; + + optBounds.AddPoint( vert->pv ); + + return vert; +} + +/* +================ +DrawAllEdges +================ +*/ +static void DrawAllEdges( void ) +{ +// int i; + + if( !dmapGlobals.drawflag ) + { + return; + } + +#if 0 + Draw_ClearWindow(); + + qglBegin( GL_LINES ); + for( i = 0 ; i < numOptEdges ; i++ ) + { + if( optEdges[i].v1 == NULL ) + { + continue; + } + qglColor3f( 1, 0, 0 ); + qglVertex3fv( optEdges[i].v1->pv.ToFloatPtr() ); + qglColor3f( 0, 0, 0 ); + qglVertex3fv( optEdges[i].v2->pv.ToFloatPtr() ); + } + qglEnd(); + qglFlush(); + +// GLimp_SwapBuffers(); +#endif +} + +/* +================ +DrawVerts +================ +*/ +static void DrawVerts( optIsland_t* island ) +{ +// optVertex_t *vert; + + if( !dmapGlobals.drawflag ) + { + return; + } + +#if 0 + qglEnable( GL_BLEND ); + qglBlendFunc( GL_ONE, GL_ONE ); + qglColor3f( 0.3f, 0.3f, 0.3f ); + qglPointSize( 3 ); + qglBegin( GL_POINTS ); + for( vert = island->verts ; vert ; vert = vert->islandLink ) + { + qglVertex3fv( vert->pv.ToFloatPtr() ); + } + qglEnd(); + qglDisable( GL_BLEND ); + qglFlush(); +#endif +} + +/* +================ +DrawEdges +================ +*/ +static void DrawEdges( optIsland_t* island ) +{ +// optEdge_t *edge; + + if( !dmapGlobals.drawflag ) + { + return; + } + +#if 0 + Draw_ClearWindow(); + + qglBegin( GL_LINES ); + for( edge = island->edges ; edge ; edge = edge->islandLink ) + { + if( edge->v1 == NULL ) + { + continue; + } + qglColor3f( 1, 0, 0 ); + qglVertex3fv( edge->v1->pv.ToFloatPtr() ); + qglColor3f( 0, 0, 0 ); + qglVertex3fv( edge->v2->pv.ToFloatPtr() ); + } + qglEnd(); + qglFlush(); + +// GLimp_SwapBuffers(); +#endif +} + +//================================================================= + +/* +================= +VertexBetween +================= +*/ +static bool VertexBetween( const optVertex_t* p1, const optVertex_t* v1, const optVertex_t* v2 ) +{ + idVec3 d1, d2; + float d; + + d1 = p1->pv - v1->pv; + d2 = p1->pv - v2->pv; + d = d1 * d2; + if( d < 0 ) + { + return true; + } + return false; +} + + +/* +==================== +EdgeIntersection + +Creates a new optVertex_t where the line segments cross. +This should only be called if PointsStraddleLine returned true + +Will return NULL if the lines are colinear +==================== +*/ +static optVertex_t* EdgeIntersection( const optVertex_t* p1, const optVertex_t* p2, + const optVertex_t* l1, const optVertex_t* l2, optimizeGroup_t* opt ) +{ + float f; + idDrawVert* v; + idVec3 dir1, dir2, cross1, cross2; + + dir1 = p1->pv - l1->pv; + dir2 = p1->pv - l2->pv; + cross1 = dir1.Cross( dir2 ); + + dir1 = p2->pv - l1->pv; + dir2 = p2->pv - l2->pv; + cross2 = dir1.Cross( dir2 ); + + if( cross1[2] - cross2[2] == 0 ) + { + return NULL; + } + + f = cross1[2] / ( cross1[2] - cross2[2] ); + + // FIXME: how are we freeing this, since it doesn't belong to a tri? + v = ( idDrawVert* )Mem_Alloc( sizeof( *v ), TAG_TOOLS ); + memset( v, 0, sizeof( *v ) ); + + v->xyz = p1->v.xyz * ( 1.0 - f ) + p2->v.xyz * f; + idVec3 normal = p1->v.GetNormal() * ( 1.0 - f ) + p2->v.GetNormal() * f; + normal.Normalize(); + v->SetNormal( normal ); + + idVec2 st; + st.x = p1->v.GetTexCoordS() * ( 1.0 - f ) + p2->v.GetTexCoordS() * f; + st.y = p1->v.GetTexCoordT() * ( 1.0 - f ) + p2->v.GetTexCoordT() * f; + v->SetTexCoord( st ); + + return FindOptVertex( v, opt ); +} + + +/* +==================== +PointsStraddleLine + +Colinear is considdered crossing. +==================== +*/ +static bool PointsStraddleLine( optVertex_t* p1, optVertex_t* p2, optVertex_t* l1, optVertex_t* l2 ) +{ + bool t1, t2; + + t1 = IsTriangleDegenerate( l1, l2, p1 ); + t2 = IsTriangleDegenerate( l1, l2, p2 ); + if( t1 && t2 ) + { + // colinear case + float s1, s2, s3, s4; + bool positive, negative; + + s1 = ( p1->pv - l1->pv ) * ( l2->pv - l1->pv ); + s2 = ( p2->pv - l1->pv ) * ( l2->pv - l1->pv ); + s3 = ( p1->pv - l2->pv ) * ( l2->pv - l1->pv ); + s4 = ( p2->pv - l2->pv ) * ( l2->pv - l1->pv ); + + if( s1 > 0 || s2 > 0 || s3 > 0 || s4 > 0 ) + { + positive = true; + } + else + { + positive = false; + } + if( s1 < 0 || s2 < 0 || s3 < 0 || s4 < 0 ) + { + negative = true; + } + else + { + negative = false; + } + + if( positive && negative ) + { + return true; + } + return false; + } + else if( p1 != l1 && p1 != l2 && p2 != l1 && p2 != l2 ) + { + // no shared verts + t1 = IsTriangleValid( l1, l2, p1 ); + t2 = IsTriangleValid( l1, l2, p2 ); + if( t1 && t2 ) + { + return false; + } + + t1 = IsTriangleValid( l1, p1, l2 ); + t2 = IsTriangleValid( l1, p2, l2 ); + if( t1 && t2 ) + { + return false; + } + + return true; + } + else + { + // a shared vert, not colinear, so not crossing + return false; + } +} + + +/* +==================== +EdgesCross +==================== +*/ +static bool EdgesCross( optVertex_t* a1, optVertex_t* a2, optVertex_t* b1, optVertex_t* b2 ) +{ + // if both verts match, consider it to be crossed + if( a1 == b1 && a2 == b2 ) + { + return true; + } + if( a1 == b2 && a2 == b1 ) + { + return true; + } + // if only one vert matches, it might still be colinear, which + // would be considered crossing + + // if both lines' verts are on opposite sides of the other + // line, it is crossed + if( !PointsStraddleLine( a1, a2, b1, b2 ) ) + { + return false; + } + if( !PointsStraddleLine( b1, b2, a1, a2 ) ) + { + return false; + } + + return true; +} + +/* +==================== +TryAddNewEdge + +==================== +*/ +static bool TryAddNewEdge( optVertex_t* v1, optVertex_t* v2, optIsland_t* island ) +{ + optEdge_t* e; + + // if the new edge crosses any other edges, don't add it + for( e = island->edges ; e ; e = e->islandLink ) + { + if( EdgesCross( e->v1, e->v2, v1, v2 ) ) + { + return false; + } + } + + if( dmapGlobals.drawflag ) + { +#if 0 + qglBegin( GL_LINES ); + qglColor3f( 0, ( 128 + orandom.RandomInt( 127 ) ) / 255.0, 0 ); + qglVertex3fv( v1->pv.ToFloatPtr() ); + qglVertex3fv( v2->pv.ToFloatPtr() ); + qglEnd(); + qglFlush(); +#endif + } + // add it + e = AllocEdge(); + + e->islandLink = island->edges; + island->edges = e; + e->v1 = v1; + e->v2 = v2; + + e->created = true; + + // link the edge to its verts + LinkEdge( e ); + + return true; +} + +typedef struct +{ + optVertex_t* v1, *v2; + float length; +} edgeLength_t; + + +static int LengthSort( const void* a, const void* b ) +{ + const edgeLength_t* ea, *eb; + + ea = ( const edgeLength_t* )a; + eb = ( const edgeLength_t* )b; + if( ea->length < eb->length ) + { + return -1; + } + if( ea->length > eb->length ) + { + return 1; + } + return 0; +} + +/* +================== +AddInteriorEdges + +Add all possible edges between the verts +================== +*/ +static void AddInteriorEdges( optIsland_t* island ) +{ + int c_addedEdges; + optVertex_t* vert, *vert2; + int c_verts; + edgeLength_t* lengths; + int numLengths; + int i; + + DrawVerts( island ); + + // count the verts + c_verts = 0; + for( vert = island->verts ; vert ; vert = vert->islandLink ) + { + if( !vert->edges ) + { + continue; + } + c_verts++; + } + + // allocate space for all the lengths + lengths = ( edgeLength_t* )Mem_Alloc( sizeof( *lengths ) * c_verts * c_verts / 2, TAG_TOOLS ); + numLengths = 0; + for( vert = island->verts ; vert ; vert = vert->islandLink ) + { + if( !vert->edges ) + { + continue; + } + for( vert2 = vert->islandLink ; vert2 ; vert2 = vert2->islandLink ) + { + idVec3 dir; + + if( !vert2->edges ) + { + continue; + } + lengths[numLengths].v1 = vert; + lengths[numLengths].v2 = vert2; + dir = ( vert->pv - vert2->pv ) ; + lengths[numLengths].length = dir.Length(); + numLengths++; + } + } + + + // sort by length, shortest first + qsort( lengths, numLengths, sizeof( lengths[0] ), LengthSort ); + + // try to create them in that order + c_addedEdges = 0; + for( i = 0 ; i < numLengths ; i++ ) + { + if( TryAddNewEdge( lengths[i].v1, lengths[i].v2, island ) ) + { + c_addedEdges++; + } + } + + if( dmapGlobals.verbose ) + { + common->Printf( "%6i tested segments\n", numLengths ); + common->Printf( "%6i added interior edges\n", c_addedEdges ); + } + + Mem_Free( lengths ); +} + + + +//================================================================== + +/* +==================== +RemoveIfColinear + +==================== +*/ +#define COLINEAR_EPSILON 0.1 +static void RemoveIfColinear( optVertex_t* ov, optIsland_t* island ) +{ + optEdge_t* e, *e1, *e2; + optVertex_t* v1 = NULL, *v2 = NULL, *v3 = NULL; + idVec3 dir1, dir2; + float len, dist; + idVec3 point; + idVec3 offset; + float off; + + v2 = ov; + + // we must find exactly two edges before testing for colinear + e1 = NULL; + e2 = NULL; + for( e = ov->edges ; e ; ) + { + if( !e1 ) + { + e1 = e; + } + else if( !e2 ) + { + e2 = e; + } + else + { + return; // can't remove a vertex with three edges + } + if( e->v1 == v2 ) + { + e = e->v1link; + } + else if( e->v2 == v2 ) + { + e = e->v2link; + } + else + { + common->Error( "RemoveIfColinear: mislinked edge" ); + } + } + + // can't remove if no edges + if( !e1 ) + { + return; + } + + if( !e2 ) + { + // this may still happen legally when a tiny triangle is + // the only thing in a group + common->Printf( "WARNING: vertex with only one edge\n" ); + return; + } + + if( e1->v1 == v2 ) + { + v1 = e1->v2; + } + else if( e1->v2 == v2 ) + { + v1 = e1->v1; + } + else + { + common->Error( "RemoveIfColinear: mislinked edge" ); + } + if( e2->v1 == v2 ) + { + v3 = e2->v2; + } + else if( e2->v2 == v2 ) + { + v3 = e2->v1; + } + else + { + common->Error( "RemoveIfColinear: mislinked edge" ); + } + + if( v1 == v3 ) + { + common->Error( "RemoveIfColinear: mislinked edge" ); + } + + // they must point in opposite directions + dist = ( v3->pv - v2->pv ) * ( v1->pv - v2->pv ); + if( dist >= 0 ) + { + return; + } + + // see if they are colinear + dir1 = v3->v.xyz - v1->v.xyz; + len = dir1.Normalize(); + dir2 = v2->v.xyz - v1->v.xyz; + dist = dir2 * dir1; + VectorMA( v1->v.xyz, dist, dir1, point ); + offset = point - v2->v.xyz; + off = offset.Length(); + + if( off > COLINEAR_EPSILON ) + { + return; + } + + if( dmapGlobals.drawflag ) + { +#if 0 + qglBegin( GL_LINES ); + qglColor3f( 1, 1, 0 ); + qglVertex3fv( v1->pv.ToFloatPtr() ); + qglVertex3fv( v2->pv.ToFloatPtr() ); + qglEnd(); + qglFlush(); + qglBegin( GL_LINES ); + qglColor3f( 0, 1, 1 ); + qglVertex3fv( v2->pv.ToFloatPtr() ); + qglVertex3fv( v3->pv.ToFloatPtr() ); + qglEnd(); + qglFlush(); +#endif + } + + // replace the two edges with a single edge + UnlinkEdge( e1, island ); + UnlinkEdge( e2, island ); + + // v2 should have no edges now + if( v2->edges ) + { + common->Error( "RemoveIfColinear: didn't remove properly" ); + } + + + // if there is an existing edge that already + // has these exact verts, we have just collapsed a + // sliver triangle out of existance, and all the edges + // can be removed + for( e = island->edges ; e ; e = e->islandLink ) + { + if( ( e->v1 == v1 && e->v2 == v3 ) + || ( e->v1 == v3 && e->v2 == v1 ) ) + { + UnlinkEdge( e, island ); + RemoveIfColinear( v1, island ); + RemoveIfColinear( v3, island ); + return; + } + } + + // if we can't add the combined edge, link + // the originals back in + if( !TryAddNewEdge( v1, v3, island ) ) + { + e1->islandLink = island->edges; + island->edges = e1; + LinkEdge( e1 ); + + e2->islandLink = island->edges; + island->edges = e2; + LinkEdge( e2 ); + return; + } + + // recursively try to combine both verts now, + // because things may have changed since the last combine test + RemoveIfColinear( v1, island ); + RemoveIfColinear( v3, island ); +} + +/* +==================== +CombineColinearEdges +==================== +*/ +static void CombineColinearEdges( optIsland_t* island ) +{ + int c_edges; + optVertex_t* ov; + optEdge_t* e; + + c_edges = 0; + for( e = island->edges ; e ; e = e->islandLink ) + { + c_edges++; + } + if( dmapGlobals.verbose ) + { + common->Printf( "%6i original exterior edges\n", c_edges ); + } + + for( ov = island->verts ; ov ; ov = ov->islandLink ) + { + RemoveIfColinear( ov, island ); + } + + c_edges = 0; + for( e = island->edges ; e ; e = e->islandLink ) + { + c_edges++; + } + if( dmapGlobals.verbose ) + { + common->Printf( "%6i optimized exterior edges\n", c_edges ); + } +} + + +//================================================================== + +/* +=================== +FreeOptTriangles + +=================== +*/ +static void FreeOptTriangles( optIsland_t* island ) +{ + optTri_t* opt, *next; + + for( opt = island->tris ; opt ; opt = next ) + { + next = opt->next; + Mem_Free( opt ); + } + + island->tris = NULL; +} + + +/* +================= +IsTriangleValid + +empty area will be considered invalid. +Due to some truly aweful epsilon issues, a triangle can switch between +valid and invalid depending on which order you look at the verts, so +consider it invalid if any one of the possibilities is invalid. +================= +*/ +static bool IsTriangleValid( const optVertex_t* v1, const optVertex_t* v2, const optVertex_t* v3 ) +{ + idVec3 d1, d2, normal; + + d1 = v2->pv - v1->pv; + d2 = v3->pv - v1->pv; + normal = d1.Cross( d2 ); + if( normal[2] <= 0 ) + { + return false; + } + + d1 = v3->pv - v2->pv; + d2 = v1->pv - v2->pv; + normal = d1.Cross( d2 ); + if( normal[2] <= 0 ) + { + return false; + } + + d1 = v1->pv - v3->pv; + d2 = v2->pv - v3->pv; + normal = d1.Cross( d2 ); + if( normal[2] <= 0 ) + { + return false; + } + + return true; +} + + +/* +================= +IsTriangleDegenerate + +Returns false if it is either front or back facing +================= +*/ +static bool IsTriangleDegenerate( const optVertex_t* v1, const optVertex_t* v2, const optVertex_t* v3 ) +{ +#if 1 + idVec3 d1, d2, normal; + + d1 = v2->pv - v1->pv; + d2 = v3->pv - v1->pv; + normal = d1.Cross( d2 ); + if( normal[2] == 0 ) + { + return true; + } + return false; +#else + return ( bool )!IsTriangleValid( v1, v2, v3 ); +#endif +} + + +/* +================== +PointInTri + +Tests if a 2D point is inside an original triangle +================== +*/ +static bool PointInTri( const idVec3& p, const mapTri_t* tri, optIsland_t* island ) +{ + idVec3 d1, d2, normal; + + // the normal[2] == 0 case is not uncommon when a square is triangulated in + // the opposite manner to the original + + d1 = tri->optVert[0]->pv - p; + d2 = tri->optVert[1]->pv - p; + normal = d1.Cross( d2 ); + if( normal[2] < 0 ) + { + return false; + } + + d1 = tri->optVert[1]->pv - p; + d2 = tri->optVert[2]->pv - p; + normal = d1.Cross( d2 ); + if( normal[2] < 0 ) + { + return false; + } + + d1 = tri->optVert[2]->pv - p; + d2 = tri->optVert[0]->pv - p; + normal = d1.Cross( d2 ); + if( normal[2] < 0 ) + { + return false; + } + + return true; +} + + +/* +==================== +LinkTriToEdge + +==================== +*/ +static void LinkTriToEdge( optTri_t* optTri, optEdge_t* edge ) +{ + if( ( edge->v1 == optTri->v[0] && edge->v2 == optTri->v[1] ) + || ( edge->v1 == optTri->v[1] && edge->v2 == optTri->v[2] ) + || ( edge->v1 == optTri->v[2] && edge->v2 == optTri->v[0] ) ) + { + if( edge->backTri ) + { + common->Printf( "Warning: LinkTriToEdge: already in use\n" ); + return; + } + edge->backTri = optTri; + return; + } + if( ( edge->v1 == optTri->v[1] && edge->v2 == optTri->v[0] ) + || ( edge->v1 == optTri->v[2] && edge->v2 == optTri->v[1] ) + || ( edge->v1 == optTri->v[0] && edge->v2 == optTri->v[2] ) ) + { + if( edge->frontTri ) + { + common->Printf( "Warning: LinkTriToEdge: already in use\n" ); + return; + } + edge->frontTri = optTri; + return; + } + common->Error( "LinkTriToEdge: edge not found on tri" ); +} + +/* +=============== +CreateOptTri +=============== +*/ +static void CreateOptTri( optVertex_t* first, optEdge_t* e1, optEdge_t* e2, optIsland_t* island ) +{ + optEdge_t* opposite = NULL; + optVertex_t* second = NULL, *third = NULL; + optTri_t* optTri = NULL; + mapTri_t* tri = NULL; + + if( e1->v1 == first ) + { + second = e1->v2; + } + else if( e1->v2 == first ) + { + second = e1->v1; + } + else + { + common->Error( "CreateOptTri: mislinked edge" ); + } + + if( e2->v1 == first ) + { + third = e2->v2; + } + else if( e2->v2 == first ) + { + third = e2->v1; + } + else + { + common->Error( "CreateOptTri: mislinked edge" ); + } + + if( !IsTriangleValid( first, second, third ) ) + { + common->Error( "CreateOptTri: invalid" ); + } + +//DrawEdges( island ); + + // identify the third edge + if( dmapGlobals.drawflag ) + { +#if 0 + qglColor3f( 1, 1, 0 ); + qglBegin( GL_LINES ); + qglVertex3fv( e1->v1->pv.ToFloatPtr() ); + qglVertex3fv( e1->v2->pv.ToFloatPtr() ); + qglEnd(); + qglFlush(); + qglColor3f( 0, 1, 1 ); + qglBegin( GL_LINES ); + qglVertex3fv( e2->v1->pv.ToFloatPtr() ); + qglVertex3fv( e2->v2->pv.ToFloatPtr() ); + qglEnd(); + qglFlush(); +#endif + } + + for( opposite = second->edges ; opposite ; ) + { + if( opposite != e1 && ( opposite->v1 == third || opposite->v2 == third ) ) + { + break; + } + if( opposite->v1 == second ) + { + opposite = opposite->v1link; + } + else if( opposite->v2 == second ) + { + opposite = opposite->v2link; + } + else + { + common->Error( "BuildOptTriangles: mislinked edge" ); + } + } + + if( !opposite ) + { + common->Printf( "Warning: BuildOptTriangles: couldn't locate opposite\n" ); + return; + } + + if( dmapGlobals.drawflag ) + { +#if 0 + qglColor3f( 1, 0, 1 ); + qglBegin( GL_LINES ); + qglVertex3fv( opposite->v1->pv.ToFloatPtr() ); + qglVertex3fv( opposite->v2->pv.ToFloatPtr() ); + qglEnd(); + qglFlush(); +#endif + } + + // create new triangle + optTri = ( optTri_t* )Mem_Alloc( sizeof( *optTri ), TAG_TOOLS ); + optTri->v[0] = first; + optTri->v[1] = second; + optTri->v[2] = third; + optTri->midpoint = ( optTri->v[0]->pv + optTri->v[1]->pv + optTri->v[2]->pv ) * ( 1.0f / 3.0f ); + optTri->next = island->tris; + island->tris = optTri; + + if( dmapGlobals.drawflag ) + { +#if 0 + qglColor3f( 1, 1, 1 ); + qglPointSize( 4 ); + qglBegin( GL_POINTS ); + qglVertex3fv( optTri->midpoint.ToFloatPtr() ); + qglEnd(); + qglFlush(); +#endif + } + + // find the midpoint, and scan through all the original triangles to + // see if it is inside any of them + for( tri = island->group->triList ; tri ; tri = tri->next ) + { + if( PointInTri( optTri->midpoint, tri, island ) ) + { + break; + } + } + if( tri ) + { + optTri->filled = true; + } + else + { + optTri->filled = false; + } + if( dmapGlobals.drawflag ) + { +#if 0 + if( optTri->filled ) + { + qglColor3f( ( 128 + orandom.RandomInt( 127 ) ) / 255.0, 0, 0 ); + } + else + { + qglColor3f( 0, ( 128 + orandom.RandomInt( 127 ) ) / 255.0, 0 ); + } + qglBegin( GL_TRIANGLES ); + qglVertex3fv( optTri->v[0]->pv.ToFloatPtr() ); + qglVertex3fv( optTri->v[1]->pv.ToFloatPtr() ); + qglVertex3fv( optTri->v[2]->pv.ToFloatPtr() ); + qglEnd(); + qglColor3f( 1, 1, 1 ); + qglBegin( GL_LINE_LOOP ); + qglVertex3fv( optTri->v[0]->pv.ToFloatPtr() ); + qglVertex3fv( optTri->v[1]->pv.ToFloatPtr() ); + qglVertex3fv( optTri->v[2]->pv.ToFloatPtr() ); + qglEnd(); + qglFlush(); +#endif + } + + // link the triangle to it's edges + LinkTriToEdge( optTri, e1 ); + LinkTriToEdge( optTri, e2 ); + LinkTriToEdge( optTri, opposite ); +} + +/* +==================== +BuildOptTriangles + +Generate a new list of triangles from the optEdeges +==================== +*/ +static void BuildOptTriangles( optIsland_t* island ) +{ + optVertex_t* ov = NULL, *second = NULL, *third = NULL, *middle = NULL; + optEdge_t* e1 = NULL, *e1Next = NULL, *e2 = NULL, *e2Next = NULL, *check = NULL, *checkNext = NULL; + + // free them + FreeOptTriangles( island ); + + // clear the vertex emitted flags + for( ov = island->verts ; ov ; ov = ov->islandLink ) + { + ov->emited = false; + } + + // clear the edge triangle links + for( check = island->edges ; check ; check = check->islandLink ) + { + check->frontTri = check->backTri = NULL; + } + + // check all possible triangle made up out of the + // edges coming off the vertex + for( ov = island->verts ; ov ; ov = ov->islandLink ) + { + if( !ov->edges ) + { + continue; + } + +#if 0 + if( dmapGlobals.drawflag && ov == ( optVertex_t* )0x1845a60 ) + { + for( e1 = ov->edges ; e1 ; e1 = e1Next ) + { + qglBegin( GL_LINES ); + qglColor3f( 0, 1, 0 ); + qglVertex3fv( e1->v1->pv.ToFloatPtr() ); + qglVertex3fv( e1->v2->pv.ToFloatPtr() ); + qglEnd(); + qglFlush(); + if( e1->v1 == ov ) + { + e1Next = e1->v1link; + } + else if( e1->v2 == ov ) + { + e1Next = e1->v2link; + } + } + } +#endif + for( e1 = ov->edges ; e1 ; e1 = e1Next ) + { + if( e1->v1 == ov ) + { + second = e1->v2; + e1Next = e1->v1link; + } + else if( e1->v2 == ov ) + { + second = e1->v1; + e1Next = e1->v2link; + } + else + { + common->Error( "BuildOptTriangles: mislinked edge" ); + } + + // if the vertex has already been used, it can't be used again + if( second->emited ) + { + continue; + } + + for( e2 = ov->edges ; e2 ; e2 = e2Next ) + { + if( e2->v1 == ov ) + { + third = e2->v2; + e2Next = e2->v1link; + } + else if( e2->v2 == ov ) + { + third = e2->v1; + e2Next = e2->v2link; + } + else + { + common->Error( "BuildOptTriangles: mislinked edge" ); + } + if( e2 == e1 ) + { + continue; + } + + // if the vertex has already been used, it can't be used again + if( third->emited ) + { + continue; + } + + // if the triangle is backwards or degenerate, don't use it + if( !IsTriangleValid( ov, second, third ) ) + { + continue; + } + + // see if any other edge bisects these two, which means + // this triangle shouldn't be used + for( check = ov->edges ; check ; check = checkNext ) + { + if( check->v1 == ov ) + { + middle = check->v2; + checkNext = check->v1link; + } + else if( check->v2 == ov ) + { + middle = check->v1; + checkNext = check->v2link; + } + else + { + common->Error( "BuildOptTriangles: mislinked edge" ); + } + + if( check == e1 || check == e2 ) + { + continue; + } + + if( IsTriangleValid( ov, second, middle ) + && IsTriangleValid( ov, middle, third ) ) + { + break; // should use the subdivided ones + } + } + + if( check ) + { + continue; // don't use it + } + + // the triangle is valid + CreateOptTri( ov, e1, e2, island ); + } + } + + // later vertexes will not emit triangles that use an + // edge that this vert has already used + ov->emited = true; + } +} + + + +/* +==================== +RegenerateTriangles + +Add new triangles to the group's regeneratedTris +==================== +*/ +static void RegenerateTriangles( optIsland_t* island ) +{ + optTri_t* optTri; + mapTri_t* tri; + int c_out; + + c_out = 0; + + for( optTri = island->tris ; optTri ; optTri = optTri->next ) + { + if( !optTri->filled ) + { + continue; + } + + // create a new mapTri_t + tri = AllocTri(); + + tri->material = island->group->material; + tri->mergeGroup = island->group->mergeGroup; + + tri->v[0] = optTri->v[0]->v; + tri->v[1] = optTri->v[1]->v; + tri->v[2] = optTri->v[2]->v; + + idPlane plane; + PlaneForTri( tri, plane ); + if( plane.Normal() * dmapGlobals.mapPlanes[ island->group->planeNum ].Normal() <= 0 ) + { + // this can happen reasonably when a triangle is nearly degenerate in + // optimization planar space, and winds up being degenerate in 3D space + common->Printf( "WARNING: backwards triangle generated!\n" ); + // discard it + FreeTri( tri ); + continue; + } + + c_out++; + tri->next = island->group->regeneratedTris; + island->group->regeneratedTris = tri; + } + + FreeOptTriangles( island ); + + if( dmapGlobals.verbose ) + { + common->Printf( "%6i tris out\n", c_out ); + } +} + +//=========================================================================== + +/* +==================== +RemoveInteriorEdges + +Edges that have triangles of the same type (filled / empty) +on both sides will be removed +==================== +*/ +static void RemoveInteriorEdges( optIsland_t* island ) +{ + int c_interiorEdges; + int c_exteriorEdges; + optEdge_t* e, *next; + bool front, back; + + c_exteriorEdges = 0; + c_interiorEdges = 0; + for( e = island->edges ; e ; e = next ) + { + // we might remove the edge, so get the next link now + next = e->islandLink; + + if( !e->frontTri ) + { + front = false; + } + else + { + front = e->frontTri->filled; + } + if( !e->backTri ) + { + back = false; + } + else + { + back = e->backTri->filled; + } + + if( front == back ) + { + // free the edge + UnlinkEdge( e, island ); + c_interiorEdges++; + continue; + } + + c_exteriorEdges++; + } + + if( dmapGlobals.verbose ) + { + common->Printf( "%6i original interior edges\n", c_interiorEdges ); + common->Printf( "%6i original exterior edges\n", c_exteriorEdges ); + } +} + +//================================================================================== + +typedef struct +{ + optVertex_t* v1, *v2; +} originalEdges_t; + +/* +================= +AddEdgeIfNotAlready +================= +*/ +void AddEdgeIfNotAlready( optVertex_t* v1, optVertex_t* v2 ) +{ + optEdge_t* e; + + // make sure that there isn't an identical edge already added + for( e = v1->edges ; e ; ) + { + if( ( e->v1 == v1 && e->v2 == v2 ) || ( e->v1 == v2 && e->v2 == v1 ) ) + { + return; // already added + } + if( e->v1 == v1 ) + { + e = e->v1link; + } + else if( e->v2 == v1 ) + { + e = e->v2link; + } + else + { + common->Error( "SplitEdgeByList: bad edge link" ); + } + } + + // this edge is a keeper + e = AllocEdge(); + e->v1 = v1; + e->v2 = v2; + + e->islandLink = NULL; + + // link the edge to its verts + LinkEdge( e ); +} + + + +/* +================= +DrawOriginalEdges +================= +*/ +static void DrawOriginalEdges( int numOriginalEdges, originalEdges_t* originalEdges ) +{ +// int i; + + if( !dmapGlobals.drawflag ) + { + return; + } + +#if 0 + Draw_ClearWindow(); + + qglBegin( GL_LINES ); + for( i = 0 ; i < numOriginalEdges ; i++ ) + { + qglColor3f( 1, 0, 0 ); + qglVertex3fv( originalEdges[i].v1->pv.ToFloatPtr() ); + qglColor3f( 0, 0, 0 ); + qglVertex3fv( originalEdges[i].v2->pv.ToFloatPtr() ); + } + qglEnd(); + qglFlush(); +#endif +} + + +typedef struct edgeCrossing_s +{ + struct edgeCrossing_s* next; + optVertex_t* ov; +} edgeCrossing_t; + +static originalEdges_t* originalEdges; +static int numOriginalEdges; + +/* +================= +AddOriginalTriangle +================= +*/ +static void AddOriginalTriangle( optVertex_t* v[3] ) +{ + optVertex_t* v1, *v2; + + // if this triangle is backwards (possible with epsilon issues) + // ignore it completely + if( !IsTriangleValid( v[0], v[1], v[2] ) ) + { + common->Printf( "WARNING: backwards triangle in input!\n" ); + return; + } + + for( int i = 0 ; i < 3 ; i++ ) + { + v1 = v[i]; + v2 = v[( i + 1 ) % 3]; + + if( v1 == v2 ) + { + // this probably shouldn't happen, because the + // tri would be degenerate + continue; + } + int j; + // see if there is an existing one + for( j = 0 ; j < numOriginalEdges ; j++ ) + { + if( originalEdges[j].v1 == v1 && originalEdges[j].v2 == v2 ) + { + break; + } + if( originalEdges[j].v2 == v1 && originalEdges[j].v1 == v2 ) + { + break; + } + } + + if( j == numOriginalEdges ) + { + // add it + originalEdges[j].v1 = v1; + originalEdges[j].v2 = v2; + numOriginalEdges++; + } + } +} + +/* +================= +AddOriginalEdges +================= +*/ +static void AddOriginalEdges( optimizeGroup_t* opt ) +{ + mapTri_t* tri; + optVertex_t* v[3]; + int numTris; + + if( dmapGlobals.verbose ) + { + common->Printf( "----\n" ); + common->Printf( "%6i original tris\n", CountTriList( opt->triList ) ); + } + + optBounds.Clear(); + + // allocate space for max possible edges + numTris = CountTriList( opt->triList ); + originalEdges = ( originalEdges_t* )Mem_Alloc( numTris * 3 * sizeof( *originalEdges ), TAG_TOOLS ); + numOriginalEdges = 0; + + // add all unique triangle edges + numOptVerts = 0; + numOptEdges = 0; + for( tri = opt->triList ; tri ; tri = tri->next ) + { + v[0] = tri->optVert[0] = FindOptVertex( &tri->v[0], opt ); + v[1] = tri->optVert[1] = FindOptVertex( &tri->v[1], opt ); + v[2] = tri->optVert[2] = FindOptVertex( &tri->v[2], opt ); + + AddOriginalTriangle( v ); + } +} + +/* +===================== +SplitOriginalEdgesAtCrossings +===================== +*/ +void SplitOriginalEdgesAtCrossings( optimizeGroup_t* opt ) +{ + int i, j, k, l; + int numOriginalVerts; + edgeCrossing_t** crossings; + + numOriginalVerts = numOptVerts; + // now split any crossing edges and create optEdges + // linked to the vertexes + + // debug drawing bounds + dmapGlobals.drawBounds = optBounds; + + dmapGlobals.drawBounds[0][0] -= 2; + dmapGlobals.drawBounds[0][1] -= 2; + dmapGlobals.drawBounds[1][0] += 2; + dmapGlobals.drawBounds[1][1] += 2; + + // generate crossing points between all the original edges + crossings = ( edgeCrossing_t** )Mem_ClearedAlloc( numOriginalEdges * sizeof( *crossings ), TAG_TOOLS ); + + for( i = 0 ; i < numOriginalEdges ; i++ ) + { + if( dmapGlobals.drawflag ) + { +#if 0 + DrawOriginalEdges( numOriginalEdges, originalEdges ); + qglBegin( GL_LINES ); + qglColor3f( 0, 1, 0 ); + qglVertex3fv( originalEdges[i].v1->pv.ToFloatPtr() ); + qglColor3f( 0, 0, 1 ); + qglVertex3fv( originalEdges[i].v2->pv.ToFloatPtr() ); + qglEnd(); + qglFlush(); +#endif + } + for( j = i + 1 ; j < numOriginalEdges ; j++ ) + { + optVertex_t* v1, *v2, *v3, *v4; + optVertex_t* newVert; + edgeCrossing_t* cross; + + v1 = originalEdges[i].v1; + v2 = originalEdges[i].v2; + v3 = originalEdges[j].v1; + v4 = originalEdges[j].v2; + + if( !EdgesCross( v1, v2, v3, v4 ) ) + { + continue; + } + + // this is the only point in optimization where + // completely new points are created, and it only + // happens if there is overlapping coplanar + // geometry in the source triangles + newVert = EdgeIntersection( v1, v2, v3, v4, opt ); + + if( !newVert ) + { +//common->Printf( "lines %i (%i to %i) and %i (%i to %i) are colinear\n", i, v1 - optVerts, v2 - optVerts, +// j, v3 - optVerts, v4 - optVerts ); // !@# + // colinear, so add both verts of each edge to opposite + if( VertexBetween( v3, v1, v2 ) ) + { + cross = ( edgeCrossing_t* )Mem_ClearedAlloc( sizeof( *cross ), TAG_TOOLS ); + cross->ov = v3; + cross->next = crossings[i]; + crossings[i] = cross; + } + + if( VertexBetween( v4, v1, v2 ) ) + { + cross = ( edgeCrossing_t* )Mem_ClearedAlloc( sizeof( *cross ), TAG_TOOLS ); + cross->ov = v4; + cross->next = crossings[i]; + crossings[i] = cross; + } + + if( VertexBetween( v1, v3, v4 ) ) + { + cross = ( edgeCrossing_t* )Mem_ClearedAlloc( sizeof( *cross ), TAG_TOOLS ); + cross->ov = v1; + cross->next = crossings[j]; + crossings[j] = cross; + } + + if( VertexBetween( v2, v3, v4 ) ) + { + cross = ( edgeCrossing_t* )Mem_ClearedAlloc( sizeof( *cross ), TAG_TOOLS ); + cross->ov = v2; + cross->next = crossings[j]; + crossings[j] = cross; + } + + continue; + } +#if 0 + if( newVert && newVert != v1 && newVert != v2 && newVert != v3 && newVert != v4 ) + { + common->Printf( "lines %i (%i to %i) and %i (%i to %i) cross at new point %i\n", i, v1 - optVerts, v2 - optVerts, + j, v3 - optVerts, v4 - optVerts, newVert - optVerts ); + } + else if( newVert ) + { + common->Printf( "lines %i (%i to %i) and %i (%i to %i) intersect at old point %i\n", i, v1 - optVerts, v2 - optVerts, + j, v3 - optVerts, v4 - optVerts, newVert - optVerts ); + } +#endif + if( newVert != v1 && newVert != v2 ) + { + cross = ( edgeCrossing_t* )Mem_ClearedAlloc( sizeof( *cross ), TAG_TOOLS ); + cross->ov = newVert; + cross->next = crossings[i]; + crossings[i] = cross; + } + + if( newVert != v3 && newVert != v4 ) + { + cross = ( edgeCrossing_t* )Mem_ClearedAlloc( sizeof( *cross ), TAG_TOOLS ); + cross->ov = newVert; + cross->next = crossings[j]; + crossings[j] = cross; + } + + } + } + + + // now split each edge by its crossing points + // colinear edges will have duplicated edges added, but it won't hurt anything + for( i = 0 ; i < numOriginalEdges ; i++ ) + { + edgeCrossing_t* cross, *nextCross; + int numCross; + optVertex_t** sorted; + + numCross = 0; + for( cross = crossings[i] ; cross ; cross = cross->next ) + { + numCross++; + } + numCross += 2; // account for originals + sorted = ( optVertex_t** )Mem_Alloc( numCross * sizeof( *sorted ), TAG_TOOLS ); + sorted[0] = originalEdges[i].v1; + sorted[1] = originalEdges[i].v2; + j = 2; + for( cross = crossings[i] ; cross ; cross = nextCross ) + { + nextCross = cross->next; + sorted[j] = cross->ov; + Mem_Free( cross ); + j++; + } + + // add all possible fragment combinations that aren't divided + // by another point + for( j = 0 ; j < numCross ; j++ ) + { + for( k = j + 1 ; k < numCross ; k++ ) + { + for( l = 0 ; l < numCross ; l++ ) + { + if( sorted[l] == sorted[j] || sorted[l] == sorted[k] ) + { + continue; + } + if( sorted[j] == sorted[k] ) + { + continue; + } + if( VertexBetween( sorted[l], sorted[j], sorted[k] ) ) + { + break; + } + } + if( l == numCross ) + { +//common->Printf( "line %i fragment from point %i to %i\n", i, sorted[j] - optVerts, sorted[k] - optVerts ); + AddEdgeIfNotAlready( sorted[j], sorted[k] ); + } + } + } + + Mem_Free( sorted ); + } + + + Mem_Free( crossings ); + Mem_Free( originalEdges ); + + // check for duplicated edges + for( i = 0 ; i < numOptEdges ; i++ ) + { + for( j = i + 1 ; j < numOptEdges ; j++ ) + { + if( ( optEdges[i].v1 == optEdges[j].v1 && optEdges[i].v2 == optEdges[j].v2 ) + || ( optEdges[i].v1 == optEdges[j].v2 && optEdges[i].v2 == optEdges[j].v1 ) ) + { + common->Printf( "duplicated optEdge\n" ); + } + } + } + + if( dmapGlobals.verbose ) + { + common->Printf( "%6i original edges\n", numOriginalEdges ); + common->Printf( "%6i edges after splits\n", numOptEdges ); + common->Printf( "%6i original vertexes\n", numOriginalVerts ); + common->Printf( "%6i vertexes after splits\n", numOptVerts ); + } +} + +//================================================================= + + +/* +=================== +CullUnusedVerts + +Unlink any verts with no edges, so they +won't be used in the retriangulation +=================== +*/ +static void CullUnusedVerts( optIsland_t* island ) +{ + optVertex_t** prev, *vert; + int c_keep, c_free; + optEdge_t* edge; + + c_keep = 0; + c_free = 0; + + for( prev = &island->verts ; *prev ; ) + { + vert = *prev; + + if( !vert->edges ) + { + // free it + *prev = vert->islandLink; + c_free++; + } + else + { + edge = vert->edges; + if( ( edge->v1 == vert && !edge->v1link ) + || ( edge->v2 == vert && !edge->v2link ) ) + { + // is is occasionally possible to get a vert + // with only a single edge when colinear optimizations + // crunch down a complex sliver + UnlinkEdge( edge, island ); + // free it + *prev = vert->islandLink; + c_free++; + } + else + { + prev = &vert->islandLink; + c_keep++; + } + } + } + + if( dmapGlobals.verbose ) + { + common->Printf( "%6i verts kept\n", c_keep ); + common->Printf( "%6i verts freed\n", c_free ); + } +} + + + +/* +==================== +OptimizeIsland + +At this point, all needed vertexes are already in the +list, including any that were added at crossing points. + +Interior and colinear vertexes will be removed, and +a new triangulation will be created. +==================== +*/ +static void OptimizeIsland( optIsland_t* island ) +{ + // add space-filling fake edges so we have a complete + // triangulation of a convex hull before optimization + AddInteriorEdges( island ); + DrawEdges( island ); + + // determine all the possible triangles, and decide if + // the are filled or empty + BuildOptTriangles( island ); + + // remove interior vertexes that have filled triangles + // between all their edges + RemoveInteriorEdges( island ); + DrawEdges( island ); + + ValidateEdgeCounts( island ); + + // remove vertexes that only have two colinear edges + CombineColinearEdges( island ); + CullUnusedVerts( island ); + DrawEdges( island ); + + // add new internal edges between the remaining exterior edges + // to give us a full triangulation again + AddInteriorEdges( island ); + DrawEdges( island ); + + // determine all the possible triangles, and decide if + // the are filled or empty + BuildOptTriangles( island ); + + // make mapTri_t out of the filled optTri_t + RegenerateTriangles( island ); +} + +/* +================ +AddVertexToIsland_r +================ +*/ +static void AddVertexToIsland_r( optVertex_t* vert, optIsland_t* island ) +{ + optEdge_t* e; + + // we can't just check islandLink, because the + // last vert will have a NULL + if( vert->addedToIsland ) + { + return; + } + vert->addedToIsland = true; + vert->islandLink = island->verts; + island->verts = vert; + + for( e = vert->edges ; e ; ) + { + if( !e->addedToIsland ) + { + e->addedToIsland = true; + + e->islandLink = island->edges; + island->edges = e; + } + + if( e->v1 == vert ) + { + AddVertexToIsland_r( e->v2, island ); + e = e->v1link; + continue; + } + if( e->v2 == vert ) + { + AddVertexToIsland_r( e->v1, island ); + e = e->v2link; + continue; + } + common->Error( "AddVertexToIsland_r: mislinked vert" ); + } + +} + + +static void DontSeparateIslands( optimizeGroup_t* opt ) +{ + int i; + optIsland_t island; + + DrawAllEdges(); + + memset( &island, 0, sizeof( island ) ); + island.group = opt; + + // link everything together + for( i = 0 ; i < numOptVerts ; i++ ) + { + optVerts[i].islandLink = island.verts; + island.verts = &optVerts[i]; + } + + for( i = 0 ; i < numOptEdges ; i++ ) + { + optEdges[i].islandLink = island.edges; + island.edges = &optEdges[i]; + } + + OptimizeIsland( &island ); +} + +/* +==================== +OptimizeOptList +==================== +*/ +static void OptimizeOptList( optimizeGroup_t* opt ) +{ + optimizeGroup_t* oldNext; + + // fix the t junctions among this single list + // so we can match edges + // can we avoid doing this if colinear vertexes break edges? + oldNext = opt->nextGroup; + opt->nextGroup = NULL; + FixAreaGroupsTjunctions( opt ); + opt->nextGroup = oldNext; + + // create the 2D vectors + dmapGlobals.mapPlanes[opt->planeNum].Normal().NormalVectors( opt->axis[0], opt->axis[1] ); + + AddOriginalEdges( opt ); + SplitOriginalEdgesAtCrossings( opt ); + +#if 0 + // seperate any discontinuous areas for individual optimization + // to reduce the scope of the problem + SeparateIslands( opt ); +#else + DontSeparateIslands( opt ); +#endif + + // now free the hash verts + FreeTJunctionHash(); + + // free the original list and use the new one + FreeTriList( opt->triList ); + opt->triList = opt->regeneratedTris; + opt->regeneratedTris = NULL; +} + + +/* +================== +SetGroupTriPlaneNums + +Copies the group planeNum to every triangle in each group +================== +*/ +void SetGroupTriPlaneNums( optimizeGroup_t* groups ) +{ + mapTri_t* tri; + optimizeGroup_t* group; + + for( group = groups ; group ; group = group->nextGroup ) + { + for( tri = group->triList ; tri ; tri = tri->next ) + { + tri->planeNum = group->planeNum; + } + } +} + + +/* +=================== +OptimizeGroupList + +This will also fix tjunctions + +=================== +*/ +void OptimizeGroupList( optimizeGroup_t* groupList ) +{ + int c_in, c_edge, c_tjunc2; + optimizeGroup_t* group; + + if( !groupList ) + { + return; + } + + c_in = CountGroupListTris( groupList ); + + // optimize and remove colinear edges, which will + // re-introduce some t junctions + for( group = groupList ; group ; group = group->nextGroup ) + { + OptimizeOptList( group ); + } + c_edge = CountGroupListTris( groupList ); + + // fix t junctions again + FixAreaGroupsTjunctions( groupList ); + FreeTJunctionHash(); + c_tjunc2 = CountGroupListTris( groupList ); + + SetGroupTriPlaneNums( groupList ); + + common->Printf( "----- OptimizeAreaGroups Results -----\n" ); + common->Printf( "%6i tris in\n", c_in ); + common->Printf( "%6i tris after edge removal optimization\n", c_edge ); + common->Printf( "%6i tris after final t junction fixing\n", c_tjunc2 ); +} + + +/* +================== +OptimizeEntity +================== +*/ +void OptimizeEntity( uEntity_t* e ) +{ + int i; + + common->Printf( "----- OptimizeEntity -----\n" ); + for( i = 0 ; i < e->numAreas ; i++ ) + { + OptimizeGroupList( e->areas[i].groups ); + } +} diff --git a/neo/tools/compilers/dmap/output.cpp b/neo/tools/compilers/dmap/output.cpp new file mode 100644 index 00000000..e1fef26c --- /dev/null +++ b/neo/tools/compilers/dmap/output.cpp @@ -0,0 +1,708 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "dmap.h" + +//================================================================================= + + +#if 0 + +should we try and snap values very close to 0.5, 0.25, 0.125, etc ? + + do we write out normals, or just a "smooth shade" flag ? + resolved : normals. otherwise adjacent facet shaded surfaces get their + vertexes merged, and they would have to be split apart before drawing + + do we save out "wings" for shadow silhouette info ? + + +#endif + + static idFile* procFile; + +#define AREANUM_DIFFERENT -2 +/* +============= +PruneNodes_r + +Any nodes that have all children with the same +area can be combined into a single leaf node + +Returns the area number of all children, or +AREANUM_DIFFERENT if not the same. +============= +*/ +int PruneNodes_r( node_t* node ) +{ + int a1, a2; + + if( node->planenum == PLANENUM_LEAF ) + { + return node->area; + } + + a1 = PruneNodes_r( node->children[0] ); + a2 = PruneNodes_r( node->children[1] ); + + if( a1 != a2 || a1 == AREANUM_DIFFERENT ) + { + return AREANUM_DIFFERENT; + } + + // free all the nodes below this point + FreeTreePortals_r( node->children[0] ); + FreeTreePortals_r( node->children[1] ); + FreeTree_r( node->children[0] ); + FreeTree_r( node->children[1] ); + + // change this node to a leaf + node->planenum = PLANENUM_LEAF; + node->area = a1; + + return a1; +} + +static void WriteFloat( idFile* f, float v ) +{ + if( idMath::Fabs( v - idMath::Rint( v ) ) < 0.001 ) + { + f->WriteFloatString( "%i ", ( int )idMath::Rint( v ) ); + } + else + { + f->WriteFloatString( "%f ", v ); + } +} + +void Write1DMatrix( idFile* f, int x, float* m ) +{ + int i; + + f->WriteFloatString( "( " ); + + for( i = 0; i < x; i++ ) + { + WriteFloat( f, m[i] ); + } + + f->WriteFloatString( ") " ); +} + +static int CountUniqueShaders( optimizeGroup_t* groups ) +{ + optimizeGroup_t* a, *b; + int count; + + count = 0; + + for( a = groups ; a ; a = a->nextGroup ) + { + if( !a->triList ) // ignore groups with no tris + { + continue; + } + for( b = groups ; b != a ; b = b->nextGroup ) + { + if( !b->triList ) + { + continue; + } + if( a->material != b->material ) + { + continue; + } + if( a->mergeGroup != b->mergeGroup ) + { + continue; + } + break; + } + if( a == b ) + { + count++; + } + } + + return count; +} + + +/* +============== +MatchVert +============== +*/ +#define XYZ_EPSILON 0.01 +#define ST_EPSILON 0.001 +#define COSINE_EPSILON 0.999 + +static bool MatchVert( const idDrawVert* a, const idDrawVert* b ) +{ + if( idMath::Fabs( a->xyz[0] - b->xyz[0] ) > XYZ_EPSILON ) + { + return false; + } + if( idMath::Fabs( a->xyz[1] - b->xyz[1] ) > XYZ_EPSILON ) + { + return false; + } + if( idMath::Fabs( a->xyz[2] - b->xyz[2] ) > XYZ_EPSILON ) + { + return false; + } + + if( idMath::Fabs( a->GetTexCoordS() - b->GetTexCoordS() ) > ST_EPSILON ) + { + return false; + } + if( idMath::Fabs( a->GetTexCoordT() - b->GetTexCoordT() ) > ST_EPSILON ) + { + return false; + } + + // if the normal is 0 (smoothed normals), consider it a match + if( a->GetNormal() == idVec3( 0, 0, 0 ) || b->GetNormal() == idVec3( 0, 0, 0 ) ) + { + return true; + } + + // otherwise do a dot-product cosine check + if( a->GetNormal() * b->GetNormal() < COSINE_EPSILON ) + { + return false; + } + + return true; +} + +/* +==================== +ShareMapTriVerts + +Converts independent triangles to shared vertex triangles +==================== +*/ +srfTriangles_t* ShareMapTriVerts( const mapTri_t* tris ) +{ + const mapTri_t* step; + int count; + int i, j; + int numVerts; + int numIndexes; + srfTriangles_t* uTri; + + // unique the vertexes + count = CountTriList( tris ); + + uTri = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( uTri, count * 3 ); + R_AllocStaticTriSurfIndexes( uTri, count * 3 ); + + numVerts = 0; + numIndexes = 0; + + for( step = tris ; step ; step = step->next ) + { + for( i = 0 ; i < 3 ; i++ ) + { + const idDrawVert* dv; + + dv = &step->v[i]; + + // search for a match + for( j = 0 ; j < numVerts ; j++ ) + { + if( MatchVert( &uTri->verts[j], dv ) ) + { + break; + } + } + if( j == numVerts ) + { + numVerts++; + uTri->verts[j].xyz = dv->xyz; + //uTri->verts[j].SetNormal( dv->normal[0], dv->normal[1], dv->normal[2] ); + uTri->verts[j].SetNormal( dv->GetNormal() ); + uTri->verts[j].SetTexCoordS( dv->GetTexCoordS() ); + uTri->verts[j].SetTexCoordT( dv->GetTexCoordT() ); + } + + uTri->indexes[numIndexes++] = j; + } + } + + uTri->numVerts = numVerts; + uTri->numIndexes = numIndexes; + + return uTri; +} + +/* +================== +CleanupUTriangles +================== +*/ +static void CleanupUTriangles( srfTriangles_t* tri ) +{ + // perform cleanup operations + + R_RangeCheckIndexes( tri ); + R_CreateSilIndexes( tri ); +// R_RemoveDuplicatedTriangles( tri ); // this may remove valid overlapped transparent triangles + R_RemoveDegenerateTriangles( tri ); +// R_RemoveUnusedVerts( tri ); + + R_FreeStaticTriSurfSilIndexes( tri ); +} + +/* +==================== +WriteUTriangles + +Writes text verts and indexes to procfile +==================== +*/ +static void WriteUTriangles( const srfTriangles_t* uTris ) +{ + int col; + int i; + + // emit this chain + procFile->WriteFloatString( "/* numVerts = */ %i /* numIndexes = */ %i\n", + uTris->numVerts, uTris->numIndexes ); + + // verts + col = 0; + for( i = 0 ; i < uTris->numVerts ; i++ ) + { + float vec[8]; + const idDrawVert* dv; + + dv = &uTris->verts[i]; + + vec[0] = dv->xyz[0]; + vec[1] = dv->xyz[1]; + vec[2] = dv->xyz[2]; + + idVec2 st = dv->GetTexCoord(); + vec[3] = st.x; + vec[4] = st.y; + + idVec3 normal = dv->GetNormal(); + vec[5] = normal.x; + vec[6] = normal.y; + vec[7] = normal.z; + + Write1DMatrix( procFile, 8, vec ); + + if( ++col == 3 ) + { + col = 0; + procFile->WriteFloatString( "\n" ); + } + } + if( col != 0 ) + { + procFile->WriteFloatString( "\n" ); + } + + // indexes + col = 0; + for( i = 0 ; i < uTris->numIndexes ; i++ ) + { + procFile->WriteFloatString( "%i ", uTris->indexes[i] ); + + if( ++col == 18 ) + { + col = 0; + procFile->WriteFloatString( "\n" ); + } + } + if( col != 0 ) + { + procFile->WriteFloatString( "\n" ); + } +} + + +/* +======================= +GroupsAreSurfaceCompatible + +Planes, texcoords, and groupLights can differ, +but the material and mergegroup must match +======================= +*/ +static bool GroupsAreSurfaceCompatible( const optimizeGroup_t* a, const optimizeGroup_t* b ) +{ + if( a->material != b->material ) + { + return false; + } + if( a->mergeGroup != b->mergeGroup ) + { + return false; + } + return true; +} + +/* +==================== +WriteOutputSurfaces +==================== +*/ +static void WriteOutputSurfaces( int entityNum, int areaNum ) +{ + mapTri_t* ambient, *copy; + int surfaceNum; + int numSurfaces; + idMapEntity* entity; + uArea_t* area; + optimizeGroup_t* group, *groupStep; + int i; // , j; +// int col; + srfTriangles_t* uTri; +// mapTri_t *tri; + typedef struct interactionTris_s + { + struct interactionTris_s* next; + mapTri_t* triList; + mapLight_t* light; + } interactionTris_t; + + interactionTris_t* interactions, *checkInter; //, *nextInter; + + + area = &dmapGlobals.uEntities[entityNum].areas[areaNum]; + entity = dmapGlobals.uEntities[entityNum].mapEntity; + + numSurfaces = CountUniqueShaders( area->groups ); + + + if( entityNum == 0 ) + { + procFile->WriteFloatString( "model { /* name = */ \"_area%i\" /* numSurfaces = */ %i\n\n", + areaNum, numSurfaces ); + } + else + { + const char* name; + + entity->epairs.GetString( "name", "", &name ); + if( !name[0] ) + { + common->Error( "Entity %i has surfaces, but no name key", entityNum ); + } + procFile->WriteFloatString( "model { /* name = */ \"%s\" /* numSurfaces = */ %i\n\n", + name, numSurfaces ); + } + + surfaceNum = 0; + for( group = area->groups ; group ; group = group->nextGroup ) + { + if( group->surfaceEmited ) + { + continue; + } + + // combine all groups compatible with this one + // usually several optimizeGroup_t can be combined into a single + // surface, even though they couldn't be merged together to save + // vertexes because they had different planes, texture coordinates, or lights. + // Different mergeGroups will stay in separate surfaces. + ambient = NULL; + + // each light that illuminates any of the groups in the surface will + // get its own list of indexes out of the original surface + interactions = NULL; + + for( groupStep = group ; groupStep ; groupStep = groupStep->nextGroup ) + { + if( groupStep->surfaceEmited ) + { + continue; + } + if( !GroupsAreSurfaceCompatible( group, groupStep ) ) + { + continue; + } + + // copy it out to the ambient list + copy = CopyTriList( groupStep->triList ); + ambient = MergeTriLists( ambient, copy ); + groupStep->surfaceEmited = true; + + // duplicate it into an interaction for each groupLight + for( i = 0 ; i < groupStep->numGroupLights ; i++ ) + { + for( checkInter = interactions ; checkInter ; checkInter = checkInter->next ) + { + if( checkInter->light == groupStep->groupLights[i] ) + { + break; + } + } + if( !checkInter ) + { + // create a new interaction + checkInter = ( interactionTris_t* )Mem_ClearedAlloc( sizeof( *checkInter ), TAG_TOOLS ); + checkInter->light = groupStep->groupLights[i]; + checkInter->next = interactions; + interactions = checkInter; + } + copy = CopyTriList( groupStep->triList ); + checkInter->triList = MergeTriLists( checkInter->triList, copy ); + } + } + + if( !ambient ) + { + continue; + } + + if( surfaceNum >= numSurfaces ) + { + common->Error( "WriteOutputSurfaces: surfaceNum >= numSurfaces" ); + } + + procFile->WriteFloatString( "/* surface %i */ { ", surfaceNum ); + surfaceNum++; + procFile->WriteFloatString( "\"%s\" ", ambient->material->GetName() ); + + uTri = ShareMapTriVerts( ambient ); + FreeTriList( ambient ); + + CleanupUTriangles( uTri ); + WriteUTriangles( uTri ); + R_FreeStaticTriSurf( uTri ); + + procFile->WriteFloatString( "}\n\n" ); + } + + procFile->WriteFloatString( "}\n\n" ); +} + +/* +=============== +WriteNode_r + +=============== +*/ +static void WriteNode_r( node_t* node ) +{ + int child[2]; + int i; + idPlane* plane; + + if( node->planenum == PLANENUM_LEAF ) + { + // we shouldn't get here unless the entire world + // was a single leaf + procFile->WriteFloatString( "/* node 0 */ ( 0 0 0 0 ) -1 -1\n" ); + return; + } + + for( i = 0 ; i < 2 ; i++ ) + { + if( node->children[i]->planenum == PLANENUM_LEAF ) + { + child[i] = -1 - node->children[i]->area; + } + else + { + child[i] = node->children[i]->nodeNumber; + } + } + + plane = &dmapGlobals.mapPlanes[node->planenum]; + + procFile->WriteFloatString( "/* node %i */ ", node->nodeNumber ); + Write1DMatrix( procFile, 4, plane->ToFloatPtr() ); + procFile->WriteFloatString( "%i %i\n", child[0], child[1] ); + + if( child[0] > 0 ) + { + WriteNode_r( node->children[0] ); + } + if( child[1] > 0 ) + { + WriteNode_r( node->children[1] ); + } +} + +static int NumberNodes_r( node_t* node, int nextNumber ) +{ + if( node->planenum == PLANENUM_LEAF ) + { + return nextNumber; + } + node->nodeNumber = nextNumber; + nextNumber++; + nextNumber = NumberNodes_r( node->children[0], nextNumber ); + nextNumber = NumberNodes_r( node->children[1], nextNumber ); + + return nextNumber; +} + +/* +==================== +WriteOutputNodes +==================== +*/ +static void WriteOutputNodes( node_t* node ) +{ + int numNodes; + + // prune unneeded nodes and count + PruneNodes_r( node ); + numNodes = NumberNodes_r( node, 0 ); + + // output + procFile->WriteFloatString( "nodes { /* numNodes = */ %i\n\n", numNodes ); + procFile->WriteFloatString( "/* node format is: ( planeVector ) positiveChild negativeChild */\n" ); + procFile->WriteFloatString( "/* a child number of 0 is an opaque, solid area */\n" ); + procFile->WriteFloatString( "/* negative child numbers are areas: (-1-child) */\n" ); + + WriteNode_r( node ); + + procFile->WriteFloatString( "}\n\n" ); +} + +/* +==================== +WriteOutputPortals +==================== +*/ +static void WriteOutputPortals( uEntity_t* e ) +{ + int i, j; + interAreaPortal_t* iap; + idWinding* w; + + procFile->WriteFloatString( "interAreaPortals { /* numAreas = */ %i /* numIAP = */ %i\n\n", + e->numAreas, numInterAreaPortals ); + procFile->WriteFloatString( "/* interAreaPortal format is: numPoints positiveSideArea negativeSideArea ( point) ... */\n" ); + for( i = 0 ; i < numInterAreaPortals ; i++ ) + { + iap = &interAreaPortals[i]; + w = iap->side->winding; + procFile->WriteFloatString( "/* iap %i */ %i %i %i ", i, w->GetNumPoints(), iap->area0, iap->area1 ); + for( j = 0 ; j < w->GetNumPoints() ; j++ ) + { + Write1DMatrix( procFile, 3, ( *w )[j].ToFloatPtr() ); + } + procFile->WriteFloatString( "\n" ); + } + + procFile->WriteFloatString( "}\n\n" ); +} + + +/* +==================== +WriteOutputEntity +==================== +*/ +static void WriteOutputEntity( int entityNum ) +{ + int i; + uEntity_t* e; + + e = &dmapGlobals.uEntities[entityNum]; + + if( entityNum != 0 ) + { + // entities may have enclosed, empty areas that we don't need to write out + if( e->numAreas > 1 ) + { + e->numAreas = 1; + } + } + + for( i = 0 ; i < e->numAreas ; i++ ) + { + WriteOutputSurfaces( entityNum, i ); + } + + // we will completely skip the portals and nodes if it is a single area + if( entityNum == 0 && e->numAreas > 1 ) + { + // output the area portals + WriteOutputPortals( e ); + + // output the nodes + WriteOutputNodes( e->tree->headnode ); + } +} + + +/* +==================== +WriteOutputFile +==================== +*/ +void WriteOutputFile( void ) +{ + int i; + uEntity_t* entity; + idStr qpath; + + // write the file + common->Printf( "----- WriteOutputFile -----\n" ); + + sprintf( qpath, "%s." PROC_FILE_EXT, dmapGlobals.mapFileBase ); + + common->Printf( "writing %s\n", qpath.c_str() ); + // _D3XP used fs_cdpath + procFile = fileSystem->OpenFileWrite( qpath, "fs_basepath" ); + if( !procFile ) + { + common->Error( "Error opening %s", qpath.c_str() ); + } + + procFile->WriteFloatString( "%s\n\n", PROC_FILE_ID ); + + // write the entity models and information, writing entities first + for( i = dmapGlobals.num_entities - 1 ; i >= 0 ; i-- ) + { + entity = &dmapGlobals.uEntities[i]; + + if( !entity->primitives ) + { + continue; + } + + WriteOutputEntity( i ); + } + + fileSystem->CloseFile( procFile ); +} diff --git a/neo/tools/compilers/dmap/portals.cpp b/neo/tools/compilers/dmap/portals.cpp new file mode 100644 index 00000000..958058de --- /dev/null +++ b/neo/tools/compilers/dmap/portals.cpp @@ -0,0 +1,1113 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "dmap.h" + + +interAreaPortal_t interAreaPortals[MAX_INTER_AREA_PORTALS]; +int numInterAreaPortals; + + +int c_active_portals; +int c_peak_portals; + +/* +=========== +AllocPortal +=========== +*/ +uPortal_t* AllocPortal( void ) +{ + uPortal_t* p; + + c_active_portals++; + if( c_active_portals > c_peak_portals ) + c_peak_portals = c_active_portals; + + p = ( uPortal_t* )Mem_Alloc( sizeof( uPortal_t ), TAG_TOOLS ); + memset( p, 0, sizeof( uPortal_t ) ); + + return p; +} + + +void FreePortal( uPortal_t* p ) +{ + if( p->winding ) + delete p->winding; + c_active_portals--; + Mem_Free( p ); +} + +//============================================================== + +/* +============= +Portal_Passable + +Returns true if the portal has non-opaque leafs on both sides +============= +*/ +static bool Portal_Passable( uPortal_t* p ) +{ + if( !p->onnode ) + { + return false; // to global outsideleaf + } + + if( p->nodes[0]->planenum != PLANENUM_LEAF + || p->nodes[1]->planenum != PLANENUM_LEAF ) + { + common->Error( "Portal_EntityFlood: not a leaf" ); + } + + if( !p->nodes[0]->opaque && !p->nodes[1]->opaque ) + { + return true; + } + + return false; +} + + +//============================================================================= + +int c_tinyportals; + +/* +============= +AddPortalToNodes +============= +*/ +void AddPortalToNodes( uPortal_t* p, node_t* front, node_t* back ) +{ + if( p->nodes[0] || p->nodes[1] ) + { + common->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( uPortal_t* portal, node_t* l ) +{ + uPortal_t** pp, *t; + +// remove reference to the current portal + pp = &l->portals; + while( 1 ) + { + t = *pp; + if( !t ) + common->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 + common->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; + } + else + { + common->Error( "RemovePortalFromNode: mislinked" ); + } +} + +//============================================================================ + +void PrintPortal( uPortal_t* p ) +{ + int i; + idWinding* w; + + w = p->winding; + for( i = 0; i < w->GetNumPoints(); i++ ) + common->Printf( "(%5.0f,%5.0f,%5.0f)\n", ( *w )[i][0], ( *w )[i][1], ( *w )[i][2] ); +} + +/* +================ +MakeHeadnodePortals + +The created portals will face the global outside_node +================ +*/ +#define SIDESPACE 8 +static void MakeHeadnodePortals( tree_t* tree ) +{ + idBounds bounds; + int i, j, n; + uPortal_t* p, *portals[6]; + idPlane bplanes[6], *pl; + node_t* node; + + node = tree->headnode; + + tree->outside_node.planenum = PLANENUM_LEAF; + tree->outside_node.brushlist = NULL; + tree->outside_node.portals = NULL; + tree->outside_node.opaque = false; + + // if no nodes, don't go any farther + if( node->planenum == PLANENUM_LEAF ) + { + return; + } + + // pad with some space so there will never be null volume leafs + for( i = 0 ; i < 3 ; i++ ) + { + bounds[0][i] = tree->bounds[0][i] - SIDESPACE; + bounds[1][i] = tree->bounds[1][i] + SIDESPACE; + if( bounds[0][i] >= bounds[1][i] ) + { + common->Error( "Backwards tree volume" ); + } + } + + 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 )[i] = -1; + ( *pl )[3] = bounds[j][i]; + } + else + { + ( *pl )[i] = 1; + ( *pl )[3] = -bounds[j][i]; + } + p->plane = *pl; + p->winding = new idWinding( *pl ); + 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; + } + portals[i]->winding = portals[i]->winding->Clip( bplanes[j], ON_EPSILON ); + } + } +} + +//=================================================== + + +/* +================ +BaseWindingForNode +================ +*/ +#define BASE_WINDING_EPSILON 0.001f +#define SPLIT_WINDING_EPSILON 0.001f + +idWinding* BaseWindingForNode( node_t* node ) +{ + idWinding* w; + node_t* n; + + w = new idWinding( dmapGlobals.mapPlanes[node->planenum] ); + + // clip by all the parents + for( n = node->parent ; n && w ; ) + { + idPlane& plane = dmapGlobals.mapPlanes[n->planenum]; + + if( n->children[0] == node ) + { + // take front + w = w->Clip( plane, BASE_WINDING_EPSILON ); + } + else + { + // take back + idPlane back = -plane; + w = w->Clip( back, 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 +================== +*/ +static void MakeNodePortal( node_t* node ) +{ + uPortal_t* new_portal, *p; + idWinding* w; + idVec3 normal; + int side; + + w = BaseWindingForNode( node ); + + // clip the portal by all the other portals in the node + for( p = node->portals ; p && w; p = p->next[side] ) + { + idPlane plane; + + if( p->nodes[0] == node ) + { + side = 0; + plane = p->plane; + } + else if( p->nodes[1] == node ) + { + side = 1; + plane = -p->plane; + } + else + { + common->Error( "CutNodePortals_r: mislinked portal" ); + side = 0; // quiet a compiler warning + } + + w = w->Clip( plane, CLIP_EPSILON ); + } + + if( !w ) + { + return; + } + + if( w->IsTiny() ) + { + c_tinyportals++; + delete w; + return; + } + + + new_portal = AllocPortal(); + new_portal->plane = dmapGlobals.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. +============== +*/ +static void SplitNodePortals( node_t* node ) +{ + uPortal_t* p, *next_portal, *new_portal; + node_t* f, *b, *other_node; + int side; + idPlane* plane; + idWinding* frontwinding, *backwinding; + + plane = &dmapGlobals.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 + { + common->Error( "SplitNodePortals: mislinked portal" ); + side = 0; // quiet a compiler warning + } + 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 + // + p->winding->Split( *plane, SPLIT_WINDING_EPSILON, &frontwinding, &backwinding ); + + if( frontwinding && frontwinding->IsTiny() ) + { + delete frontwinding; + frontwinding = NULL; + c_tinyportals++; + } + + if( backwinding && backwinding->IsTiny() ) + { + delete backwinding; + backwinding = NULL; + c_tinyportals++; + } + + if( !frontwinding && !backwinding ) + { + // tiny windings on both sides + continue; + } + + if( !frontwinding ) + { + delete backwinding; + if( side == 0 ) + AddPortalToNodes( p, b, other_node ); + else + AddPortalToNodes( p, other_node, b ); + continue; + } + if( !backwinding ) + { + delete 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; + delete 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 ) +{ + uPortal_t* p; + int s; + int i; + + // calc mins/maxs for both leafs and nodes + node->bounds.Clear(); + for( p = node->portals ; p ; p = p->next[s] ) + { + s = ( p->nodes[1] == node ); + for( i = 0; i < p->winding->GetNumPoints(); i++ ) + { + node->bounds.AddPoint( ( *p->winding )[i].ToVec3() ); + } + } +} + + +/* +================== +MakeTreePortals_r +================== +*/ +void MakeTreePortals_r( node_t* node ) +{ + int i; + + CalcNodeBounds( node ); + + if( node->bounds[0][0] >= node->bounds[1][0] ) + { + common->Warning( "node without a volume" ); + } + + for( i = 0; i < 3; i++ ) + { + if( node->bounds[0][i] < MIN_WORLD_COORD || node->bounds[1][i] > MAX_WORLD_COORD ) + { + common->Warning( "node with unbounded volume" ); + 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 ) +{ + common->Printf( "----- MakeTreePortals -----\n" ); + MakeHeadnodePortals( tree ); + MakeTreePortals_r( tree->headnode ); +} + +/* +========================================================= + +FLOOD ENTITIES + +========================================================= +*/ + +int c_floodedleafs; + +/* +============= +FloodPortals_r +============= +*/ +void FloodPortals_r( node_t* node, int dist ) +{ + uPortal_t* p; + int s; + + if( node->occupied ) + { + return; + } + + if( node->opaque ) + { + return; + } + + c_floodedleafs++; + node->occupied = dist; + + for( p = node->portals ; p ; p = p->next[s] ) + { + s = ( p->nodes[1] == node ); + FloodPortals_r( p->nodes[!s], dist + 1 ); + } +} + +/* +============= +PlaceOccupant +============= +*/ +bool PlaceOccupant( node_t* headnode, idVec3 origin, uEntity_t* occupant ) +{ + node_t* node; + float d; + idPlane* plane; + + // find the leaf to start in + node = headnode; + while( node->planenum != PLANENUM_LEAF ) + { + plane = &dmapGlobals.mapPlanes[node->planenum]; + d = plane->Distance( origin ); + if( d >= 0.0f ) + { + node = node->children[0]; + } + else + { + node = node->children[1]; + } + } + + if( node->opaque ) + { + return false; + } + node->occupant = occupant; + + FloodPortals_r( node, 1 ); + + return true; +} + +/* +============= +FloodEntities + +Marks all nodes that can be reached by entites +============= +*/ +bool FloodEntities( tree_t* tree ) +{ + int i; + idVec3 origin; + const char* cl; + bool inside; + node_t* headnode; + + headnode = tree->headnode; + common->Printf( "--- FloodEntities ---\n" ); + inside = false; + tree->outside_node.occupied = 0; + + c_floodedleafs = 0; + bool errorShown = false; + for( i = 1 ; i < dmapGlobals.num_entities ; i++ ) + { + idMapEntity* mapEnt; + + mapEnt = dmapGlobals.uEntities[i].mapEntity; + if( !mapEnt->epairs.GetVector( "origin", "", origin ) ) + { + continue; + } + + // any entity can have "noFlood" set to skip it + if( mapEnt->epairs.GetString( "noFlood", "", &cl ) ) + { + continue; + } + + mapEnt->epairs.GetString( "classname", "", &cl ); + + if( !strcmp( cl, "light" ) ) + { + const char* v; + + // don't place lights that have a light_start field, because they can still + // be valid if their origin is outside the world + mapEnt->epairs.GetString( "light_start", "", &v ); + if( v[0] ) + { + continue; + } + + // don't place fog lights, because they often + // have origins outside the light + mapEnt->epairs.GetString( "texture", "", &v ); + if( v[0] ) + { + const idMaterial* mat = declManager->FindMaterial( v ); + if( mat->IsFogLight() ) + { + continue; + } + } + } + + if( PlaceOccupant( headnode, origin, &dmapGlobals.uEntities[i] ) ) + { + inside = true; + } + + if( tree->outside_node.occupied && !errorShown ) + { + errorShown = true; + common->Printf( "Leak on entity # %d\n", i ); + const char* p; + + mapEnt->epairs.GetString( "classname", "", &p ); + common->Printf( "Entity classname was: %s\n", p ); + mapEnt->epairs.GetString( "name", "", &p ); + common->Printf( "Entity name was: %s\n", p ); + idVec3 origin; + if( mapEnt->epairs.GetVector( "origin", "", origin ) ) + { + common->Printf( "Entity origin is: %f %f %f\n\n\n", origin.x, origin.y, origin.z ); + } + } + } + + common->Printf( "%5i flooded leafs\n", c_floodedleafs ); + + if( !inside ) + { + common->Printf( "no entities in open -- no filling\n" ); + } + else if( tree->outside_node.occupied ) + { + common->Printf( "entity reached from outside -- no filling\n" ); + } + + return ( bool )( inside && !tree->outside_node.occupied ); +} + +/* +========================================================= + +FLOOD AREAS + +========================================================= +*/ + +static int c_areas; +static int c_areaFloods; + +/* +================= +FindSideForPortal +================= +*/ +static side_t* FindSideForPortal( uPortal_t* p ) +{ + int i, j, k; + node_t* node; + uBrush_t* b, *orig; + side_t* s, *s2; + + // scan both bordering nodes brush lists for a portal brush + // that shares the plane + for( i = 0 ; i < 2 ; i++ ) + { + node = p->nodes[i]; + for( b = node->brushlist ; b ; b = b->next ) + { + if( !( b->contents & CONTENTS_AREAPORTAL ) ) + { + continue; + } + orig = b->original; + for( j = 0 ; j < orig->numsides ; j++ ) + { + s = orig->sides + j; + if( !s->visibleHull ) + { + continue; + } + if( !( s->material->GetContentFlags() & CONTENTS_AREAPORTAL ) ) + { + continue; + } + if( ( s->planenum & ~1 ) != ( p->onnode->planenum & ~1 ) ) + { + continue; + } + // remove the visible hull from any other portal sides of this portal brush + for( k = 0; k < orig->numsides; k++ ) + { + if( k == j ) + { + continue; + } + s2 = orig->sides + k; + if( s2->visibleHull == NULL ) + { + continue; + } + if( !( s2->material->GetContentFlags() & CONTENTS_AREAPORTAL ) ) + { + continue; + } + common->Warning( "brush has multiple area portal sides at %s", s2->visibleHull->GetCenter().ToString() ); + delete s2->visibleHull; + s2->visibleHull = NULL; + } + return s; + } + } + } + return NULL; +} + +/* +============= +FloodAreas_r +============= +*/ +void FloodAreas_r( node_t* node ) +{ + uPortal_t* p; + int s; + + if( node->area != -1 ) + { + return; // allready got it + } + if( node->opaque ) + { + return; + } + + c_areaFloods++; + node->area = c_areas; + + for( p = node->portals ; p ; p = p->next[s] ) + { + node_t* other; + + s = ( p->nodes[1] == node ); + other = p->nodes[!s]; + + if( !Portal_Passable( p ) ) + { + continue; + } + + // can't flood through an area portal + if( FindSideForPortal( p ) ) + { + continue; + } + + FloodAreas_r( other ); + } +} + +/* +============= +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->opaque ) + { + return; + } + + if( node->area != -1 ) + { + return; // allready got it + } + + c_areaFloods = 0; + FloodAreas_r( node ); + common->Printf( "area %i has %i leafs\n", c_areas, c_areaFloods ); + c_areas++; +} + +/* +============ +CheckAreas_r +============ +*/ +void CheckAreas_r( node_t* node ) +{ + if( node->planenum != PLANENUM_LEAF ) + { + CheckAreas_r( node->children[0] ); + CheckAreas_r( node->children[1] ); + return; + } + if( !node->opaque && node->area < 0 ) + { + common->Error( "CheckAreas_r: area = %i", node->area ); + } +} + +/* +============ +ClearAreas_r + +Set all the areas to -1 before filling +============ +*/ +void ClearAreas_r( node_t* node ) +{ + if( node->planenum != PLANENUM_LEAF ) + { + ClearAreas_r( node->children[0] ); + ClearAreas_r( node->children[1] ); + return; + } + node->area = -1; +} + +//============================================================= + + +/* +================= +FindInterAreaPortals_r + +================= +*/ +static void FindInterAreaPortals_r( node_t* node ) +{ + uPortal_t* p; + int s; + int i; + idWinding* w; + interAreaPortal_t* iap; + side_t* side; + + if( node->planenum != PLANENUM_LEAF ) + { + FindInterAreaPortals_r( node->children[0] ); + FindInterAreaPortals_r( node->children[1] ); + return; + } + + if( node->opaque ) + { + return; + } + + for( p = node->portals ; p ; p = p->next[s] ) + { + node_t* other; + + s = ( p->nodes[1] == node ); + other = p->nodes[!s]; + + if( other->opaque ) + { + continue; + } + + // only report areas going from lower number to higher number + // so we don't report the portal twice + if( other->area <= node->area ) + { + continue; + } + + side = FindSideForPortal( p ); +// w = p->winding; + if( !side ) + { + common->Warning( "FindSideForPortal failed at %s", p->winding->GetCenter().ToString() ); + continue; + } + w = side->visibleHull; + if( !w ) + { + continue; + } + + // see if we have created this portal before + for( i = 0 ; i < numInterAreaPortals ; i++ ) + { + iap = &interAreaPortals[i]; + + if( side == iap->side && + ( ( p->nodes[0]->area == iap->area0 && p->nodes[1]->area == iap->area1 ) + || ( p->nodes[1]->area == iap->area0 && p->nodes[0]->area == iap->area1 ) ) ) + { + break; + } + } + + if( i != numInterAreaPortals ) + { + continue; // already emited + } + + iap = &interAreaPortals[numInterAreaPortals]; + numInterAreaPortals++; + if( side->planenum == p->onnode->planenum ) + { + iap->area0 = p->nodes[0]->area; + iap->area1 = p->nodes[1]->area; + } + else + { + iap->area0 = p->nodes[1]->area; + iap->area1 = p->nodes[0]->area; + } + iap->side = side; + + } +} + + + + + +/* +============= +FloodAreas + +Mark each leaf with an area, bounded by CONTENTS_AREAPORTAL +Sets e->areas.numAreas +============= +*/ +void FloodAreas( uEntity_t* e ) +{ + common->Printf( "--- FloodAreas ---\n" ); + + // set all areas to -1 + ClearAreas_r( e->tree->headnode ); + + // flood fill from non-opaque areas + c_areas = 0; + FindAreas_r( e->tree->headnode ); + + common->Printf( "%5i areas\n", c_areas ); + e->numAreas = c_areas; + + // make sure we got all of them + CheckAreas_r( e->tree->headnode ); + + // identify all portals between areas if this is the world + if( e == &dmapGlobals.uEntities[0] ) + { + numInterAreaPortals = 0; + FindInterAreaPortals_r( e->tree->headnode ); + } +} + +/* +====================================================== + +FILL OUTSIDE + +====================================================== +*/ + +static int c_outside; +static int c_inside; +static 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->opaque ) + { + c_outside++; + node->opaque = true; + } + else + { + c_solid++; + } + } + else + { + c_inside++; + } + +} + +/* +============= +FillOutside + +Fill (set node->opaque = true) all nodes that can't be reached by entities +============= +*/ +void FillOutside( uEntity_t* e ) +{ + c_outside = 0; + c_inside = 0; + c_solid = 0; + common->Printf( "--- FillOutside ---\n" ); + FillOutside_r( e->tree->headnode ); + common->Printf( "%5i solid leafs\n", c_solid ); + common->Printf( "%5i leafs filled\n", c_outside ); + common->Printf( "%5i inside leafs\n", c_inside ); +} diff --git a/neo/tools/compilers/dmap/tritjunction.cpp b/neo/tools/compilers/dmap/tritjunction.cpp new file mode 100644 index 00000000..c9089c4e --- /dev/null +++ b/neo/tools/compilers/dmap/tritjunction.cpp @@ -0,0 +1,751 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "dmap.h" + +/* + + T junction fixing never creates more xyz points, but + new vertexes will be created when different surfaces + cause a fix + + The vertex cleaning accomplishes two goals: removing extranious low order + bits to avoid numbers like 1.000001233, and grouping nearby vertexes + together. Straight truncation accomplishes the first foal, but two vertexes + only a tiny epsilon apart could still be spread to different snap points. + To avoid this, we allow the merge test to group points together that + snapped to neighboring integer coordinates. + + Snaping verts can drag some triangles backwards or collapse them to points, + which will cause them to be removed. + + + When snapping to ints, a point can move a maximum of sqrt(3)/2 distance + Two points that were an epsilon apart can then become sqrt(3) apart + + A case that causes recursive overflow with point to triangle fixing: + + A + C D + B + + Triangle ABC tests against point D and splits into triangles ADC and DBC + Triangle DBC then tests against point A again and splits into ABC and ADB + infinite recursive loop + + + For a given source triangle + init the no-check list to hold the three triangle hashVerts + + recursiveFixTriAgainstHash + + recursiveFixTriAgainstHashVert_r + if hashVert is on the no-check list + exit + if the hashVert should split the triangle + add to the no-check list + recursiveFixTriAgainstHash(a) + recursiveFixTriAgainstHash(b) + +*/ + +#define SNAP_FRACTIONS 32 +//#define SNAP_FRACTIONS 8 +//#define SNAP_FRACTIONS 1 + +#define VERTEX_EPSILON ( 1.0 / SNAP_FRACTIONS ) + +#define COLINEAR_EPSILON ( 1.8 * VERTEX_EPSILON ) + +#define HASH_BINS 16 + +typedef struct hashVert_s +{ + struct hashVert_s* next; + idVec3 v; + int iv[3]; +} hashVert_t; + +static idBounds hashBounds; +static idVec3 hashScale; +static hashVert_t* hashVerts[HASH_BINS][HASH_BINS][HASH_BINS]; +static int numHashVerts, numTotalVerts; +static int hashIntMins[3], hashIntScale[3]; + +/* +=============== +GetHashVert + +Also modifies the original vert to the snapped value +=============== +*/ +struct hashVert_s* GetHashVert( idVec3& v ) +{ + int iv[3]; + int block[3]; + int i; + hashVert_t* hv; + + numTotalVerts++; + + // snap the vert to integral values + for( i = 0 ; i < 3 ; i++ ) + { + iv[i] = floor( ( v[i] + 0.5 / SNAP_FRACTIONS ) * SNAP_FRACTIONS ); + block[i] = ( iv[i] - hashIntMins[i] ) / hashIntScale[i]; + if( block[i] < 0 ) + { + block[i] = 0; + } + else if( block[i] >= HASH_BINS ) + { + block[i] = HASH_BINS - 1; + } + } + + // see if a vertex near enough already exists + // this could still fail to find a near neighbor right at the hash block boundary + for( hv = hashVerts[block[0]][block[1]][block[2]] ; hv ; hv = hv->next ) + { + for( i = 0 ; i < 3 ; i++ ) + { + int d; + d = hv->iv[i] - iv[i]; + if( d < -1 || d > 1 ) + { + break; + } + } + if( i == 3 ) + { + v = hv->v; + return hv; + } + } + + // create a new one + hv = ( hashVert_t* )Mem_Alloc( sizeof( *hv ), TAG_TOOLS ); + + hv->next = hashVerts[block[0]][block[1]][block[2]]; + hashVerts[block[0]][block[1]][block[2]] = hv; + + hv->iv[0] = iv[0]; + hv->iv[1] = iv[1]; + hv->iv[2] = iv[2]; + + hv->v[0] = ( float )iv[0] / SNAP_FRACTIONS; + hv->v[1] = ( float )iv[1] / SNAP_FRACTIONS; + hv->v[2] = ( float )iv[2] / SNAP_FRACTIONS; + + v = hv->v; + + numHashVerts++; + + return hv; +} + + +/* +================== +HashBlocksForTri + +Returns an inclusive bounding box of hash +bins that should hold the triangle +================== +*/ +static void HashBlocksForTri( const mapTri_t* tri, int blocks[2][3] ) +{ + idBounds bounds; + int i; + + bounds.Clear(); + bounds.AddPoint( tri->v[0].xyz ); + bounds.AddPoint( tri->v[1].xyz ); + bounds.AddPoint( tri->v[2].xyz ); + + // add a 1.0 slop margin on each side + for( i = 0 ; i < 3 ; i++ ) + { + blocks[0][i] = ( bounds[0][i] - 1.0 - hashBounds[0][i] ) / hashScale[i]; + if( blocks[0][i] < 0 ) + { + blocks[0][i] = 0; + } + else if( blocks[0][i] >= HASH_BINS ) + { + blocks[0][i] = HASH_BINS - 1; + } + + blocks[1][i] = ( bounds[1][i] + 1.0 - hashBounds[0][i] ) / hashScale[i]; + if( blocks[1][i] < 0 ) + { + blocks[1][i] = 0; + } + else if( blocks[1][i] >= HASH_BINS ) + { + blocks[1][i] = HASH_BINS - 1; + } + } +} + + +/* +================= +HashTriangles + +Removes triangles that are degenerated or flipped backwards +================= +*/ +void HashTriangles( optimizeGroup_t* groupList ) +{ + mapTri_t* a; + int vert; + int i; + optimizeGroup_t* group; + + // clear the hash tables + memset( hashVerts, 0, sizeof( hashVerts ) ); + + numHashVerts = 0; + numTotalVerts = 0; + + // bound all the triangles to determine the bucket size + hashBounds.Clear(); + for( group = groupList ; group ; group = group->nextGroup ) + { + for( a = group->triList ; a ; a = a->next ) + { + hashBounds.AddPoint( a->v[0].xyz ); + hashBounds.AddPoint( a->v[1].xyz ); + hashBounds.AddPoint( a->v[2].xyz ); + } + } + + // spread the bounds so it will never have a zero size + for( i = 0 ; i < 3 ; i++ ) + { + hashBounds[0][i] = floor( hashBounds[0][i] - 1 ); + hashBounds[1][i] = ceil( hashBounds[1][i] + 1 ); + hashIntMins[i] = hashBounds[0][i] * SNAP_FRACTIONS; + + hashScale[i] = ( hashBounds[1][i] - hashBounds[0][i] ) / HASH_BINS; + hashIntScale[i] = hashScale[i] * SNAP_FRACTIONS; + if( hashIntScale[i] < 1 ) + { + hashIntScale[i] = 1; + } + } + + // add all the points to the hash buckets + for( group = groupList ; group ; group = group->nextGroup ) + { + // don't create tjunctions against discrete surfaces (blood decals, etc) + if( group->material != NULL && group->material->IsDiscrete() ) + { + continue; + } + for( a = group->triList ; a ; a = a->next ) + { + for( vert = 0 ; vert < 3 ; vert++ ) + { + a->hashVert[vert] = GetHashVert( a->v[vert].xyz ); + } + } + } +} + +/* +================= +FreeTJunctionHash + +The optimizer may add some more crossing verts +after t junction processing +================= +*/ +void FreeTJunctionHash( void ) +{ + int i, j, k; + hashVert_t* hv, *next; + + for( i = 0 ; i < HASH_BINS ; i++ ) + { + for( j = 0 ; j < HASH_BINS ; j++ ) + { + for( k = 0 ; k < HASH_BINS ; k++ ) + { + for( hv = hashVerts[i][j][k] ; hv ; hv = next ) + { + next = hv->next; + Mem_Free( hv ); + } + } + } + } + memset( hashVerts, 0, sizeof( hashVerts ) ); +} + + +/* +================== +FixTriangleAgainstHashVert + +Returns a list of two new mapTri if the hashVert is +on an edge of the given mapTri, otherwise returns NULL. +================== +*/ +static mapTri_t* FixTriangleAgainstHashVert( const mapTri_t* a, const hashVert_t* hv ) +{ + int i; + const idDrawVert* v1, *v2, *v3; + idDrawVert split; + idVec3 dir; + float len; + float frac; + mapTri_t* new1, *new2; + idVec3 temp; + float d, off; + const idVec3* v; + idPlane plane1, plane2; + + v = &hv->v; + + // if the triangle already has this hashVert as a vert, + // it can't be split by it + if( a->hashVert[0] == hv || a->hashVert[1] == hv || a->hashVert[2] == hv ) + { + return NULL; + } + + // we probably should find the edge that the vertex is closest to. + // it is possible to be < 1 unit away from multiple + // edges, but we only want to split by one of them + for( i = 0 ; i < 3 ; i++ ) + { + v1 = &a->v[i]; + v2 = &a->v[( i + 1 ) % 3]; + v3 = &a->v[( i + 2 ) % 3]; + dir = v2->xyz - v1->xyz; + + len = dir.Normalize(); + + // if it is close to one of the edge vertexes, skip it + temp = *v - v1->xyz; + d = temp * dir; + if( d <= 0 || d >= len ) + { + continue; + } + + // make sure it is on the line + VectorMA( v1->xyz, d, dir, temp ); + temp = *v - temp; + off = temp.Length(); + if( off <= -COLINEAR_EPSILON || off >= COLINEAR_EPSILON ) + { + continue; + } + + // take the x/y/z from the splitter, + // but interpolate everything else from the original tri + split.xyz = *v; + frac = d / len; + + idVec2 st; + st.x = v1->GetTexCoordS() + frac * ( v2->GetTexCoordS() - v1->GetTexCoordS() ); + st.y = v1->GetTexCoordT() + frac * ( v2->GetTexCoordT() - v1->GetTexCoordT() ); + split.SetTexCoord( st ); + idVec3 splitNormal; + idVec3 v1Normal = v1->GetNormal(); + idVec3 v2Normal = v2->GetNormal(); + splitNormal[0] = v1Normal[0] + frac * ( v2Normal[0] - v1Normal[0] ); + splitNormal[1] = v1Normal[1] + frac * ( v2Normal[1] - v1Normal[1] ); + splitNormal[2] = v1Normal[2] + frac * ( v2Normal[2] - v1Normal[2] ); + splitNormal.Normalize(); + split.SetNormal( splitNormal ); + + // split the tri + new1 = CopyMapTri( a ); + new1->v[( i + 1 ) % 3] = split; + new1->hashVert[( i + 1 ) % 3] = hv; + new1->next = NULL; + + new2 = CopyMapTri( a ); + new2->v[i] = split; + new2->hashVert[i] = hv; + new2->next = new1; + + plane1.FromPoints( new1->hashVert[0]->v, new1->hashVert[1]->v, new1->hashVert[2]->v ); + plane2.FromPoints( new2->hashVert[0]->v, new2->hashVert[1]->v, new2->hashVert[2]->v ); + + d = plane1.ToVec4() * plane2.ToVec4(); + + // if the two split triangle's normals don't face the same way, + // it should not be split + if( d <= 0 ) + { + FreeTriList( new2 ); + continue; + } + + return new2; + } + + + return NULL; +} + + + +/* +================== +FixTriangleAgainstHash + +Potentially splits a triangle into a list of triangles based on tjunctions +================== +*/ +static mapTri_t* FixTriangleAgainstHash( const mapTri_t* tri ) +{ + mapTri_t* fixed; + mapTri_t* a; + mapTri_t* test, *next; + int blocks[2][3]; + int i, j, k; + hashVert_t* hv; + + // if this triangle is degenerate after point snapping, + // do nothing (this shouldn't happen, because they should + // be removed as they are hashed) + if( tri->hashVert[0] == tri->hashVert[1] + || tri->hashVert[0] == tri->hashVert[2] + || tri->hashVert[1] == tri->hashVert[2] ) + { + return NULL; + } + + fixed = CopyMapTri( tri ); + fixed->next = NULL; + + HashBlocksForTri( tri, blocks ); + for( i = blocks[0][0] ; i <= blocks[1][0] ; i++ ) + { + for( j = blocks[0][1] ; j <= blocks[1][1] ; j++ ) + { + for( k = blocks[0][2] ; k <= blocks[1][2] ; k++ ) + { + for( hv = hashVerts[i][j][k] ; hv ; hv = hv->next ) + { + // fix all triangles in the list against this point + test = fixed; + fixed = NULL; + for( ; test ; test = next ) + { + next = test->next; + a = FixTriangleAgainstHashVert( test, hv ); + if( a ) + { + // cut into two triangles + a->next->next = fixed; + fixed = a; + FreeTri( test ); + } + else + { + test->next = fixed; + fixed = test; + } + } + } + } + } + } + + return fixed; +} + + +/* +================== +CountGroupListTris +================== +*/ +int CountGroupListTris( const optimizeGroup_t* groupList ) +{ + int c; + + c = 0; + for( ; groupList ; groupList = groupList->nextGroup ) + { + c += CountTriList( groupList->triList ); + } + + return c; +} + +/* +================== +FixAreaGroupsTjunctions +================== +*/ +void FixAreaGroupsTjunctions( optimizeGroup_t* groupList ) +{ + const mapTri_t* tri; + mapTri_t* newList; + mapTri_t* fixed; + int startCount, endCount; + optimizeGroup_t* group; + + if( dmapGlobals.noTJunc ) + { + return; + } + + if( !groupList ) + { + return; + } + + startCount = CountGroupListTris( groupList ); + + if( dmapGlobals.verbose ) + { + common->Printf( "----- FixAreaGroupsTjunctions -----\n" ); + common->Printf( "%6i triangles in\n", startCount ); + } + + HashTriangles( groupList ); + + for( group = groupList ; group ; group = group->nextGroup ) + { + // don't touch discrete surfaces + if( group->material != NULL && group->material->IsDiscrete() ) + { + continue; + } + + newList = NULL; + for( tri = group->triList ; tri ; tri = tri->next ) + { + fixed = FixTriangleAgainstHash( tri ); + newList = MergeTriLists( newList, fixed ); + } + FreeTriList( group->triList ); + group->triList = newList; + } + + endCount = CountGroupListTris( groupList ); + if( dmapGlobals.verbose ) + { + common->Printf( "%6i triangles out\n", endCount ); + } +} + + +/* +================== +FixEntityTjunctions +================== +*/ +void FixEntityTjunctions( uEntity_t* e ) +{ + int i; + + for( i = 0 ; i < e->numAreas ; i++ ) + { + FixAreaGroupsTjunctions( e->areas[i].groups ); + FreeTJunctionHash(); + } +} + +/* +================== +FixGlobalTjunctions +================== +*/ +void FixGlobalTjunctions( uEntity_t* e ) +{ + mapTri_t* a; + int vert; + int i; + optimizeGroup_t* group; + int areaNum; + + common->Printf( "----- FixGlobalTjunctions -----\n" ); + + // clear the hash tables + memset( hashVerts, 0, sizeof( hashVerts ) ); + + numHashVerts = 0; + numTotalVerts = 0; + + // bound all the triangles to determine the bucket size + hashBounds.Clear(); + for( areaNum = 0 ; areaNum < e->numAreas ; areaNum++ ) + { + for( group = e->areas[areaNum].groups ; group ; group = group->nextGroup ) + { + for( a = group->triList ; a ; a = a->next ) + { + hashBounds.AddPoint( a->v[0].xyz ); + hashBounds.AddPoint( a->v[1].xyz ); + hashBounds.AddPoint( a->v[2].xyz ); + } + } + } + + // spread the bounds so it will never have a zero size + for( i = 0 ; i < 3 ; i++ ) + { + hashBounds[0][i] = floor( hashBounds[0][i] - 1 ); + hashBounds[1][i] = ceil( hashBounds[1][i] + 1 ); + hashIntMins[i] = hashBounds[0][i] * SNAP_FRACTIONS; + + hashScale[i] = ( hashBounds[1][i] - hashBounds[0][i] ) / HASH_BINS; + hashIntScale[i] = hashScale[i] * SNAP_FRACTIONS; + if( hashIntScale[i] < 1 ) + { + hashIntScale[i] = 1; + } + } + + // add all the points to the hash buckets + for( areaNum = 0 ; areaNum < e->numAreas ; areaNum++ ) + { + for( group = e->areas[areaNum].groups ; group ; group = group->nextGroup ) + { + // don't touch discrete surfaces + if( group->material != NULL && group->material->IsDiscrete() ) + { + continue; + } + + for( a = group->triList ; a ; a = a->next ) + { + for( vert = 0 ; vert < 3 ; vert++ ) + { + a->hashVert[vert] = GetHashVert( a->v[vert].xyz ); + } + } + } + } + + // add all the func_static model vertexes to the hash buckets + // optionally inline some of the func_static models + if( dmapGlobals.entityNum == 0 ) + { + for( int eNum = 1 ; eNum < dmapGlobals.num_entities ; eNum++ ) + { + uEntity_t* entity = &dmapGlobals.uEntities[eNum]; + const char* className = entity->mapEntity->epairs.GetString( "classname" ); + if( idStr::Icmp( className, "func_static" ) ) + { + continue; + } + const char* modelName = entity->mapEntity->epairs.GetString( "model" ); + if( !modelName ) + { + continue; + } + if( !strstr( modelName, ".lwo" ) && !strstr( modelName, ".ase" ) && !strstr( modelName, ".ma" ) ) + { + continue; + } + + idRenderModel* model = renderModelManager->FindModel( modelName ); + +// common->Printf( "adding T junction verts for %s.\n", entity->mapEntity->epairs.GetString( "name" ) ); + + idMat3 axis; + // get the rotation matrix in either full form, or single angle form + if( !entity->mapEntity->epairs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) + { + float angle = entity->mapEntity->epairs.GetFloat( "angle" ); + if( angle != 0.0f ) + { + axis = idAngles( 0.0f, angle, 0.0f ).ToMat3(); + } + else + { + axis.Identity(); + } + } + + idVec3 origin = entity->mapEntity->epairs.GetVector( "origin" ); + + for( i = 0 ; i < model->NumSurfaces() ; i++ ) + { + const modelSurface_t* surface = model->Surface( i ); + const srfTriangles_t* tri = surface->geometry; + + mapTri_t mapTri; + memset( &mapTri, 0, sizeof( mapTri ) ); + mapTri.material = surface->shader; + // don't let discretes (autosprites, etc) merge together + if( mapTri.material->IsDiscrete() ) + { + mapTri.mergeGroup = ( void* )surface; + } + for( int j = 0 ; j < tri->numVerts ; j += 3 ) + { + idVec3 v = tri->verts[j].xyz * axis + origin; + GetHashVert( v ); + } + } + } + } + + + + // now fix each area + for( areaNum = 0 ; areaNum < e->numAreas ; areaNum++ ) + { + for( group = e->areas[areaNum].groups ; group ; group = group->nextGroup ) + { + // don't touch discrete surfaces + if( group->material != NULL && group->material->IsDiscrete() ) + { + continue; + } + + mapTri_t* newList = NULL; + for( mapTri_t* tri = group->triList ; tri ; tri = tri->next ) + { + mapTri_t* fixed = FixTriangleAgainstHash( tri ); + newList = MergeTriLists( newList, fixed ); + } + FreeTriList( group->triList ); + group->triList = newList; + } + } + + + // done + FreeTJunctionHash(); +} diff --git a/neo/tools/compilers/dmap/tritools.cpp b/neo/tools/compilers/dmap/tritools.cpp new file mode 100644 index 00000000..65f3aeba --- /dev/null +++ b/neo/tools/compilers/dmap/tritools.cpp @@ -0,0 +1,428 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "dmap.h" + +/* + + All triangle list functions should behave reasonably with NULL lists. + +*/ + +/* +=============== +AllocTri +=============== +*/ +mapTri_t* AllocTri( void ) +{ + mapTri_t* tri; + + tri = ( mapTri_t* )Mem_Alloc( sizeof( *tri ), TAG_TOOLS ); + memset( tri, 0, sizeof( *tri ) ); + return tri; +} + +/* +=============== +FreeTri +=============== +*/ +void FreeTri( mapTri_t* tri ) +{ + Mem_Free( tri ); +} + + +/* +=============== +MergeTriLists + +This does not copy any tris, it just relinks them +=============== +*/ +mapTri_t* MergeTriLists( mapTri_t* a, mapTri_t* b ) +{ + mapTri_t** prev; + + prev = &a; + while( *prev ) + { + prev = &( *prev )->next; + } + + *prev = b; + + return a; +} + + +/* +=============== +FreeTriList +=============== +*/ +void FreeTriList( mapTri_t* a ) +{ + mapTri_t* next; + + for( ; a ; a = next ) + { + next = a->next; + Mem_Free( a ); + } +} + +/* +=============== +CopyTriList +=============== +*/ +mapTri_t* CopyTriList( const mapTri_t* a ) +{ + mapTri_t* testList; + const mapTri_t* tri; + + testList = NULL; + for( tri = a ; tri ; tri = tri->next ) + { + mapTri_t* copy; + + copy = CopyMapTri( tri ); + copy ->next = testList; + testList = copy; + } + + return testList; +} + + +/* +============= +CountTriList +============= +*/ +int CountTriList( const mapTri_t* tri ) +{ + int c; + + c = 0; + while( tri ) + { + c++; + tri = tri->next; + } + + return c; +} + + +/* +=============== +CopyMapTri +=============== +*/ +mapTri_t* CopyMapTri( const mapTri_t* tri ) +{ + mapTri_t* t; + + t = ( mapTri_t* )Mem_Alloc( sizeof( *t ), TAG_TOOLS ); + *t = *tri; + + return t; +} + +/* +=============== +MapTriArea +=============== +*/ +float MapTriArea( const mapTri_t* tri ) +{ + return idWinding::TriangleArea( tri->v[0].xyz, tri->v[1].xyz, tri->v[2].xyz ); +} + +/* +=============== +RemoveBadTris + +Return a new list with any zero or negative area triangles removed +=============== +*/ +mapTri_t* RemoveBadTris( const mapTri_t* list ) +{ + mapTri_t* newList; + mapTri_t* copy; + const mapTri_t* tri; + + newList = NULL; + + for( tri = list ; tri ; tri = tri->next ) + { + if( MapTriArea( tri ) > 0 ) + { + copy = CopyMapTri( tri ); + copy->next = newList; + newList = copy; + } + } + + return newList; +} + +/* +================ +BoundTriList +================ +*/ +void BoundTriList( const mapTri_t* list, idBounds& b ) +{ + b.Clear(); + for( ; list ; list = list->next ) + { + b.AddPoint( list->v[0].xyz ); + b.AddPoint( list->v[1].xyz ); + b.AddPoint( list->v[2].xyz ); + } +} + +/* +================ +DrawTri +================ +*/ +void DrawTri( const mapTri_t* tri ) +{ + idWinding w; + + w.SetNumPoints( 3 ); + w[0] = tri->v[0].xyz; + w[1] = tri->v[1].xyz; + w[2] = tri->v[2].xyz; + DrawWinding( &w ); +} + + +/* +================ +FlipTriList + +Swaps the vertex order +================ +*/ +void FlipTriList( mapTri_t* tris ) +{ + mapTri_t* tri; + + for( tri = tris ; tri ; tri = tri->next ) + { + idDrawVert v; + const struct hashVert_s* hv; + struct optVertex_s* ov; + + v = tri->v[0]; + tri->v[0] = tri->v[2]; + tri->v[2] = v; + + hv = tri->hashVert[0]; + tri->hashVert[0] = tri->hashVert[2]; + tri->hashVert[2] = hv; + + ov = tri->optVert[0]; + tri->optVert[0] = tri->optVert[2]; + tri->optVert[2] = ov; + } +} + +/* +================ +WindingForTri +================ +*/ +idWinding* WindingForTri( const mapTri_t* tri ) +{ + idWinding* w; + + w = new idWinding( 3 ); + w->SetNumPoints( 3 ); + ( *w )[0] = tri->v[0].xyz; + ( *w )[1] = tri->v[1].xyz; + ( *w )[2] = tri->v[2].xyz; + return w; +} + +/* +================ +TriVertsFromOriginal + +Regenerate the texcoords and colors on a fragmented tri from the plane equations +================ +*/ +void TriVertsFromOriginal( mapTri_t* tri, const mapTri_t* original ) +{ + int i, j; + float denom; + + denom = idWinding::TriangleArea( original->v[0].xyz, original->v[1].xyz, original->v[2].xyz ); + if( denom == 0 ) + { + return; // original was degenerate, so it doesn't matter + } + + for( i = 0 ; i < 3 ; i++ ) + { + float a, b, c; + + // find the barycentric coordinates + a = idWinding::TriangleArea( tri->v[i].xyz, original->v[1].xyz, original->v[2].xyz ) / denom; + b = idWinding::TriangleArea( tri->v[i].xyz, original->v[2].xyz, original->v[0].xyz ) / denom; + c = idWinding::TriangleArea( tri->v[i].xyz, original->v[0].xyz, original->v[1].xyz ) / denom; + + // regenerate the interpolated values + tri->v[i].SetTexCoordS( a * original->v[0].GetTexCoordS() + + b * original->v[1].GetTexCoordS() + c * original->v[2].GetTexCoordS() ); + tri->v[i].SetTexCoordT( a * original->v[0].GetTexCoordT() + + b * original->v[1].GetTexCoordT() + c * original->v[2].GetTexCoordT() ); + + idVec3 normal = tri->v[i].GetNormal(); + for( j = 0 ; j < 3 ; j++ ) + { + normal[j] = a * original->v[0].GetNormal()[j] + + b * original->v[1].GetNormal()[j] + c * original->v[2].GetNormal()[j]; + } + normal.Normalize(); + tri->v[i].SetNormal( normal ); + } +} + +/* +================ +WindingToTriList + +Generates a new list of triangles with proper texcoords from a winding +created by clipping the originalTri + +OriginalTri can be NULL if you don't care about texCoords +================ +*/ +mapTri_t* WindingToTriList( const idWinding* w, const mapTri_t* originalTri ) +{ + mapTri_t* tri; + mapTri_t* triList; + int i, j; + const idVec3* vec; + + if( !w ) + { + return NULL; + } + + triList = NULL; + for( i = 2 ; i < w->GetNumPoints() ; i++ ) + { + tri = AllocTri(); + if( !originalTri ) + { + memset( tri, 0, sizeof( *tri ) ); + } + else + { + *tri = *originalTri; + } + tri->next = triList; + triList = tri; + + for( j = 0 ; j < 3 ; j++ ) + { + if( j == 0 ) + { + vec = &( ( *w )[0] ).ToVec3(); + } + else if( j == 1 ) + { + vec = &( ( *w )[i - 1] ).ToVec3(); + } + else + { + vec = &( ( *w )[i] ).ToVec3(); + } + tri->v[j].xyz = *vec; + } + if( originalTri ) + { + TriVertsFromOriginal( tri, originalTri ); + } + } + + return triList; +} + + +/* +================== +ClipTriList +================== +*/ +void ClipTriList( const mapTri_t* list, const idPlane& plane, float epsilon, + mapTri_t** front, mapTri_t** back ) +{ + const mapTri_t* tri; + mapTri_t* newList; + idWinding* w, *frontW, *backW; + + *front = NULL; + *back = NULL; + + for( tri = list ; tri ; tri = tri->next ) + { + w = WindingForTri( tri ); + w->Split( plane, epsilon, &frontW, &backW ); + + newList = WindingToTriList( frontW, tri ); + *front = MergeTriLists( *front, newList ); + + newList = WindingToTriList( backW, tri ); + *back = MergeTriLists( *back, newList ); + + delete w; + } + +} + +/* +================== +PlaneForTri +================== +*/ +void PlaneForTri( const mapTri_t* tri, idPlane& plane ) +{ + plane.FromPoints( tri->v[0].xyz, tri->v[1].xyz, tri->v[2].xyz ); +} diff --git a/neo/tools/compilers/dmap/ubrush.cpp b/neo/tools/compilers/dmap/ubrush.cpp new file mode 100644 index 00000000..e6d5fda8 --- /dev/null +++ b/neo/tools/compilers/dmap/ubrush.cpp @@ -0,0 +1,753 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "dmap.h" + +int c_active_brushes; + +int c_nodes; + +// if a brush just barely pokes onto the other side, +// let it slide by without chopping +#define PLANESIDE_EPSILON 0.001 +//0.1 + + + + +/* +================ +CountBrushList +================ +*/ +int CountBrushList( uBrush_t* brushes ) +{ + int c; + + c = 0; + for( ; brushes ; brushes = brushes->next ) + c++; + return c; +} + + +int BrushSizeForSides( int numsides ) +{ + int c; + + // allocate a structure with a variable number of sides at the end +// c = (int)&(((uBrush_t *)0)->sides[numsides]); // bounds checker complains about this + c = sizeof( uBrush_t ) + sizeof( side_t ) * ( numsides - 6 ); + + return c; +} + +/* +================ +AllocBrush +================ +*/ +uBrush_t* AllocBrush( int numsides ) +{ + uBrush_t* bb; + int c; + + c = BrushSizeForSides( numsides ); + + bb = ( uBrush_t* )Mem_Alloc( c, TAG_TOOLS ); + memset( bb, 0, c ); + c_active_brushes++; + return bb; +} + +/* +================ +FreeBrush +================ +*/ +void FreeBrush( uBrush_t* brushes ) +{ + int i; + + for( i = 0 ; i < brushes->numsides ; i++ ) + { + if( brushes->sides[i].winding ) + { + delete brushes->sides[i].winding; + } + if( brushes->sides[i].visibleHull ) + { + delete brushes->sides[i].visibleHull; + } + } + Mem_Free( brushes ); + c_active_brushes--; +} + + +/* +================ +FreeBrushList +================ +*/ +void FreeBrushList( uBrush_t* brushes ) +{ + uBrush_t* next; + + for( ; brushes ; brushes = next ) + { + next = brushes->next; + + FreeBrush( brushes ); + } +} + +/* +================== +CopyBrush + +Duplicates the brush, the sides, and the windings +================== +*/ +uBrush_t* CopyBrush( uBrush_t* brush ) +{ + uBrush_t* newbrush; + int size; + int i; + + size = BrushSizeForSides( brush->numsides ); + + newbrush = AllocBrush( brush->numsides ); + memcpy( newbrush, brush, size ); + + for( i = 0 ; i < brush->numsides ; i++ ) + { + if( brush->sides[i].winding ) + newbrush->sides[i].winding = brush->sides[i].winding->Copy(); + } + + return newbrush; +} + + +/* +================ +DrawBrushList +================ +*/ +void DrawBrushList( uBrush_t* brush ) +{ + int i; + side_t* s; + + GLS_BeginScene(); + for( ; brush ; brush = brush->next ) + { + for( i = 0 ; i < brush->numsides ; i++ ) + { + s = &brush->sides[i]; + if( !s->winding ) + continue; + GLS_Winding( s->winding, 0 ); + } + } + GLS_EndScene(); +} + + +/* +============= +PrintBrush +============= +*/ +void PrintBrush( uBrush_t* brush ) +{ + int i; + + common->Printf( "brush: %p\n", brush ); + for( i = 0; i < brush->numsides ; i++ ) + { + brush->sides[i].winding->Print(); + common->Printf( "\n" ); + } +} + +/* +================== +BoundBrush + +Sets the mins/maxs based on the windings +returns false if the brush doesn't enclose a valid volume +================== +*/ +bool BoundBrush( uBrush_t* brush ) +{ + int i, j; + idWinding* w; + + brush->bounds.Clear(); + for( i = 0; i < brush->numsides; i++ ) + { + w = brush->sides[i].winding; + if( !w ) + continue; + for( j = 0; j < w->GetNumPoints(); j++ ) + brush->bounds.AddPoint( ( *w )[j].ToVec3() ); + } + + for( i = 0; i < 3; i++ ) + { + if( brush->bounds[0][i] < MIN_WORLD_COORD || brush->bounds[1][i] > MAX_WORLD_COORD + || brush->bounds[0][i] >= brush->bounds[1][i] ) + { + return false; + } + } + + return true; +} + +/* +================== +CreateBrushWindings + +makes basewindigs for sides and mins / maxs for the brush +returns false if the brush doesn't enclose a valid volume +================== +*/ +bool CreateBrushWindings( uBrush_t* brush ) +{ + int i, j; + idWinding* w; + idPlane* plane; + side_t* side; + + for( i = 0; i < brush->numsides; i++ ) + { + side = &brush->sides[i]; + plane = &dmapGlobals.mapPlanes[side->planenum]; + w = new idWinding( *plane ); + for( j = 0; j < brush->numsides && w; j++ ) + { + if( i == j ) + { + continue; + } + if( brush->sides[j].planenum == ( brush->sides[i].planenum ^ 1 ) ) + { + continue; // back side clipaway + } + plane = &dmapGlobals.mapPlanes[brush->sides[j].planenum ^ 1]; + w = w->Clip( *plane, 0 );//CLIP_EPSILON); + } + if( side->winding ) + { + delete side->winding; + } + side->winding = w; + } + + return BoundBrush( brush ); +} + +/* +================== +BrushFromBounds + +Creates a new axial brush +================== +*/ +uBrush_t* BrushFromBounds( const idBounds& bounds ) +{ + uBrush_t* b; + int i; + idPlane plane; + + b = AllocBrush( 6 ); + b->numsides = 6; + for( i = 0 ; i < 3 ; i++ ) + { + plane[0] = plane[1] = plane[2] = 0; + plane[i] = 1; + plane[3] = -bounds[1][i]; + b->sides[i].planenum = FindFloatPlane( plane ); + + plane[i] = -1; + plane[3] = bounds[0][i]; + b->sides[3 + i].planenum = FindFloatPlane( plane ); + } + + CreateBrushWindings( b ); + + return b; +} + +/* +================== +BrushVolume + +================== +*/ +float BrushVolume( uBrush_t* brush ) +{ + int i; + idWinding* w; + idVec3 corner; + float d, area, volume; + idPlane* plane; + + if( !brush ) + return 0; + + // grab the first valid point as the corner + + w = NULL; + for( i = 0; i < brush->numsides; i++ ) + { + w = brush->sides[i].winding; + if( w ) + break; + } + if( !w ) + { + return 0; + } + corner = ( *w )[0].ToVec3(); + + // make tetrahedrons to all other faces + + volume = 0; + for( ; i < brush->numsides; i++ ) + { + w = brush->sides[i].winding; + if( !w ) + continue; + plane = &dmapGlobals.mapPlanes[brush->sides[i].planenum]; + d = -plane->Distance( corner ); + area = w->GetArea(); + volume += d * area; + } + + volume /= 3; + return volume; +} + + +/* +================== +WriteBspBrushMap + +FIXME: use new brush format +================== +*/ +void WriteBspBrushMap( const char* name, uBrush_t* list ) +{ + idFile* f; + side_t* s; + int i; + idWinding* w; + + common->Printf( "writing %s\n", name ); + f = fileSystem->OpenFileWrite( name ); + + if( !f ) + { + common->Error( "Can't write %s\b", name ); + } + + f->Printf( "{\n\"classname\" \"worldspawn\"\n" ); + + for( ; list ; list = list->next ) + { + f->Printf( "{\n" ); + for( i = 0, s = list->sides ; i < list->numsides ; i++, s++ ) + { + w = new idWinding( dmapGlobals.mapPlanes[s->planenum] ); + + f->Printf( "( %i %i %i ) ", ( int )( *w )[0][0], ( int )( *w )[0][1], ( int )( *w )[0][2] ); + f->Printf( "( %i %i %i ) ", ( int )( *w )[1][0], ( int )( *w )[1][1], ( int )( *w )[1][2] ); + f->Printf( "( %i %i %i ) ", ( int )( *w )[2][0], ( int )( *w )[2][1], ( int )( *w )[2][2] ); + + f->Printf( "notexture 0 0 0 1 1\n" ); + delete w; + } + f->Printf( "}\n" ); + } + f->Printf( "}\n" ); + + fileSystem->CloseFile( f ); + +} + + +//===================================================================================== + +/* +==================== +FilterBrushIntoTree_r + +==================== +*/ +int FilterBrushIntoTree_r( uBrush_t* b, node_t* node ) +{ + uBrush_t* front, *back; + int c; + + if( !b ) + { + return 0; + } + + // add it to the leaf list + if( node->planenum == PLANENUM_LEAF ) + { + b->next = node->brushlist; + node->brushlist = b; + + // classify the leaf by the structural brush + if( b->opaque ) + { + node->opaque = true; + } + + return 1; + } + + // split it by the node plane + SplitBrush( b, node->planenum, &front, &back ); + FreeBrush( b ); + + c = 0; + c += FilterBrushIntoTree_r( front, node->children[0] ); + c += FilterBrushIntoTree_r( back, node->children[1] ); + + return c; +} + + +/* +===================== +FilterBrushesIntoTree + +Mark the leafs as opaque and areaportals and put brush +fragments in each leaf so portal surfaces can be matched +to materials +===================== +*/ +void FilterBrushesIntoTree( uEntity_t* e ) +{ + primitive_t* prim; + uBrush_t* b, *newb; + int r; + int c_unique, c_clusters; + + common->Printf( "----- FilterBrushesIntoTree -----\n" ); + + c_unique = 0; + c_clusters = 0; + for( prim = e->primitives ; prim ; prim = prim->next ) + { + b = prim->brush; + if( !b ) + { + continue; + } + c_unique++; + newb = CopyBrush( b ); + r = FilterBrushIntoTree_r( newb, e->tree->headnode ); + c_clusters += r; + } + + common->Printf( "%5i total brushes\n", c_unique ); + common->Printf( "%5i cluster references\n", c_clusters ); +} + + + +/* +================ +AllocTree +================ +*/ +tree_t* AllocTree( void ) +{ + tree_t* tree; + + tree = ( tree_t* )Mem_Alloc( sizeof( *tree ), TAG_TOOLS ); + memset( tree, 0, sizeof( *tree ) ); + tree->bounds.Clear(); + + return tree; +} + +/* +================ +AllocNode +================ +*/ +node_t* AllocNode( void ) +{ + node_t* node; + + node = ( node_t* )Mem_Alloc( sizeof( *node ), TAG_TOOLS ); + memset( node, 0, sizeof( *node ) ); + + return node; +} + +//============================================================ + +/* +================== +BrushMostlyOnSide + +================== +*/ +int BrushMostlyOnSide( uBrush_t* brush, idPlane& plane ) +{ + int i, j; + idWinding* w; + float d, max; + int side; + + max = 0; + side = PSIDE_FRONT; + for( i = 0; i < brush->numsides; i++ ) + { + w = brush->sides[i].winding; + if( !w ) + continue; + for( j = 0; j < w->GetNumPoints(); j++ ) + { + d = plane.Distance( ( *w )[j].ToVec3() ); + 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( uBrush_t* brush, int planenum, uBrush_t** front, uBrush_t** back ) +{ + uBrush_t* b[2]; + int i, j; + idWinding* w, *cw[2], *midwinding; + side_t* s, *cs; + float d, d_front, d_back; + + *front = *back = NULL; + idPlane& plane = dmapGlobals.mapPlanes[planenum]; + + // check all points + d_front = d_back = 0; + for( i = 0; i < brush->numsides; i++ ) + { + w = brush->sides[i].winding; + if( !w ) + { + continue; + } + for( j = 0; j < w->GetNumPoints(); j++ ) + { + d = plane.Distance( ( *w )[j].ToVec3() ); + 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; + } + + // create a new winding from the split plane + + w = new idWinding( plane ); + for( i = 0; i < brush->numsides && w; i++ ) + { + idPlane& plane2 = dmapGlobals.mapPlanes[brush->sides[i].planenum ^ 1]; + w = w->Clip( plane2, 0 ); // PLANESIDE_EPSILON); + } + + if( !w || w->IsTiny() ) + { + // 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( w->IsHuge() ) + { + common->Printf( "WARNING: huge winding\n" ); + } + + midwinding = w; + + // split it for real + + for( i = 0; i < 2; i++ ) + { + b[i] = AllocBrush( brush->numsides + 1 ); + memcpy( b[i], brush, sizeof( uBrush_t ) - sizeof( brush->sides ) ); + b[i]->numsides = 0; + b[i]->next = NULL; + b[i]->original = brush->original; + } + + // split all the current windings + + for( i = 0; i < brush->numsides; i++ ) + { + s = &brush->sides[i]; + w = s->winding; + if( !w ) + continue; + w->Split( plane, 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1] ); + for( j = 0; j < 2; j++ ) + { + if( !cw[j] ) + { + continue; + } + /* + if ( cw[j]->IsTiny() ) + { + delete cw[j]; + continue; + } + */ + cs = &b[j]->sides[b[j]->numsides]; + b[j]->numsides++; + *cs = *s; + cs->winding = cw[j]; + } + } + + + // see if we have valid polygons on both sides + + for( i = 0 ; i < 2 ; i++ ) + { + if( !BoundBrush( b[i] ) ) + { + break; + } + + if( b[i]->numsides < 3 ) + { + FreeBrush( b[i] ); + b[i] = NULL; + } + } + + if( !( b[0] && b[1] ) ) + { + if( !b[0] && !b[1] ) + common->Printf( "split removed brush\n" ); + else + common->Printf( "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->material = NULL; + if( i == 0 ) + cs->winding = midwinding->Copy(); + else + cs->winding = midwinding; + } + + { + float v1; + int i; + + for( i = 0 ; i < 2 ; i++ ) + { + v1 = BrushVolume( b[i] ); + if( v1 < 1.0 ) + { + FreeBrush( b[i] ); + b[i] = NULL; +// common->Printf ("tiny volume after clip\n"); + } + } + } + + *front = b[0]; + *back = b[1]; +} diff --git a/neo/tools/compilers/dmap/usurface.cpp b/neo/tools/compilers/dmap/usurface.cpp new file mode 100644 index 00000000..0a711ffc --- /dev/null +++ b/neo/tools/compilers/dmap/usurface.cpp @@ -0,0 +1,1064 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "dmap.h" + + +#define TEXTURE_OFFSET_EQUAL_EPSILON 0.005 +#define TEXTURE_VECTOR_EQUAL_EPSILON 0.001 + +/* +=============== +AddTriListToArea + +The triList is appended to the apropriate optimzeGroup_t, +creating a new one if needed. +The entire list is assumed to come from the same planar primitive +=============== +*/ +static void AddTriListToArea( uEntity_t* e, mapTri_t* triList, int planeNum, int areaNum, textureVectors_t* texVec ) +{ + uArea_t* area; + optimizeGroup_t* group; + int i, j; + + if( !triList ) + { + return; + } + + area = &e->areas[areaNum]; + for( group = area->groups ; group ; group = group->nextGroup ) + { + if( group->material == triList->material + && group->planeNum == planeNum + && group->mergeGroup == triList->mergeGroup ) + { + // check the texture vectors + for( i = 0 ; i < 2 ; i++ ) + { + for( j = 0 ; j < 3 ; j++ ) + { + if( idMath::Fabs( texVec->v[i][j] - group->texVec.v[i][j] ) > TEXTURE_VECTOR_EQUAL_EPSILON ) + { + break; + } + } + if( j != 3 ) + { + break; + } + if( idMath::Fabs( texVec->v[i][3] - group->texVec.v[i][3] ) > TEXTURE_OFFSET_EQUAL_EPSILON ) + { + break; + } + } + if( i == 2 ) + { + break; // exact match + } + else + { + // different texture offsets + i = 1; // just for debugger breakpoint + } + } + } + + if( !group ) + { + group = ( optimizeGroup_t* )Mem_Alloc( sizeof( *group ), TAG_TOOLS ); + memset( group, 0, sizeof( *group ) ); + group->planeNum = planeNum; + group->mergeGroup = triList->mergeGroup; + group->material = triList->material; + group->nextGroup = area->groups; + group->texVec = *texVec; + area->groups = group; + } + + group->triList = MergeTriLists( group->triList, triList ); +} + +/* +=================== +TexVecForTri +=================== +*/ +static void TexVecForTri( textureVectors_t* texVec, mapTri_t* tri ) +{ + float area, inva; + idVec3 temp; + idVec5 d0, d1; + idDrawVert* a, *b, *c; + + a = &tri->v[0]; + b = &tri->v[1]; + c = &tri->v[2]; + + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + d0[3] = b->GetTexCoordS() - a->GetTexCoordS(); + d0[4] = b->GetTexCoordT() - a->GetTexCoordT(); + + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + d1[3] = c->GetTexCoordS() - a->GetTexCoordS(); + d1[4] = c->GetTexCoordT() - a->GetTexCoordT(); + + area = d0[3] * d1[4] - d0[4] * d1[3]; + inva = 1.0 / area; + + temp[0] = ( d0[0] * d1[4] - d0[4] * d1[0] ) * inva; + temp[1] = ( d0[1] * d1[4] - d0[4] * d1[1] ) * inva; + temp[2] = ( d0[2] * d1[4] - d0[4] * d1[2] ) * inva; + temp.Normalize(); + texVec->v[0].ToVec3() = temp; + texVec->v[0][3] = tri->v[0].xyz * texVec->v[0].ToVec3() - tri->v[0].GetTexCoordS(); + + temp[0] = ( d0[3] * d1[0] - d0[0] * d1[3] ) * inva; + temp[1] = ( d0[3] * d1[1] - d0[1] * d1[3] ) * inva; + temp[2] = ( d0[3] * d1[2] - d0[2] * d1[3] ) * inva; + temp.Normalize(); + texVec->v[1].ToVec3() = temp; + texVec->v[1][3] = tri->v[0].xyz * texVec->v[0].ToVec3() - tri->v[0].GetTexCoordT(); +} + + +/* +================= +TriListForSide +================= +*/ +//#define SNAP_FLOAT_TO_INT 8 +#define SNAP_FLOAT_TO_INT 256 +#define SNAP_INT_TO_FLOAT (1.0/SNAP_FLOAT_TO_INT) + +mapTri_t* TriListForSide( const side_t* s, const idWinding* w ) +{ + int i, j; + idDrawVert* dv; + mapTri_t* tri, *triList; + const idVec3* vec; + const idMaterial* si; + + si = s->material; + + // skip any generated faces + if( !si ) + { + return NULL; + } + + // don't create faces for non-visible sides + if( !si->SurfaceCastsShadow() && !si->IsDrawn() ) + { + return NULL; + } + + if( 1 ) + { + // triangle fan using only the outer verts + // this gives the minimum triangle count, + // but may have some very distended triangles + triList = NULL; + for( i = 2 ; i < w->GetNumPoints() ; i++ ) + { + tri = AllocTri(); + tri->material = si; + tri->next = triList; + triList = tri; + + for( j = 0 ; j < 3 ; j++ ) + { + if( j == 0 ) + { + vec = &( ( *w )[0] ).ToVec3(); + } + else if( j == 1 ) + { + vec = &( ( *w )[i - 1] ).ToVec3(); + } + else + { + vec = &( ( *w )[i] ).ToVec3(); + } + + dv = tri->v + j; +#if 0 + // round the xyz to a given precision + for( k = 0 ; k < 3 ; k++ ) + { + dv->xyz[k] = SNAP_INT_TO_FLOAT * floor( vec[k] * SNAP_FLOAT_TO_INT + 0.5 ); + } +#else + dv->xyz = *vec; +#endif + + // calculate texture s/t from brush primitive texture matrix + idVec2 st; + st.x = ( dv->xyz * s->texVec.v[0].ToVec3() ) + s->texVec.v[0][3]; + st.y = ( dv->xyz * s->texVec.v[1].ToVec3() ) + s->texVec.v[1][3]; + + dv->SetTexCoord( st ); + + // copy normal + dv->SetNormal( dmapGlobals.mapPlanes[s->planenum].Normal() ); + if( dv->GetNormal().Length() < 0.9 || dv->GetNormal().Length() > 1.1 ) + { + common->Error( "Bad normal in TriListForSide" ); + } + } + } + } + else + { + // triangle fan from central point, more verts and tris, but less distended + // I use this when debugging some tjunction problems + triList = NULL; + for( i = 0 ; i < w->GetNumPoints() ; i++ ) + { + idVec3 midPoint; + + tri = AllocTri(); + tri->material = si; + tri->next = triList; + triList = tri; + + for( j = 0 ; j < 3 ; j++ ) + { + if( j == 0 ) + { + vec = &midPoint; + midPoint = w->GetCenter(); + } + else if( j == 1 ) + { + vec = &( ( *w )[i] ).ToVec3(); + } + else + { + vec = &( ( *w )[( i + 1 ) % w->GetNumPoints()] ).ToVec3(); + } + + dv = tri->v + j; + dv->xyz = *vec; + + idVec2 st; + st.x = ( dv->xyz * s->texVec.v[0].ToVec3() ) + s->texVec.v[0][3]; + st.y = ( dv->xyz * s->texVec.v[1].ToVec3() ) + s->texVec.v[1][3]; + dv->SetTexCoord( st ); + + // copy normal + dv->SetNormal( dmapGlobals.mapPlanes[s->planenum].Normal() ); + if( dv->GetNormal().Length() < 0.9f || dv->GetNormal().Length() > 1.1f ) + { + common->Error( "Bad normal in TriListForSide" ); + } + } + } + } + + // set merge groups if needed, to prevent multiple sides from being + // merged into a single surface in the case of gui shaders, mirrors, and autosprites + if( s->material->IsDiscrete() ) + { + for( tri = triList ; tri ; tri = tri->next ) + { + tri->mergeGroup = ( void* )s; + } + } + + return triList; +} + +//================================================================================= + +/* +==================== +ClipSideByTree_r + +Adds non-opaque leaf fragments to the convex hull +==================== +*/ +static void ClipSideByTree_r( idWinding* w, side_t* side, node_t* node ) +{ + idWinding* front, *back; + + if( !w ) + { + return; + } + + if( node->planenum != PLANENUM_LEAF ) + { + if( side->planenum == node->planenum ) + { + ClipSideByTree_r( w, side, node->children[0] ); + return; + } + if( side->planenum == ( node->planenum ^ 1 ) ) + { + ClipSideByTree_r( w, side, node->children[1] ); + return; + } + + w->Split( dmapGlobals.mapPlanes[ node->planenum ], ON_EPSILON, &front, &back ); + delete w; + + ClipSideByTree_r( front, side, node->children[0] ); + ClipSideByTree_r( back, side, node->children[1] ); + + return; + } + + // if opaque leaf, don't add + if( !node->opaque ) + { + if( !side->visibleHull ) + { + side->visibleHull = w->Copy(); + } + else + { + side->visibleHull->AddToConvexHull( w, dmapGlobals.mapPlanes[ side->planenum ].Normal() ); + } + } + + delete w; + return; +} + + +/* +===================== +ClipSidesByTree + +Creates side->visibleHull for all visible sides + +The visible hull for a side will consist of the convex hull of +all points in non-opaque clusters, which allows overlaps +to be trimmed off automatically. +===================== +*/ +void ClipSidesByTree( uEntity_t* e ) +{ + uBrush_t* b; + int i; + idWinding* w; + side_t* side; + primitive_t* prim; + + common->Printf( "----- ClipSidesByTree -----\n" ); + + for( prim = e->primitives ; prim ; prim = prim->next ) + { + b = prim->brush; + if( !b ) + { + // FIXME: other primitives! + continue; + } + for( i = 0 ; i < b->numsides ; i++ ) + { + side = &b->sides[i]; + if( !side->winding ) + { + continue; + } + w = side->winding->Copy(); + side->visibleHull = NULL; + ClipSideByTree_r( w, side, e->tree->headnode ); + // for debugging, we can choose to use the entire original side + // but we skip this if the side was completely clipped away + if( side->visibleHull && dmapGlobals.noClipSides ) + { + delete side->visibleHull; + side->visibleHull = side->winding->Copy(); + } + } + } +} + + + +//================================================================================= + +/* +==================== +ClipTriIntoTree_r + +This is used for adding curve triangles +The winding will be freed before it returns +==================== +*/ +void ClipTriIntoTree_r( idWinding* w, mapTri_t* originalTri, uEntity_t* e, node_t* node ) +{ + idWinding* front, *back; + + if( !w ) + { + return; + } + + if( node->planenum != PLANENUM_LEAF ) + { + w->Split( dmapGlobals.mapPlanes[ node->planenum ], ON_EPSILON, &front, &back ); + delete w; + + ClipTriIntoTree_r( front, originalTri, e, node->children[0] ); + ClipTriIntoTree_r( back, originalTri, e, node->children[1] ); + + return; + } + + // if opaque leaf, don't add + if( !node->opaque && node->area >= 0 ) + { + mapTri_t* list; + int planeNum; + idPlane plane; + textureVectors_t texVec; + + list = WindingToTriList( w, originalTri ); + + PlaneForTri( originalTri, plane ); + planeNum = FindFloatPlane( plane ); + + TexVecForTri( &texVec, originalTri ); + + AddTriListToArea( e, list, planeNum, node->area, &texVec ); + } + + delete w; + return; +} + + + +//============================================================= + +/* +==================== +CheckWindingInAreas_r + +Returns the area number that the winding is in, or +-2 if it crosses multiple areas. + +==================== +*/ +static int CheckWindingInAreas_r( const idWinding* w, node_t* node ) +{ + idWinding* front, *back; + + if( !w ) + { + return -1; + } + + if( node->planenum != PLANENUM_LEAF ) + { + int a1, a2; +#if 0 + if( side->planenum == node->planenum ) + { + return CheckWindingInAreas_r( w, node->children[0] ); + } + if( side->planenum == ( node->planenum ^ 1 ) ) + { + return CheckWindingInAreas_r( w, node->children[1] ); + } +#endif + w->Split( dmapGlobals.mapPlanes[ node->planenum ], ON_EPSILON, &front, &back ); + + a1 = CheckWindingInAreas_r( front, node->children[0] ); + delete front; + a2 = CheckWindingInAreas_r( back, node->children[1] ); + delete back; + + if( a1 == -2 || a2 == -2 ) + { + return -2; // different + } + if( a1 == -1 ) + { + return a2; // one solid + } + if( a2 == -1 ) + { + return a1; // one solid + } + + if( a1 != a2 ) + { + return -2; // cross areas + } + return a1; + } + + return node->area; +} + + + +/* +==================== +PutWindingIntoAreas_r + +Clips a winding down into the bsp tree, then converts +the fragments to triangles and adds them to the area lists +==================== +*/ +static void PutWindingIntoAreas_r( uEntity_t* e, const idWinding* w, side_t* side, node_t* node ) +{ + idWinding* front, *back; + int area; + + if( !w ) + { + return; + } + + if( node->planenum != PLANENUM_LEAF ) + { + if( side->planenum == node->planenum ) + { + PutWindingIntoAreas_r( e, w, side, node->children[0] ); + return; + } + if( side->planenum == ( node->planenum ^ 1 ) ) + { + PutWindingIntoAreas_r( e, w, side, node->children[1] ); + return; + } + + // see if we need to split it + // adding the "noFragment" flag to big surfaces like sky boxes + // will avoid potentially dicing them up into tons of triangles + // that take forever to optimize back together + if( !dmapGlobals.fullCarve || side->material->NoFragment() ) + { + area = CheckWindingInAreas_r( w, node ); + if( area >= 0 ) + { + mapTri_t* tri; + + // put in single area + tri = TriListForSide( side, w ); + AddTriListToArea( e, tri, side->planenum, area, &side->texVec ); + return; + } + } + + w->Split( dmapGlobals.mapPlanes[ node->planenum ], ON_EPSILON, &front, &back ); + + PutWindingIntoAreas_r( e, front, side, node->children[0] ); + if( front ) + { + delete front; + } + + PutWindingIntoAreas_r( e, back, side, node->children[1] ); + if( back ) + { + delete back; + } + + return; + } + + // if opaque leaf, don't add + if( node->area >= 0 && !node->opaque ) + { + mapTri_t* tri; + + tri = TriListForSide( side, w ); + AddTriListToArea( e, tri, side->planenum, node->area, &side->texVec ); + } +} + +/* +================== +AddMapTriToAreas + +Used for curves and inlined models +================== +*/ +void AddMapTriToAreas( mapTri_t* tri, uEntity_t* e ) +{ + int area; + idWinding* w; + + // skip degenerate triangles from pinched curves + if( MapTriArea( tri ) <= 0 ) + { + return; + } + + if( dmapGlobals.fullCarve ) + { + // always fragment into areas + w = WindingForTri( tri ); + ClipTriIntoTree_r( w, tri, e, e->tree->headnode ); + return; + } + + w = WindingForTri( tri ); + area = CheckWindingInAreas_r( w, e->tree->headnode ); + delete w; + if( area == -1 ) + { + return; + } + if( area >= 0 ) + { + mapTri_t* newTri; + idPlane plane; + int planeNum; + textureVectors_t texVec; + + // put in single area + newTri = CopyMapTri( tri ); + newTri->next = NULL; + + PlaneForTri( tri, plane ); + planeNum = FindFloatPlane( plane ); + + TexVecForTri( &texVec, newTri ); + + AddTriListToArea( e, newTri, planeNum, area, &texVec ); + } + else + { + // fragment into areas + w = WindingForTri( tri ); + ClipTriIntoTree_r( w, tri, e, e->tree->headnode ); + } +} + +/* +===================== +PutPrimitivesInAreas + +===================== +*/ +void PutPrimitivesInAreas( uEntity_t* e ) +{ + uBrush_t* b; + int i; + side_t* side; + primitive_t* prim; + mapTri_t* tri; + + common->Printf( "----- PutPrimitivesInAreas -----\n" ); + + // allocate space for surface chains for each area + e->areas = ( uArea_t* )Mem_Alloc( e->numAreas * sizeof( e->areas[0] ), TAG_TOOLS ); + memset( e->areas, 0, e->numAreas * sizeof( e->areas[0] ) ); + + // for each primitive, clip it to the non-solid leafs + // and divide it into different areas + for( prim = e->primitives ; prim ; prim = prim->next ) + { + b = prim->brush; + + if( !b ) + { + // add curve triangles + for( tri = prim->tris ; tri ; tri = tri->next ) + { + AddMapTriToAreas( tri, e ); + } + continue; + } + + // clip in brush sides + for( i = 0 ; i < b->numsides ; i++ ) + { + side = &b->sides[i]; + if( !side->visibleHull ) + { + continue; + } + PutWindingIntoAreas_r( e, side->visibleHull, side, e->tree->headnode ); + } + } + + + // optionally inline some of the func_static models + if( dmapGlobals.entityNum == 0 ) + { + bool inlineAll = dmapGlobals.uEntities[0].mapEntity->epairs.GetBool( "inlineAllStatics" ); + + for( int eNum = 1 ; eNum < dmapGlobals.num_entities ; eNum++ ) + { + uEntity_t* entity = &dmapGlobals.uEntities[eNum]; + const char* className = entity->mapEntity->epairs.GetString( "classname" ); + if( idStr::Icmp( className, "func_static" ) ) + { + continue; + } + if( !entity->mapEntity->epairs.GetBool( "inline" ) && !inlineAll ) + { + continue; + } + const char* modelName = entity->mapEntity->epairs.GetString( "model" ); + if( !modelName ) + { + continue; + } + idRenderModel* model = renderModelManager->FindModel( modelName ); + + common->Printf( "inlining %s.\n", entity->mapEntity->epairs.GetString( "name" ) ); + + idMat3 axis; + // get the rotation matrix in either full form, or single angle form + if( !entity->mapEntity->epairs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) + { + float angle = entity->mapEntity->epairs.GetFloat( "angle" ); + if( angle != 0.0f ) + { + axis = idAngles( 0.0f, angle, 0.0f ).ToMat3(); + } + else + { + axis.Identity(); + } + } + + idVec3 origin = entity->mapEntity->epairs.GetVector( "origin" ); + + for( i = 0 ; i < model->NumSurfaces() ; i++ ) + { + const modelSurface_t* surface = model->Surface( i ); + const srfTriangles_t* tri = surface->geometry; + + mapTri_t mapTri; + memset( &mapTri, 0, sizeof( mapTri ) ); + mapTri.material = surface->shader; + // don't let discretes (autosprites, etc) merge together + if( mapTri.material->IsDiscrete() ) + { + mapTri.mergeGroup = ( void* )surface; + } + for( int j = 0 ; j < tri->numIndexes ; j += 3 ) + { + for( int k = 0 ; k < 3 ; k++ ) + { + idVec3 v = tri->verts[tri->indexes[j + k]].xyz; + + mapTri.v[k].xyz = v * axis + origin; + + mapTri.v[k].SetNormal( tri->verts[tri->indexes[j + k]].GetNormal() * axis ); + mapTri.v[k].SetTexCoord( tri->verts[tri->indexes[j + k]].GetTexCoord() ); + } + AddMapTriToAreas( &mapTri, e ); + } + } + } + } +} + +//============================================================================ + +/* +================= +ClipTriByLight + +Carves a triangle by the frustom planes of a light, producing +a (possibly empty) list of triangles on the inside and outside. + +The original triangle is not modified. + +If no clipping is required, the result will be a copy of the original. + +If clipping was required, the outside fragments will be planar clips, which +will benefit from re-optimization. +================= +*/ +static void ClipTriByLight( const mapLight_t* light, const mapTri_t* tri, + mapTri_t** in, mapTri_t** out ) +{ + idWinding* inside, *oldInside; + idWinding* outside[6]; + bool hasOutside; + int i; + + *in = NULL; + *out = NULL; + + // clip this winding to the light + inside = WindingForTri( tri ); + hasOutside = false; + for( i = 0 ; i < 6 ; i++ ) + { + oldInside = inside; + if( oldInside ) + { +// oldInside->Split( light->def.frustum[i], 0, &outside[i], &inside ); + // delete oldInside; + assert( !oldInside ); + } + else + { + outside[i] = NULL; + } + if( outside[i] ) + { + hasOutside = true; + } + } + + if( !inside ) + { + // the entire winding is outside this light + + // free the clipped fragments + for( i = 0 ; i < 6 ; i++ ) + { + if( outside[i] ) + { + delete outside[i]; + } + } + + *out = CopyMapTri( tri ); + ( *out )->next = NULL; + + return; + } + + if( !hasOutside ) + { + // the entire winding is inside this light + + // free the inside copy + delete inside; + + *in = CopyMapTri( tri ); + ( *in )->next = NULL; + + return; + } + + // the winding is split + *in = WindingToTriList( inside, tri ); + delete inside; + + // combine all the outside fragments + for( i = 0 ; i < 6 ; i++ ) + { + if( outside[i] ) + { + mapTri_t* list; + + list = WindingToTriList( outside[i], tri ); + delete outside[i]; + *out = MergeTriLists( *out, list ); + } + } +} + +/* +================= +BoundOptimizeGroup +================= +*/ +static void BoundOptimizeGroup( optimizeGroup_t* group ) +{ + group->bounds.Clear(); + for( mapTri_t* tri = group->triList ; tri ; tri = tri->next ) + { + group->bounds.AddPoint( tri->v[0].xyz ); + group->bounds.AddPoint( tri->v[1].xyz ); + group->bounds.AddPoint( tri->v[2].xyz ); + } +} + +/* +==================== +CarveGroupsByLight + +Divide each group into an inside group and an outside group, based +on which fragments are illuminated by the light's beam tree +==================== +*/ +static void CarveGroupsByLight( uEntity_t* e, mapLight_t* light ) +{ + int i; + optimizeGroup_t* group, *newGroup, *carvedGroups, *nextGroup; + mapTri_t* tri, *inside, *outside; + uArea_t* area; + + for( i = 0 ; i < e->numAreas ; i++ ) + { + area = &e->areas[i]; + carvedGroups = NULL; + + // we will be either freeing or reassigning the groups as we go + for( group = area->groups ; group ; group = nextGroup ) + { + nextGroup = group->nextGroup; + + // if the surface doesn't get lit, don't carve it up + if( ( light->def.lightShader->IsFogLight() && !group->material->ReceivesFog() ) + || ( !light->def.lightShader->IsFogLight() && !group->material->ReceivesLighting() ) + || !group->bounds.IntersectsBounds( light->def.globalLightBounds ) ) + { + + group->nextGroup = carvedGroups; + carvedGroups = group; + continue; + } + + if( group->numGroupLights == MAX_GROUP_LIGHTS ) + { + common->Error( "MAX_GROUP_LIGHTS around %f %f %f", + group->triList->v[0].xyz[0], group->triList->v[0].xyz[1], group->triList->v[0].xyz[2] ); + } + + // if the group doesn't face the light, + // it won't get carved at all + if( !light->def.lightShader->LightEffectsBackSides() && + !group->material->ReceivesLightingOnBackSides() && + dmapGlobals.mapPlanes[ group->planeNum ].Distance( light->def.parms.origin ) <= 0 ) + { + + group->nextGroup = carvedGroups; + carvedGroups = group; + continue; + } + + // split into lists for hit-by-light, and not-hit-by-light + inside = NULL; + outside = NULL; + + for( tri = group->triList ; tri ; tri = tri->next ) + { + mapTri_t* in, *out; + + ClipTriByLight( light, tri, &in, &out ); + inside = MergeTriLists( inside, in ); + outside = MergeTriLists( outside, out ); + } + + if( inside ) + { + newGroup = ( optimizeGroup_t* )Mem_Alloc( sizeof( *newGroup ), TAG_TOOLS ); + *newGroup = *group; + newGroup->groupLights[newGroup->numGroupLights] = light; + newGroup->numGroupLights++; + newGroup->triList = inside; + newGroup->nextGroup = carvedGroups; + carvedGroups = newGroup; + } + + if( outside ) + { + newGroup = ( optimizeGroup_t* )Mem_Alloc( sizeof( *newGroup ), TAG_TOOLS ); + *newGroup = *group; + newGroup->triList = outside; + newGroup->nextGroup = carvedGroups; + carvedGroups = newGroup; + } + + // free the original + group->nextGroup = NULL; + FreeOptimizeGroupList( group ); + } + + // replace this area's group list with the new one + area->groups = carvedGroups; + } +} + +/* +===================== +Prelight + +Break optimize groups up into additional groups at light boundaries, so +optimization won't cross light bounds +===================== +*/ +void Prelight( uEntity_t* e ) +{ + int i; + int start, end; + mapLight_t* light; + + // don't prelight anything but the world entity + if( dmapGlobals.entityNum != 0 ) + { + return; + } + + if( dmapGlobals.shadowOptLevel > 0 ) + { + common->Printf( "----- BuildLightShadows -----\n" ); + start = Sys_Milliseconds(); + + // calc bounds for all the groups to speed things up + for( i = 0 ; i < e->numAreas ; i++ ) + { + uArea_t* area = &e->areas[i]; + + for( optimizeGroup_t* group = area->groups ; group ; group = group->nextGroup ) + { + BoundOptimizeGroup( group ); + } + } + + end = Sys_Milliseconds(); + common->Printf( "%5.1f seconds for BuildLightShadows\n", ( end - start ) / 1000.0 ); + } + + + if( !dmapGlobals.noLightCarve ) + { + common->Printf( "----- CarveGroupsByLight -----\n" ); + start = Sys_Milliseconds(); + // now subdivide the optimize groups into additional groups for + // each light that illuminates them + for( i = 0 ; i < dmapGlobals.mapLights.Num() ; i++ ) + { + light = dmapGlobals.mapLights[i]; + CarveGroupsByLight( e, light ); + } + + end = Sys_Milliseconds(); + common->Printf( "%5.1f seconds for CarveGroupsByLight\n", ( end - start ) / 1000.0 ); + } +} + + +