/*****************************************************************************
*
* PROJECT: Multi Theft Auto v1.0
* LICENSE: See LICENSE in the top level directory
* FILE: game_sa/CWaterManagerSA.cpp
* PURPOSE: Control the lakes and seas
* DEVELOPERS: arc_
*
* Multi Theft Auto is available from http://www.multitheftauto.com/
*
*****************************************************************************/
#include <main.h>
using namespace std;
#define POLYENTRY_TYPE(entry) ( (entry)->m_wValue >> 14 )
#define POLYENTRY_ID(entry) ( (entry)->m_wValue & 0x3FFF )
#define MAKE_POLYENTRY(type, id) (WORD)( ((type) << 14) | (id) )
// These are code references in SA to the various data pools. We relocate these pools
// to our own buffers to have more space, and thus have to update all references.
DWORD CWaterManagerSA::m_VertexXrefs[] = {
0x6E5B6E, 0x6E5BC3, 0x6E5BF7, 0x6E5EA3, 0x6E5ED7, 0x6E5F84, 0x6E5F8B, 0x6E6487,
0x6E64A7, 0x6E65E4, 0x6E6608, 0x6E7B9B, 0x6E7BBC, 0x6E7C51, 0x6E7C73, 0x6E7E11,
0x6E7E18, 0x6E7E3A, 0x6E7E41, 0x6E7E5A, 0x6E7E61, 0x6E811C, 0x6E8127, 0x6E8143,
0x6E814E, 0x6E9E2F, 0x6E9E63, 0x6EA00A, 0x6EA04C, 0x6EA08E, 0x6EFC1F, 0x6EFC5E,
0x6EFC95, 0x6EFDF9, 0x6EFE31, 0x6E5B36, 0x6E5B75, 0x6E5C15, 0x6E5C45, 0x6E5EFC,
0x6E5F06, 0x6E64BF, 0x6E64E0, 0x6E6624, 0x6E6649, 0x6E7BDC, 0x6E7BF6, 0x6E7C58,
0x6E7C8E, 0x6E7DF8, 0x6E7E08, 0x6E7E31, 0x6E801C, 0x6E8029, 0x6E804B, 0x6E8060,
0x6E807B, 0x6E8088, 0x6E809A, 0x6E80BF, 0x6E80D8, 0x6E80FD, 0x6E9E8D, 0x6E9EC1,
0x6E9FFF, 0x6EA03D, 0x6EA07F, 0x6EFC18, 0x6EFC57, 0x6EFC8E, 0x6EFDCB, 0x6EFE2A,
0x6E5863, 0x6E58B7, 0x6E5945, 0x6E598F, 0x6E5B7B, 0x6E5CA9, 0x6E5CB3, 0x6E5CBB,
0x6E5CC7, 0x6E5CD3, 0x6E5D83, 0x6E5D8E, 0x6E5D96, 0x6E5DA3, 0x6E5DAE, 0x6E5FC4,
0x6E5FD0, 0x6E5FDE, 0x6E5FE4, 0x6E5FF0, 0x6E60AE, 0x6E60BA, 0x6E60C4, 0x6E60CA,
0x6E60D4, 0x6E9FC7, 0x6EFBF6, 0x6EFC35, 0x6EFC6C, 0x6EFD90, 0x6EFDC2, 0x6EFDF0,
0x6EFE1E, 0x6E5CEF, 0x6E5CFD, 0x6E5D06, 0x6E5D0D, 0x6E5D1A, 0x6E5DCB, 0x6E5DD9,
0x6E5DE2, 0x6E5DE9, 0x6E5DF4, 0x6E600C, 0x6E601A, 0x6E6025, 0x6E602C, 0x6E6039,
0x6E60F0, 0x6E60FE, 0x6E6107, 0x6E610E, 0x6E611B, 0x6E9F09, 0x6E9F41, 0x6E9F71,
0x6E9FA1, 0x6E5D30, 0x6E5D3E, 0x6E5D47, 0x6E5D4E, 0x6E5D5B, 0x6E5E0B, 0x6E5E19,
0x6E5E22, 0x6E5E29, 0x6E5E34, 0x6E604F, 0x6E605D, 0x6E6068, 0x6E606F, 0x6E607C,
0x6E6131, 0x6E613F, 0x6E6148, 0x6E614F, 0x6E615C, 0x6E9F20, 0x6E9F54, 0x6E9F84,
0x6E9FB4, 0x6EA0EE, 0x6EA149, 0x6EA193, 0x6EA1CB, 0x6EA0F5, 0x6EA150, 0x6EA19A,
0x6EA1D2, 0x000000
};
DWORD CWaterManagerSA::m_QuadXrefs[] = {
0x6E5859, 0x6E5985, 0x6E63C8, 0x6E652B, 0x6E7B88, 0x6E816A, 0x6E8635, 0x6E8709,
0x6E9E1D, 0x6E8171, 0x6E8178, 0x6EFCCF, 0x6E817F, 0x6E5849, 0x6E588A, 0x6E5979,
0x6E59B0, 0x6E8185, 0x6E8199, 0x6E81AD, 0x6E81B5, 0x6E81CB, 0x000000
};
DWORD CWaterManagerSA::m_TriangleXrefs[] = {
0x6E58AD, 0x6E593B, 0x6E7C44, 0x6E7E7F, 0x6E8673, 0x6E86E4, 0x6EFC27, 0x6E7C64,
0x6E7E8B, 0x6EFBEA, 0x6E7C7F, 0x6E7E93, 0x6EFBE2, 0x6E589D, 0x6E58DE, 0x6E592F,
0x6E5966, 0x6E7E9A, 0x6E7EA8, 0x6E7EBD, 0x6E7EC6, 0x6E7EDA, 0x6EFBD3, 0x6EFCA3,
0x000000
};
DWORD CWaterManagerSA::m_ZonePolyXrefs[] = {
0x6E57B2, 0x6E57AA, 0x6E57C8, 0x6E58F2, 0x6E638F, 0x6E86A1, 0x6E6387, 0x6E8699,
0x6E57DE, 0x6E57E8, 0x000000
};
CWaterManagerSA* g_pWaterManager = NULL;
// -----------------------------------------------------
// Water zone iterator (iterates over polys in a zone)
CWaterZoneSA::iterator::iterator ()
{
m_pCurrent = NULL;
m_pFirst = NULL;
m_bSinglePoly = false;
}
CWaterZoneSA::iterator::iterator ( CWaterZoneSA* pZone )
{
if ( POLYENTRY_TYPE ( pZone->GetInterface () ) == WATER_POLY_LIST )
{
m_pCurrent = &g_pWaterManager->m_ZonePolyPool [ POLYENTRY_ID ( pZone->GetInterface () ) ];
m_bSinglePoly = false;
}
else
{
m_pCurrent = pZone->GetInterface ();
m_bSinglePoly = true;
}
m_pFirst = m_pCurrent;
}
CWaterZoneSA::iterator::iterator ( CWaterZoneSA::iterator& other )
{
operator= ( other );
}
CWaterZoneSA::iterator& CWaterZoneSA::iterator::operator= ( CWaterZoneSA::iterator& other )
{
m_pCurrent = other.m_pCurrent;
m_pFirst = other.m_pFirst;
m_bSinglePoly = other.m_bSinglePoly;
return *this;
}
void CWaterZoneSA::iterator::operator++ ()
{
m_pCurrent++;
}
void CWaterZoneSA::iterator::operator-- ()
{
m_pCurrent--;
}
CWaterZoneSA::iterator CWaterZoneSA::iterator::operator+ ( int n )
{
iterator it ( *this );
it.m_pCurrent += n;
return it;
}
CWaterZoneSA::iterator CWaterZoneSA::iterator::operator- ( int n )
{
iterator it ( *this );
it.m_pCurrent -= n;
return it;
}
int CWaterZoneSA::iterator::operator- ( CWaterZoneSA::iterator& other )
{
return other.m_pCurrent - m_pCurrent;
}
bool CWaterZoneSA::iterator::operator== ( CWaterZoneSA::iterator& other )
{
return m_pCurrent == other.m_pCurrent;
}
bool CWaterZoneSA::iterator::operator!= ( CWaterZoneSA::iterator& other )
{
return m_pCurrent != other.m_pCurrent;
}
CWaterPolySA* CWaterZoneSA::iterator::operator* ()
{
if ( (m_bSinglePoly && m_pCurrent != m_pFirst) || m_pCurrent->m_wValue == 0 )
return NULL;
if ( POLYENTRY_TYPE ( m_pCurrent ) == WATER_POLY_QUAD )
{
return &g_pWaterManager->m_Quads [ POLYENTRY_ID ( m_pCurrent ) ];
}
else if ( POLYENTRY_TYPE ( m_pCurrent ) == WATER_POLY_TRIANGLE )
{
return &g_pWaterManager->m_Triangles [ POLYENTRY_ID ( m_pCurrent ) ];
}
return NULL;
}
CWaterZoneSA::iterator::operator CWaterPolyEntrySAInterface* ()
{
return m_pCurrent;
}
CWaterZoneSA::iterator CWaterZoneSA::begin ()
{
return iterator ( this );
}
CWaterZoneSA::iterator CWaterZoneSA::end ()
{
iterator it ( this );
while ( *it )
++it;
return it;
}
// -----------------------------------------------------
// Water zones
// SA divides the world in 500x500 squares, each of these squares is called a "zone"
// here. These zones are used for quickly finding the water polygons near a given
// point (e.g. the player for swimming, the camera for underwater post effects)
// Each zone has a list of water polygons that partially or completely overlap it.
CWaterPolyEntrySAInterface* CWaterZoneSA::AddPoly ( CWaterPoly* pPoly )
{
return AddPoly ( pPoly->GetType (), pPoly->GetID () );
}
CWaterPolyEntrySAInterface* CWaterZoneSA::AddPoly ( EWaterPolyType type, WORD wID )
{
if ( m_pInterface->m_wValue == 0 )
{
m_pInterface->m_wValue = MAKE_POLYENTRY ( type, wID );
return m_pInterface;
}
else if ( POLYENTRY_TYPE ( m_pInterface ) != WATER_POLY_LIST )
{
if ( *(DWORD *)VAR_NumWaterZonePolys + 3 > NUM_NewWaterZonePolys )
return NULL;
WORD wOffset = *(WORD *)VAR_NumWaterZonePolys;
g_pWaterManager->m_ZonePolyPool [ wOffset ].m_wValue = MAKE_POLYENTRY ( type, wID );
g_pWaterManager->m_ZonePolyPool [ wOffset + 1 ].m_wValue = m_pInterface->m_wValue;
g_pWaterManager->m_ZonePolyPool [ wOffset + 2 ].m_wValue = 0;
m_pInterface->m_wValue = MAKE_POLYENTRY ( WATER_POLY_LIST, wOffset );
*(DWORD *)VAR_NumWaterZonePolys += 3;
return &g_pWaterManager->m_ZonePolyPool [ wOffset + 1 ];
}
else
{
if ( *(DWORD *)VAR_NumWaterZonePolys + 1 > NUM_NewWaterZonePolys )
return NULL;
CWaterPolyEntrySAInterface* pZoneStart = (CWaterPolyEntrySAInterface *)begin ();
CWaterPolyEntrySAInterface* pEntry = &g_pWaterManager->m_ZonePolyPool [ *(DWORD *)VAR_NumWaterZonePolys ];
while ( pEntry > pZoneStart )
{
pEntry->m_wValue = (pEntry - 1)->m_wValue;
pEntry--;
}
pZoneStart->m_wValue = MAKE_POLYENTRY ( type, wID );
WORD wZoneStartOffset = pZoneStart - g_pWaterManager->m_ZonePolyPool;
CWaterPolyEntrySAInterface* pZoneInterface = (CWaterPolyEntrySAInterface *)ARRAY_WaterZones;
for ( ; pZoneInterface != &((CWaterPolyEntrySAInterface *)ARRAY_WaterZones) [ NUM_WaterZones ]; pZoneInterface++ )
{
if ( POLYENTRY_TYPE ( pZoneInterface ) == WATER_POLY_LIST &&
POLYENTRY_ID ( pZoneInterface ) > wZoneStartOffset )
pZoneInterface->m_wValue++;
}
(*(DWORD *)VAR_NumWaterZonePolys)++;
return pZoneStart;
}
}
bool CWaterZoneSA::RemovePoly ( CWaterPoly* pPoly )
{
return RemovePoly ( pPoly->GetType (), pPoly->GetID () );
}
bool CWaterZoneSA::RemovePoly ( EWaterPolyType type, WORD wID )
{
if ( m_pInterface->m_wValue == 0 )
{
return false;
}
else if ( POLYENTRY_TYPE ( m_pInterface ) != WATER_POLY_LIST )
{
if ( POLYENTRY_ID ( m_pInterface ) == wID )
{
m_pInterface->m_wValue = 0;
return true;
}
else
{
return false;
}
}
else
{
CWaterPolyEntrySAInterface* pEntries = (CWaterPolyEntrySAInterface *)begin ();
CWaterPolyEntrySAInterface* pEnd = &g_pWaterManager->m_ZonePolyPool [ *(DWORD *)VAR_NumWaterZonePolys ];
WORD wOffset = pEntries - g_pWaterManager->m_ZonePolyPool;
if ( end () - begin () == 2 )
{
if ( pEntries [ 0 ].m_wValue == MAKE_POLYENTRY ( type, wID ) ||
pEntries [ 1 ].m_wValue == MAKE_POLYENTRY ( type, wID ) )
{
if ( pEntries [ 0 ].m_wValue == MAKE_POLYENTRY ( type, wID ) )
m_pInterface->m_wValue = pEntries [ 1 ].m_wValue;
else
m_pInterface->m_wValue = pEntries [ 0 ].m_wValue;
CWaterPolyEntrySAInterface* pEntry = pEntries + 3;
for ( ; pEntry < pEnd; pEntry++ )
(pEntry - 3)->m_wValue = pEntry->m_wValue;
CWaterPolyEntrySAInterface* pZoneInterface = (CWaterPolyEntrySAInterface *)ARRAY_WaterZones;
for ( ; pZoneInterface < &((CWaterPolyEntrySAInterface *)ARRAY_WaterZones) [ NUM_WaterZones ]; pZoneInterface++ )
{
if ( POLYENTRY_TYPE ( pZoneInterface ) == WATER_POLY_LIST &&
POLYENTRY_ID ( pZoneInterface ) > wOffset )
pZoneInterface->m_wValue -= 3;
}
*(DWORD *)VAR_NumWaterZonePolys -= 3;
return true;
}
else
{
return false;
}
}
else
{
for ( iterator it = begin (); *it; ++it )
{
if ( (*it)->GetType () == type && (*it)->GetID () == wID )
{
CWaterPolyEntrySAInterface* pEntry = (CWaterPolyEntrySAInterface *)it + 1;
for ( ; pEntry < pEnd; pEntry++ )
(pEntry - 1)->m_wValue = pEntry->m_wValue;
CWaterPolyEntrySAInterface* pZoneInterface = (CWaterPolyEntrySAInterface *)ARRAY_WaterZones;;
for ( ; pZoneInterface < &((CWaterPolyEntrySAInterface *)ARRAY_WaterZones) [ NUM_WaterZones ]; pZoneInterface++ )
{
if ( POLYENTRY_TYPE ( pZoneInterface ) == WATER_POLY_LIST &&
POLYENTRY_ID ( pZoneInterface ) > wOffset )
pZoneInterface->m_wValue--;
}
(*(DWORD *)VAR_NumWaterZonePolys)--;
return true;
}
}
return false;
}
}
}
// -----------------------------------------------------
// Change trackkeepers
void CWaterChangeVertexMove::Undo ( void* pChangedObject )
{
((CWaterVertexSA *)pChangedObject)->SetPosition ( m_vecOriginalPosition );
}
// -----------------------------------------------------
// Manager
CWaterManagerSA::CWaterManagerSA ()
{
g_pWaterManager = this;
RelocatePools ();
InstallHooks ();
for ( DWORD i = 0; i < NUM_WaterZones; i++ )
m_Zones [ i ].SetInterface ( &((CWaterPolyEntrySAInterface *)ARRAY_WaterZones) [ i ] );
for ( DWORD i = 0; i < NUM_NewWaterVertices; i++ )
m_Vertices [ i ].SetInterface ( &m_VertexPool [ i ] );
for ( DWORD i = 0; i < NUM_NewWaterQuads; i++ )
m_Quads [ i ].SetInterface ( &m_QuadPool [ i ] );
for ( DWORD i = 0; i < NUM_NewWaterTriangles; i++ )
m_Triangles [ i ].SetInterface ( &m_TrianglePool [ i ] );
}
CWaterManagerSA::~CWaterManagerSA ()
{
UndoChanges ();
g_pWaterManager = NULL;
}
void CWaterManagerSA::RelocatePools ()
{
return;
/*
DWORD* pXrefGroups[] = { m_VertexXrefs, m_QuadXrefs, m_TriangleXrefs, m_ZonePolyXrefs, 0 };
void* pNewPools[] = { m_VertexPool, m_QuadPool, m_TrianglePool, m_ZonePolyPool, 0 };
DWORD** pXrefGroup = NULL;
void* pNewPool = NULL;
for ( int i = 0; pXrefGroup = (DWORD **)pXrefGroups [ i ]; i++ )
{
pNewPool = pNewPools [ i ];
DWORD dwDelta = (DWORD)pNewPool - *pXrefGroup [ 0 ];
for ( DWORD** ppXref = pXrefGroup; *ppXref; ppXref++ )
{
**ppXref += dwDelta;
}
}
*/
}
// The following hooks change the way SA iterates over water polygons.
// Normally it simply iterates over the first NumPolies slots in the
// pool; however in MTA, we can dynamically delete water polys,
// creating gaps. These hooks make SA skip empty pool slots.
DWORD dwHook6E9E23continue = 0x6E9E29;
void __declspec(naked) Hook6E9E23 ()
{
__asm
{
check:
mov eax, dword ptr [edi]
test eax, eax
jnz cont
add edi, 0xA // sizeof(CWaterQuadSAInterface)
jmp check
cont:
movsx eax, word ptr [edi]
lea ebx, [eax+4*eax]
jmp dwHook6E9E23continue
}
}
DWORD dwHook6EFCD7continue = 0x6EFCDD;
DWORD dwHook6EFCD7skip = 0x6EFE5E;
void __declspec(naked) Hook6EFCD7 ()
{
__asm
{
mov eax, dword ptr [esi-4]
test eax, eax
jz check
jmp dwHook6EFCD7skip
check:
add esi, 0xA // sizeof(CWaterQuadSAInterface)
mov eax, dword ptr [esi-4]
test eax, eax
jz check
jmp dwHook6EFCD7continue
}
}
DWORD dwHook6EFBD8continue = 0x6EFBDE;
void __declspec(naked) Hook6EFBD8 ()
{
__asm
{
check:
mov eax, 0x6EFC27
mov eax, dword ptr [eax]
mov eax, dword ptr [eax+8*esi]
test eax, eax
jnz cont
inc esi
jmp check
cont:
jmp dwHook6EFBD8continue
}
}
void CWaterManagerSA::InstallHooks ()
{
HookInstall ( 0x6E9E23, (DWORD)Hook6E9E23, 6 );
*(DWORD *)0x6EFCD9 = (DWORD)Hook6EFCD7 - 0x6EFCDD;
*(DWORD *)0x6EFBC7 = 0x05EBED33;
*(DWORD *)0x6EFBCB = 0x90909090;
*(BYTE *)0x6EFBCF = 0x46;
*(DWORD *)0x6EFBDA = (DWORD)Hook6EFBD8 - 0x6EFBDE;
*(BYTE *)0x6EFBFB = 0x17;
*(BYTE *)0x6EFC02 = 0x13;
*(BYTE *)0x6EFC04 = 0x57;
*(BYTE *)0x6EFC07 = 0x53;
*(BYTE *)0x6EFC0A = 0x57;
*(BYTE *)0x6EFC10 = 0x53;
*(BYTE *)0x6EFCB2 = 0x45;
*(BYTE *)0x6EFCB4 = 0xE8;
*(BYTE *)0x6EFCB7 = 0x14;
}
CWaterZoneSA* CWaterManagerSA::GetZoneContaining ( float fX, float fY )
{
if ( fX < -3000.0f || fX > 3000.0f || fY < -3000.0f || fY > 3000.0f )
return NULL;
if ( fX == 3000.0f )
fX = 2999.0f;
if ( fY == 3000.0f )
fY = 2999.0f;
int zoneID = 12*((int)(fX + 3000.0f) / 500) + (int)(fY + 3000.0f) / 500;
return &m_Zones [ zoneID ];
}
void CWaterManagerSA::GetZonesContaining ( CWaterPoly* pPoly, std::vector < CWaterZoneSA* >& out )
{
CVector v1;
CVector v2;
CVector v3;
pPoly->GetVertex ( 0 )->GetPosition ( v1 );
pPoly->GetVertex ( 1 )->GetPosition ( v2 );
pPoly->GetVertex ( 2 )->GetPosition ( v3 );
GetZonesContaining ( v1, v2, v3, out );
}
void CWaterManagerSA::GetZonesContaining ( CVector& v1, CVector& v2, CVector& v3, std::vector < CWaterZoneSA* >& out )
{
out.clear ();
float fColumnLeft = -3000.0f;
for ( int column = 0; column < 12; column++ )
{
float fRowBottom = -3000.0f;
for ( int row = 0; row < 12; row++ )
{
if ( v2.fX >= fColumnLeft && v1.fX < fColumnLeft + 500.0f && std::max<float>(v1.fY, v3.fY) >= fRowBottom && std::min<float>(v1.fY, v3.fY) < fRowBottom + 500.0f )
out.push_back ( &m_Zones [ column*12 + row ] );
fRowBottom += 500.0f;
}
fColumnLeft += 500.0f;
}
}
CWaterVertex* CWaterManagerSA::CreateVertex ( CVector& vecPosition )
{
WORD wID = ( (CreateWaterVertex_t) FUNC_CreateWaterVertex )( (long)vecPosition.fX & ~1, (long)vecPosition.fY & ~1, vecPosition.fZ, 0.2f, 0.1f, 0 );
return &m_Vertices [ wID ];
}
CWaterPoly* CWaterManagerSA::GetPolyAtPoint ( CVector& vecPosition )
{
if ( vecPosition.fX < -3000.0f || vecPosition.fX > 3000.0f || vecPosition.fY < -3000.0f || vecPosition.fY > 3000.0f )
return NULL;
CWaterZoneSA* pZone = GetZoneContaining ( vecPosition.fX, vecPosition.fY );
if ( !pZone )
return NULL;
CWaterZoneSA::iterator it;
for ( it = pZone->begin (); *it; ++it )
{
if ( (*it)->ContainsPoint ( vecPosition.fX, vecPosition.fY ) )
{
return *it;
}
}
return NULL;
}
CWaterPoly* CWaterManagerSA::CreateQuad ( CVector& vecBL, CVector& vecBR, CVector& vecTL, CVector& vecTR, bool bShallow )
{
if ( *(DWORD *)VAR_NumWaterQuads >= NUM_NewWaterQuads )
return NULL;
if ( vecTL.fX >= vecTR.fX || vecBL.fX >= vecBR.fX ||
vecTL.fY <= vecBL.fY || vecTR.fY <= vecBR.fY ||
vecTL.fX < -3000.0f || vecTL.fX > 3000.0f || vecTL.fY < -3000.0f || vecTL.fY > 3000.0f ||
vecTR.fX < -3000.0f || vecTR.fX > 3000.0f || vecTR.fY < -3000.0f || vecTR.fY > 3000.0f ||
vecBL.fX < -3000.0f || vecBL.fX > 3000.0f || vecBL.fY < -3000.0f || vecBL.fY > 3000.0f ||
vecBR.fX < -3000.0f || vecBR.fX > 3000.0f || vecBR.fY < -3000.0f || vecBR.fY > 3000.0f )
return NULL;
if ( *(DWORD *)VAR_NumWaterVertices + 4 > NUM_NewWaterVertices ||
*(DWORD *)VAR_NumWaterQuads + 1 > NUM_NewWaterQuads ||
*(DWORD *)VAR_NumWaterZonePolys + 2 > NUM_NewWaterZonePolys )
return NULL;
std::vector < CWaterZoneSA* > zones;
g_pWaterManager->GetZonesContaining ( vecBL, vecBR, vecTL, zones );
if ( zones.empty () )
return NULL;
CWaterVertex* pV1 = CreateVertex ( vecBL );
CWaterVertex* pV2 = CreateVertex ( vecBR );
CWaterVertex* pV3 = CreateVertex ( vecTL );
CWaterVertex* pV4 = CreateVertex ( vecTR );
CWaterQuadSAInterface* pInterface = g_pWaterManager->m_QuadPool;
while ( *(DWORD *)&pInterface->m_wVertexIDs != 0 )
pInterface++;
pInterface->m_wVertexIDs [ 0 ] = pV1->GetID ();
pInterface->m_wVertexIDs [ 1 ] = pV2->GetID ();
pInterface->m_wVertexIDs [ 2 ] = pV3->GetID ();
pInterface->m_wVertexIDs [ 3 ] = pV4->GetID ();
pInterface->m_wFlags = WATER_VISIBLE;
if ( bShallow )
pInterface->m_wFlags |= WATER_SHALLOW;
WORD wID = (WORD)(pInterface - g_pWaterManager->m_QuadPool);
std::vector < CWaterZoneSA* >::iterator it;
for ( it = zones.begin (); it != zones.end (); it++)
(*it)->AddPoly ( WATER_POLY_QUAD, wID );
(*(DWORD *)VAR_NumWaterQuads)++;
CWaterQuadSA* pPoly = &g_pWaterManager->m_Quads [ wID ];
return pPoly;
}