You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
580 lines
16 KiB
580 lines
16 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "disp_powerinfo.h" |
|
#include "disp_common.h" |
|
#include "commonmacros.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
// ------------------------------------------------------------------------ // |
|
// Internal classes. |
|
// ------------------------------------------------------------------------ // |
|
|
|
// These point at the vertices connecting to each of the [north,south,east,west] vertices. |
|
class CVertCorners |
|
{ |
|
public: |
|
short m_Corner1[2]; |
|
short m_Corner2[2]; |
|
}; |
|
|
|
|
|
// ------------------------------------------------------------------------ // |
|
// Globals. |
|
// ------------------------------------------------------------------------ // |
|
|
|
// This points at vertices to the side of a node (north, south, east, west). |
|
static short g_SideVertMul[4][2] = { {1,0}, {0,1}, {-1,0}, {0,-1} }; |
|
|
|
static CVertCorners g_SideVertCorners[4] = |
|
{ |
|
{ {1,-1}, {1,1} }, |
|
{ {1,1}, {-1,1} }, |
|
{ {-1,1}, {-1,-1} }, |
|
{ {-1,-1}, {1,-1} } |
|
}; |
|
|
|
// This is used in loops on child nodes. The indices point at the nodes: |
|
// 0 = upper-right |
|
// 1 = upper-left |
|
// 2 = lower-left |
|
// 3 = lower-right |
|
static CVertIndex g_ChildNodeIndexMul[4] = |
|
{ |
|
CVertIndex(1,1), |
|
CVertIndex(-1,1), |
|
CVertIndex(-1,-1), |
|
CVertIndex(1,-1) |
|
}; |
|
|
|
// These are multipliers on vertMul (not nodeMul). |
|
static CVertIndex g_ChildNodeDependencies[4][2] = |
|
{ |
|
{ CVertIndex(1,0), CVertIndex(0,1) }, |
|
{ CVertIndex(0,1), CVertIndex(-1,0) }, |
|
{ CVertIndex(-1,0), CVertIndex(0,-1) }, |
|
{ CVertIndex(0,-1), CVertIndex(1,0) } |
|
}; |
|
|
|
// 2x2 rotation matrices for each orientation. |
|
static int g_OrientationRotations[4][2][2] = |
|
{ |
|
{{1, 0}, // CCW_0 |
|
{0, 1}}, |
|
|
|
{{0, 1}, // CCW_90 |
|
{-1,0}}, |
|
|
|
{{-1,0}, // CCW_180 |
|
{0,-1}}, |
|
|
|
{{0, -1}, // CCW_270 |
|
{1, 0}} |
|
}; |
|
|
|
|
|
// ------------------------------------------------------------------------ // |
|
// Helper functions. |
|
// ------------------------------------------------------------------------ // |
|
|
|
// Apply a 2D rotation to the specified CVertIndex around the specified centerpoint. |
|
static CVertIndex Transform2D( |
|
int const mat[2][2], |
|
CVertIndex const &vert, |
|
CVertIndex const ¢erPoint ) |
|
{ |
|
CVertIndex translated = vert - centerPoint; |
|
|
|
CVertIndex transformed( |
|
translated.x*mat[0][0] + translated.y*mat[0][1], |
|
translated.x*mat[1][0] + translated.y*mat[1][1] ); |
|
|
|
return transformed + centerPoint; |
|
} |
|
|
|
|
|
// Rotate a given CVertIndex with a specified orientation. |
|
// Do this with a lookup table eventually! |
|
static void GetEdgeVertIndex( int sideLength, int iEdge, int iVert, CVertIndex &out ) |
|
{ |
|
if( iEdge == NEIGHBOREDGE_RIGHT ) |
|
{ |
|
out.x = sideLength - 1; |
|
out.y = iVert; |
|
} |
|
else if( iEdge == NEIGHBOREDGE_TOP ) |
|
{ |
|
out.x = iVert; |
|
out.y = sideLength - 1; |
|
} |
|
else if( iEdge == NEIGHBOREDGE_LEFT ) |
|
{ |
|
out.x = 0; |
|
out.y = iVert; |
|
} |
|
else |
|
{ |
|
out.x = iVert; |
|
out.y = 0; |
|
} |
|
} |
|
|
|
|
|
// Generate an index given a CVertIndex and the size of the displacement it resides in. |
|
static int VertIndex( CVertIndex const &vert, int iMaxPower ) |
|
{ |
|
return vert.y * ((1 << iMaxPower) + 1) + vert.x; |
|
} |
|
|
|
|
|
static CVertIndex WrapVertIndex( CVertIndex const &in, int sideLength ) |
|
{ |
|
int out[2]; |
|
|
|
for( int i=0; i < 2; i++ ) |
|
{ |
|
if( in[i] < 0 ) |
|
out[i] = sideLength - 1 - (-in[i] % sideLength); |
|
else if( in[i] >= sideLength ) |
|
out[i] = in[i] % sideLength; |
|
else |
|
out[i] = in[i]; |
|
} |
|
|
|
return CVertIndex( out[0], out[1] ); |
|
} |
|
|
|
|
|
static int GetFreeDependency( CVertDependency *pDep, int nElements ) |
|
{ |
|
for( int i=0; i < nElements; i++ ) |
|
{ |
|
if( !pDep[i].IsValid() ) |
|
return i; |
|
} |
|
|
|
Assert( false ); |
|
return 0; |
|
} |
|
|
|
|
|
static void AddDependency( |
|
CVertInfo *dependencies, |
|
int sideLength, |
|
CVertIndex const &nodeIndex, |
|
CVertIndex const &dependency, |
|
int iMaxPower, |
|
bool bCheckNeighborDependency, |
|
bool bAddReverseDependency ) |
|
{ |
|
int iNodeIndex = VertIndex( nodeIndex, iMaxPower ); |
|
CVertInfo *pNode = &dependencies[iNodeIndex]; |
|
|
|
int iDep = GetFreeDependency( pNode->m_Dependencies, sizeof(pNode->m_Dependencies)/sizeof(pNode->m_Dependencies[0]) ); |
|
pNode->m_Dependencies[iDep].m_iVert = dependency; |
|
pNode->m_Dependencies[iDep].m_iNeighbor = -1; |
|
|
|
if( bAddReverseDependency ) |
|
{ |
|
CVertInfo *pDep = &dependencies[VertIndex( dependency, iMaxPower )]; |
|
iDep = GetFreeDependency( pDep->m_ReverseDependencies, CVertInfo::NUM_REVERSE_DEPENDENCIES ); |
|
pDep->m_ReverseDependencies[iDep].m_iVert = nodeIndex; |
|
pDep->m_ReverseDependencies[iDep].m_iNeighbor = -1; |
|
} |
|
|
|
// Edge verts automatically add a dependency for the neighbor. |
|
// Internal verts wind up in here twice anyway so it doesn't need to |
|
if( bCheckNeighborDependency ) |
|
{ |
|
int iConnection = GetEdgeIndexFromPoint( nodeIndex, iMaxPower ); |
|
if( iConnection != -1 ) |
|
{ |
|
Assert( !pNode->m_Dependencies[1].IsValid() ); |
|
|
|
CVertIndex delta( nodeIndex.x - dependency.x, nodeIndex.y - dependency.y ); |
|
CVertIndex newIndex( nodeIndex.x + delta.x, nodeIndex.y + delta.y ); |
|
|
|
int fullSideLength = (1 << iMaxPower) + 1; |
|
pNode->m_Dependencies[1].m_iVert = WrapVertIndex( CVertIndex( newIndex.x, newIndex.y ), fullSideLength ); |
|
pNode->m_Dependencies[1].m_iNeighbor = iConnection; |
|
} |
|
} |
|
} |
|
|
|
|
|
// --------------------------------------------------------------------------------- // |
|
// CTesselateWinding stuff. |
|
// --------------------------------------------------------------------------------- // |
|
|
|
CTesselateVert::CTesselateVert( CVertIndex const &index, int iNode ) |
|
: m_Index( index ) |
|
{ |
|
m_iNode = iNode; |
|
} |
|
|
|
|
|
CVertInfo::CVertInfo() |
|
{ |
|
int i; |
|
for( i=0; i < sizeof(m_Dependencies)/sizeof(m_Dependencies[0]); i++ ) |
|
{ |
|
m_Dependencies[i].m_iVert = CVertIndex( -1, -1 ); |
|
m_Dependencies[i].m_iNeighbor = -1; |
|
} |
|
|
|
for( i=0; i < sizeof(m_ReverseDependencies)/sizeof(m_ReverseDependencies[0]); i++ ) |
|
{ |
|
m_ReverseDependencies[i].m_iVert = CVertIndex( -1, -1 ); |
|
m_ReverseDependencies[i].m_iNeighbor = -1; |
|
} |
|
|
|
m_iParent.x = m_iParent.y = -1; |
|
m_iNodeLevel = -1; |
|
} |
|
|
|
|
|
CTesselateVert g_TesselateVerts[] = |
|
{ |
|
CTesselateVert( CVertIndex(1,-1), CHILDNODE_LOWER_RIGHT), |
|
CTesselateVert( CVertIndex(0,-1), -1), |
|
CTesselateVert( CVertIndex(-1,-1), CHILDNODE_LOWER_LEFT), |
|
CTesselateVert( CVertIndex(-1, 0), -1), |
|
CTesselateVert( CVertIndex(-1, 1), CHILDNODE_UPPER_LEFT), |
|
CTesselateVert( CVertIndex(0, 1), -1), |
|
CTesselateVert( CVertIndex(1, 1), CHILDNODE_UPPER_RIGHT), |
|
CTesselateVert( CVertIndex(1, 0), -1), |
|
CTesselateVert( CVertIndex(1,-1), CHILDNODE_LOWER_RIGHT) |
|
}; |
|
|
|
CTesselateWinding g_TWinding = |
|
{ |
|
g_TesselateVerts, |
|
sizeof( g_TesselateVerts ) / sizeof( g_TesselateVerts[0] ) |
|
}; |
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------- // |
|
// CPowerInfo stuff. |
|
// --------------------------------------------------------------------------------- // |
|
|
|
// Precalculated info about each particular displacement size. |
|
#define DECLARE_TABLES( size ) \ |
|
static CVertInfo g_VertInfo_##size##x##size[ size*size ]; \ |
|
static CFourVerts g_SideVerts_##size##x##size[ size*size ]; \ |
|
static CFourVerts g_ChildVerts_##size##x##size[ size*size ]; \ |
|
static CFourVerts g_SideVertCorners_##size##x##size[ size*size ]; \ |
|
static CTwoUShorts g_ErrorEdges_##size##x##size[ size*size ]; \ |
|
static CTriInfo g_TriInfos_##size##x##size[ (size-1)*(size-1)*2 ]; \ |
|
static CPowerInfo g_PowerInfo_##size##x##size( \ |
|
g_VertInfo_##size##x##size, \ |
|
g_SideVerts_##size##x##size, \ |
|
g_ChildVerts_##size##x##size, \ |
|
g_SideVertCorners_##size##x##size,\ |
|
g_ErrorEdges_##size##x##size, \ |
|
g_TriInfos_##size##x##size \ |
|
) |
|
|
|
#define POWERINFO_ENTRY( size ) \ |
|
(&g_PowerInfo_##size##x##size) |
|
|
|
DECLARE_TABLES( 5 ); |
|
DECLARE_TABLES( 9 ); |
|
DECLARE_TABLES( 17 ); |
|
|
|
|
|
// Index by m_Power. |
|
CPowerInfo *g_PowerInfos[NUM_POWERINFOS] = |
|
{ |
|
NULL, |
|
NULL, |
|
POWERINFO_ENTRY(5), |
|
POWERINFO_ENTRY(9), |
|
POWERINFO_ENTRY(17) |
|
}; |
|
|
|
|
|
CPowerInfo::CPowerInfo( |
|
CVertInfo *pVertInfo, |
|
CFourVerts *pSideVerts, |
|
CFourVerts *pChildVerts, |
|
CFourVerts *pSideVertCorners, |
|
CTwoUShorts *pErrorEdges, |
|
CTriInfo *pTriInfos ) |
|
{ |
|
m_pVertInfo = pVertInfo; |
|
m_pSideVerts = pSideVerts; |
|
m_pChildVerts = pChildVerts; |
|
m_pSideVertCorners = pSideVertCorners; |
|
m_pErrorEdges = pErrorEdges; |
|
m_pTriInfos = pTriInfos; |
|
} |
|
|
|
static void InitPowerInfoTriInfos_R( |
|
CPowerInfo *pInfo, |
|
CVertIndex const &nodeIndex, |
|
CTriInfo* &pTriInfo, |
|
int iMaxPower, |
|
int iLevel ) |
|
{ |
|
int iNodeIndex = VertIndex( nodeIndex, iMaxPower ); |
|
|
|
if( iLevel+1 < iMaxPower ) |
|
{ |
|
// Recurse into children. |
|
for( int iChild=0; iChild < 4; iChild++ ) |
|
{ |
|
InitPowerInfoTriInfos_R( |
|
pInfo, |
|
pInfo->m_pChildVerts[iNodeIndex].m_Verts[iChild], |
|
pTriInfo, |
|
iMaxPower, |
|
iLevel+1 ); |
|
} |
|
} |
|
else |
|
{ |
|
unsigned short indices[3]; |
|
|
|
int vertInc = 1 << ((iMaxPower - iLevel) - 1); |
|
|
|
// We're at a leaf, generate the tris. |
|
CTesselateWinding *pWinding = &g_TWinding; |
|
|
|
// Starting at the bottom-left, wind clockwise picking up vertices and |
|
// generating triangles. |
|
int iCurTriVert = 0; |
|
for( int iVert=0; iVert < pWinding->m_nVerts; iVert++ ) |
|
{ |
|
CVertIndex sideVert = BuildOffsetVertIndex( nodeIndex, pWinding->m_Verts[iVert].m_Index, vertInc ); |
|
|
|
if( iCurTriVert == 1 ) |
|
{ |
|
// Add this vert and finish the tri. |
|
pTriInfo->m_Indices[0] = indices[0]; |
|
pTriInfo->m_Indices[1] = VertIndex( sideVert, iMaxPower ); |
|
pTriInfo->m_Indices[2] = iNodeIndex; |
|
++pTriInfo; |
|
} |
|
|
|
indices[0] = VertIndex( sideVert, iMaxPower ); |
|
iCurTriVert = 1; |
|
} |
|
} |
|
} |
|
|
|
|
|
static void InitPowerInfo_R( |
|
CPowerInfo *pPowerInfo, |
|
int iMaxPower, |
|
CVertIndex const &nodeIndex, |
|
CVertIndex const &dependency1, |
|
CVertIndex const &dependency2, |
|
CVertIndex const &nodeEdge1, |
|
CVertIndex const &nodeEdge2, |
|
CVertIndex const &iParent, |
|
int iLevel ) |
|
{ |
|
int sideLength = ((1 << iMaxPower) + 1); |
|
int iNodeIndex = VertIndex( nodeIndex, iMaxPower ); |
|
|
|
pPowerInfo->m_pVertInfo[iNodeIndex].m_iParent = iParent; |
|
pPowerInfo->m_pVertInfo[iNodeIndex].m_iNodeLevel = iLevel + 1; |
|
|
|
pPowerInfo->m_pErrorEdges[iNodeIndex].m_Values[0] = (unsigned short)(VertIndex( nodeEdge1, iMaxPower )); |
|
pPowerInfo->m_pErrorEdges[iNodeIndex].m_Values[1] = (unsigned short)(VertIndex( nodeEdge2, iMaxPower )); |
|
|
|
// Add this node's dependencies. |
|
AddDependency( pPowerInfo->m_pVertInfo, sideLength, nodeIndex, dependency1, iMaxPower, false, true ); |
|
AddDependency( pPowerInfo->m_pVertInfo, sideLength, nodeIndex, dependency2, iMaxPower, false, true ); |
|
|
|
// The 4 side vertices depend on this node. |
|
int iPower = iMaxPower - iLevel; |
|
int vertInc = 1 << (iPower - 1); |
|
|
|
for( int iSide=0; iSide < 4; iSide++ ) |
|
{ |
|
// Store the side vert index. |
|
CVertIndex sideVert( nodeIndex.x + g_SideVertMul[iSide][0]*vertInc, nodeIndex.y + g_SideVertMul[iSide][1]*vertInc ); |
|
int iSideVert = VertIndex( sideVert, iMaxPower ); |
|
|
|
pPowerInfo->m_pSideVerts[iNodeIndex].m_Verts[iSide] = sideVert; |
|
|
|
// Store the side vert corners. |
|
CVertIndex sideVertCorner0 = CVertIndex( nodeIndex.x + g_SideVertCorners[iSide].m_Corner1[0]*vertInc, nodeIndex.y + g_SideVertCorners[iSide].m_Corner1[1]*vertInc ); |
|
CVertIndex sideVertCorner1 = CVertIndex( nodeIndex.x + g_SideVertCorners[iSide].m_Corner2[0]*vertInc, nodeIndex.y + g_SideVertCorners[iSide].m_Corner2[1]*vertInc ); |
|
|
|
pPowerInfo->m_pSideVertCorners[iNodeIndex].m_Verts[iSide] = sideVertCorner0; |
|
|
|
// Write the side vert corners into the error-edges list. |
|
pPowerInfo->m_pErrorEdges[iSideVert].m_Values[0] = (unsigned short)VertIndex( sideVertCorner0, iMaxPower ); |
|
pPowerInfo->m_pErrorEdges[iSideVert].m_Values[1] = (unsigned short)VertIndex( sideVertCorner1, iMaxPower ); |
|
|
|
AddDependency( |
|
pPowerInfo->m_pVertInfo, |
|
sideLength, |
|
sideVert, |
|
nodeIndex, |
|
iMaxPower, |
|
true, |
|
true ); |
|
} |
|
|
|
// Recurse into the children. |
|
int nodeInc = vertInc >> 1; |
|
if( nodeInc ) |
|
{ |
|
for( int iChild=0; iChild < 4; iChild++ ) |
|
{ |
|
CVertIndex childVert( nodeIndex.x + g_ChildNodeIndexMul[iChild].x * nodeInc, nodeIndex.y + g_ChildNodeIndexMul[iChild].y * nodeInc ); |
|
|
|
pPowerInfo->m_pChildVerts[iNodeIndex].m_Verts[iChild] = childVert; |
|
|
|
InitPowerInfo_R( pPowerInfo, |
|
iMaxPower, |
|
childVert, |
|
|
|
CVertIndex(nodeIndex.x + g_ChildNodeDependencies[iChild][0].x*vertInc, nodeIndex.y + g_ChildNodeDependencies[iChild][0].y*vertInc), |
|
CVertIndex(nodeIndex.x + g_ChildNodeDependencies[iChild][1].x*vertInc, nodeIndex.y + g_ChildNodeDependencies[iChild][1].y*vertInc), |
|
|
|
nodeIndex, |
|
CVertIndex( nodeIndex.x + g_ChildNodeIndexMul[iChild].x * vertInc, nodeIndex.y + g_ChildNodeIndexMul[iChild].y * vertInc ), |
|
|
|
nodeIndex, |
|
iLevel + 1 ); |
|
} |
|
} |
|
} |
|
|
|
|
|
void InitPowerInfo( CPowerInfo *pInfo, int iMaxPower ) |
|
{ |
|
int sideLength = (1 << iMaxPower) + 1; |
|
|
|
// Precalculate the dependency graph. |
|
CVertIndex nodeDependency1( sideLength-1, sideLength-1 ); |
|
CVertIndex nodeDependency2( 0, 0 ); |
|
|
|
pInfo->m_RootNode = CVertIndex( sideLength/2, sideLength/2 ); |
|
pInfo->m_SideLength = sideLength; |
|
pInfo->m_SideLengthM1 = sideLength - 1; |
|
pInfo->m_MidPoint = sideLength / 2; |
|
pInfo->m_MaxVerts = sideLength * sideLength; |
|
|
|
// Setup the corner indices. |
|
pInfo->m_CornerPointIndices[CORNER_LOWER_LEFT].Init( 0, 0 ); |
|
pInfo->m_CornerPointIndices[CORNER_UPPER_LEFT].Init( 0, sideLength-1 ); |
|
pInfo->m_CornerPointIndices[CORNER_UPPER_RIGHT].Init( sideLength-1, sideLength-1 ); |
|
pInfo->m_CornerPointIndices[CORNER_LOWER_RIGHT].Init( sideLength-1, 0 ); |
|
|
|
InitPowerInfo_R( |
|
pInfo, |
|
iMaxPower, |
|
pInfo->m_RootNode, |
|
|
|
nodeDependency1, // dependencies |
|
nodeDependency2, |
|
|
|
CVertIndex(0,0), // error edge |
|
CVertIndex(sideLength-1, sideLength-1), |
|
|
|
CVertIndex(-1,-1), // parent |
|
0 ); |
|
|
|
pInfo->m_Power = iMaxPower; |
|
|
|
CTriInfo *pTriInfo = pInfo->m_pTriInfos; |
|
InitPowerInfoTriInfos_R( pInfo, pInfo->m_RootNode, pTriInfo, iMaxPower, 0 ); |
|
|
|
for( int iEdge=0; iEdge < 4; iEdge++ ) |
|
{ |
|
// Figure out the start vert and increment. |
|
CVertIndex nextVert; |
|
GetEdgeVertIndex( sideLength, iEdge, 0, pInfo->m_EdgeStartVerts[iEdge] ); |
|
GetEdgeVertIndex( sideLength, iEdge, 1, nextVert ); |
|
pInfo->m_EdgeIncrements[iEdge] = nextVert - pInfo->m_EdgeStartVerts[iEdge]; |
|
|
|
// Now get the neighbor's start vert and increment. |
|
CVertIndex nbStartVert, nbNextVert, nbDelta; |
|
GetEdgeVertIndex( sideLength, (iEdge+2)&3, 0, nbStartVert ); |
|
GetEdgeVertIndex( sideLength, (iEdge+2)&3, 1, nbNextVert ); |
|
nbDelta = nbNextVert - nbStartVert; |
|
|
|
// Rotate it for each orientation. |
|
for( int orient=0; orient < 4; orient++ ) |
|
{ |
|
pInfo->m_NeighborStartVerts[iEdge][orient] = Transform2D( |
|
g_OrientationRotations[orient], |
|
nbStartVert, |
|
CVertIndex( sideLength/2, sideLength/2 ) ); |
|
|
|
pInfo->m_NeighborIncrements[iEdge][orient] = Transform2D( |
|
g_OrientationRotations[orient], |
|
nbDelta, |
|
CVertIndex(0,0) ); |
|
} |
|
} |
|
|
|
|
|
// Init the node index increments. |
|
int curPowerOf4 = 1; |
|
int curTotal = 0; |
|
for( int i=0; i < iMaxPower-1; i++ ) |
|
{ |
|
curTotal += curPowerOf4; |
|
|
|
pInfo->m_NodeIndexIncrements[iMaxPower-i-2] = curTotal; |
|
|
|
curPowerOf4 *= 4; |
|
} |
|
|
|
// Store off the total node count |
|
pInfo->m_NodeCount = curTotal + curPowerOf4; |
|
|
|
pInfo->m_nTriInfos = Square( 1 << iMaxPower ) * 2; |
|
} |
|
|
|
class CPowerInfoInitializer |
|
{ |
|
public: |
|
CPowerInfoInitializer() |
|
{ |
|
Assert( MAX_MAP_DISP_POWER+1 == NUM_POWERINFOS ); |
|
|
|
for( int i=0; i <= MAX_MAP_DISP_POWER; i++ ) |
|
{ |
|
if( g_PowerInfos[i] ) |
|
{ |
|
InitPowerInfo( g_PowerInfos[i], i ); |
|
} |
|
} |
|
} |
|
}; |
|
|
|
static CPowerInfoInitializer g_PowerInfoInitializer; |
|
|
|
|
|
const CPowerInfo* GetPowerInfo( int iPower ) |
|
{ |
|
Assert( iPower >= 0 && iPower < ARRAYSIZE( g_PowerInfos ) ); |
|
Assert( g_PowerInfos[iPower] ); |
|
return g_PowerInfos[iPower]; |
|
} |
|
|
|
|
|
// ------------------------------------------------------------------------------------------------ // |
|
// CPowerInfo member function initialization. |
|
// ------------------------------------------------------------------------------------------------ // |
|
|
|
const CVertIndex& CPowerInfo::GetCornerPointIndex( int iCorner ) const |
|
{ |
|
Assert( iCorner >= 0 && iCorner < 4 ); |
|
return m_CornerPointIndices[iCorner]; |
|
} |
|
|
|
|