//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include #include "MapOverlay.h" #include "MapFace.h" #include "MapSolid.h" #include "MapWorld.h" #include "MainFrm.h" #include "GlobalFunctions.h" #include "MapDoc.h" #include "TextureSystem.h" #include "Material.h" #include "materialsystem/imesh.h" #include "Box3D.h" #include "MapDefs.h" #include "CollisionUtils.h" #include "MapSideList.h" #include "MapDisp.h" #include "ToolManager.h" #include "objectproperties.h" #include "ChunkFile.h" #include "mapview.h" #include "options.h" // memdbgon must be the last include file in a .cpp file!!! #include IMPLEMENT_MAPCLASS( CMapOverlay ) #define OVERLAY_INITSIZE 25.0f // x2 #define OVERLAY_BASIS_U 0 #define OVERLAY_BASIS_V 1 #define OVERLAY_BASIS_NORMAL 2 #define OVERLAY_HANDLES_COUNT 4 #define OVERLAY_WORLDSPACE_EPSILON 0.03125f #define OVERLAY_DISPSPACE_EPSILON 0.000001f #define OVERLAY_BARYCENTRIC_EPSILON 0.001f #define OVERLAY_BLENDTYPE_VERT 0 #define OVERLAY_BLENDTYPE_EDGE 1 #define OVERLAY_BLENDTYPE_BARY 2 #define OVERLAY_ANGLE0 1 #define OVERLAY_ANGLE45 2 #define OVERLAY_ANGLE90 3 #define OVERLAY_ANGLE135 4 #define OVERLAY_INVALID_VALUE -99999.9f //============================================================================= // // Basis Functions // //----------------------------------------------------------------------------- // Purpose: Initialize the basis data. //----------------------------------------------------------------------------- void CMapOverlay::Basis_Clear( void ) { m_Basis.m_pFace = NULL; m_Basis.m_vecOrigin.Init(); for( int iAxis = 0; iAxis < 3; iAxis++ ) { m_Basis.m_vecAxes[iAxis].Init( OVERLAY_INVALID_VALUE, OVERLAY_INVALID_VALUE, OVERLAY_INVALID_VALUE ); m_Basis.m_nAxesFlip[iAxis] = 0; } } //----------------------------------------------------------------------------- // Purpose: Build the overlay basis given an entity and base face (CMapFace). //----------------------------------------------------------------------------- void CMapOverlay::Basis_Init( CMapFace *pFace ) { // Valid face? Assert( pFace != NULL ); if( !pFace ) return; // Set the face the basis are derived from. Basis_SetFace( pFace ); // Set the basis origin. Basis_UpdateOrigin(); // Setup the basis axes. Basis_BuildAxes(); // Initialize the texture coordinates - based on basis. Material_TexCoordInit(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Basis_UpdateOrigin( void ) { CMapEntity *pEntity = static_cast( GetParent() ); if ( pEntity ) { Vector vecEntityOrigin; pEntity->GetOrigin( vecEntityOrigin ); Vector vecPoint( 0.0f, 0.0f, 0.0f ); if ( !EntityOnSurfFromListToBaseFacePlane( vecEntityOrigin, vecPoint ) ) { vecPoint = vecEntityOrigin; } m_Basis.m_vecOrigin = vecPoint; } // Update the property box. Basis_UpdateParentKey(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Basis_BuildAxes( void ) { // Valid face? if( !m_Basis.m_pFace ) return; // Build the basis axes. Vector vecFaceNormal; m_Basis.m_pFace->GetFaceNormal( vecFaceNormal ); VectorNormalize( vecFaceNormal ); VectorCopy( vecFaceNormal, m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL] ); Basis_SetInitialUAxis( vecFaceNormal ); m_Basis.m_vecAxes[OVERLAY_BASIS_V] = m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL].Cross( m_Basis.m_vecAxes[OVERLAY_BASIS_U] ); VectorNormalize( m_Basis.m_vecAxes[OVERLAY_BASIS_V] ); m_Basis.m_vecAxes[OVERLAY_BASIS_U] = m_Basis.m_vecAxes[OVERLAY_BASIS_V].Cross( m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL] ); VectorNormalize( m_Basis.m_vecAxes[OVERLAY_BASIS_U] ); // Flip uvn axes? for ( int iAxis = 0; iAxis < 3; ++iAxis ) { for ( int iComp = 0; iComp < 3; ++iComp ) { if ( Basis_IsFlipped( iAxis, iComp ) ) { m_Basis.m_vecAxes[iAxis][iComp] = -m_Basis.m_vecAxes[iAxis][iComp]; } } } Basis_UpdateParentKey(); } //----------------------------------------------------------------------------- // Purpose: A basis building helper function that finds the best guess u-axis // given a base face (CMapFace) normal. // Input: vecNormal - the base face normal //----------------------------------------------------------------------------- void CMapOverlay::Basis_SetInitialUAxis( Vector const &vecNormal ) { // Find the major vector component. int nMajorAxis = 0; float flAxisValue = vecNormal[0]; if ( FloatMakePositive( vecNormal[1] ) > FloatMakePositive( flAxisValue ) ) { nMajorAxis = 1; flAxisValue = vecNormal[1]; } if ( FloatMakePositive( vecNormal[2] ) > FloatMakePositive( flAxisValue ) ) { nMajorAxis = 2; } if ( ( nMajorAxis == 1 ) || ( nMajorAxis == 2 ) ) { m_Basis.m_vecAxes[OVERLAY_BASIS_U].Init( 1.0f, 0.0f, 0.0f ); } else { m_Basis.m_vecAxes[OVERLAY_BASIS_U].Init( 0.0f, 1.0f, 0.0f ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CMapOverlay::Basis_IsValid( void ) { for ( int iBasis = 0; iBasis < 3; ++iBasis ) { for ( int iAxis = 0; iAxis < 3; ++iAxis ) { if ( m_Basis.m_vecAxes[iBasis][iAxis] == OVERLAY_INVALID_VALUE ) return false; } } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Basis_SetFace( CMapFace *pFace ) { // Verify face. if ( !pFace ) return; m_Basis.m_pFace = pFace; } //----------------------------------------------------------------------------- // Purpose: Copy the basis data from the source into the destination. // Input: pSrc - the basis source data // pDst (Output) - destination for the basis data //----------------------------------------------------------------------------- void CMapOverlay::Basis_Copy( Basis_t *pSrc, Basis_t *pDst ) { pDst->m_pFace = pSrc->m_pFace; pDst->m_vecOrigin = pSrc->m_vecOrigin; for ( int iAxis = 0; iAxis < 3; iAxis++ ) { pDst->m_vecAxes[iAxis] = pSrc->m_vecAxes[iAxis]; pDst->m_nAxesFlip[iAxis] = pSrc->m_nAxesFlip[iAxis]; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Basis_UpdateParentKey( void ) { char szValue[80]; CMapEntity *pEntity = ( CMapEntity* )GetParent(); if ( pEntity ) { sprintf( szValue, "%g %g %g", m_Basis.m_vecOrigin.x, m_Basis.m_vecOrigin.y, m_Basis.m_vecOrigin.z ); pEntity->NotifyChildKeyChanged( this, "BasisOrigin", szValue ); sprintf( szValue, "%g %g %g", m_Basis.m_vecAxes[OVERLAY_BASIS_U].x, m_Basis.m_vecAxes[OVERLAY_BASIS_U].y, m_Basis.m_vecAxes[OVERLAY_BASIS_U].z ); pEntity->NotifyChildKeyChanged( this, "BasisU", szValue ); sprintf( szValue, "%g %g %g", m_Basis.m_vecAxes[OVERLAY_BASIS_V].x, m_Basis.m_vecAxes[OVERLAY_BASIS_V].y, m_Basis.m_vecAxes[OVERLAY_BASIS_V].z ); pEntity->NotifyChildKeyChanged( this, "BasisV", szValue ); sprintf( szValue, "%g %g %g", m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL].x, m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL].y, m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL].z ); pEntity->NotifyChildKeyChanged( this, "BasisNormal", szValue ); } } //============================================================================= // // Basis - Legacy support! // //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Basis_BuildFromSideList( void ) { // Initialization (don't have or couldn't find the basis face) if ( m_Faces.Count() > 0 ) { Basis_Init( m_Faces.Element( 0 ) ); } else { m_Basis.m_pFace = NULL; } } //----------------------------------------------------------------------------- // Purpose: // Input: iAxis - 0, 1, 2 (u, v, n) // iComponet - 0, 1, 2 (x, y, z) //----------------------------------------------------------------------------- void CMapOverlay::Basis_ToggleAxesFlip( int iAxis, int iComponent ) { if ( iAxis < 0 || iAxis > 2 || iComponent < 0 || iComponent > 2 ) return; int nValue = ( 1 << iComponent ); m_Basis.m_nAxesFlip[iAxis] ^= nValue; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CMapOverlay::Basis_IsFlipped( int iAxis, int iComponent ) { if ( iAxis < 0 || iAxis > 2 || iComponent < 0 || iComponent > 2 ) return false; int nValue = ( 1 << iComponent ); return ( ( m_Basis.m_nAxesFlip[iAxis] & nValue ) != 0 ); } //============================================================================= // // Handles Functions // //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Handles_Clear( void ) { m_Handles.m_iHit = -1; for ( int iHandle = 0; iHandle < OVERLAY_HANDLES_COUNT; iHandle++ ) { m_Handles.m_vec3D[iHandle].Init(); } m_Handles.m_vecBasisCoords[0].Init( -OVERLAY_INITSIZE, -OVERLAY_INITSIZE ); m_Handles.m_vecBasisCoords[1].Init( -OVERLAY_INITSIZE, OVERLAY_INITSIZE ); m_Handles.m_vecBasisCoords[2].Init( OVERLAY_INITSIZE, OVERLAY_INITSIZE ); m_Handles.m_vecBasisCoords[3].Init( OVERLAY_INITSIZE, -OVERLAY_INITSIZE ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Handles_Init( CMapFace *pFace ) { IEditorTexture *pTexture = g_Textures.FindActiveTexture( GetDefaultTextureName() ); int nWidth = pTexture->GetImageWidth(); int nHeight = pTexture->GetImageHeight(); // Half-height (width) and 1/4 scale int nWidthHalf = nWidth / 8; int nHeightHalf = nHeight / 8; m_Handles.m_vecBasisCoords[0].Init( -nWidthHalf, -nHeightHalf ); m_Handles.m_vecBasisCoords[1].Init( -nWidthHalf, nHeightHalf ); m_Handles.m_vecBasisCoords[2].Init( nWidthHalf, nHeightHalf ); m_Handles.m_vecBasisCoords[3].Init( nWidthHalf, -nHeightHalf ); Handles_Build3D(); Handles_UpdateParentKey(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Handles_Build3D( void ) { // Verify that we have a valid basis to build the handles from. if ( !Basis_IsValid() ) return; for ( int iHandle = 0; iHandle < OVERLAY_HANDLES_COUNT; iHandle++ ) { Vector vecHandle; OverlayUVToOverlayPlane( m_Handles.m_vecBasisCoords[iHandle], vecHandle ); OverlayPlaneToSurfFromList( vecHandle, m_Handles.m_vec3D[iHandle] ); } Handles_FixOrder(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Handles_Render3D( CRender3D *pRender ) { // Set the render mode to "flat." pRender->PushRenderMode( RENDER_MODE_FLAT ); // Set the color, should be based on selection. unsigned char ucColor[4]; ucColor[0] = ucColor[1] = ucColor[2] = ucColor[3] = 255; unsigned char ucSelectColor[4]; ucSelectColor[0] = ucSelectColor[3] = 255; ucSelectColor[1] = ucSelectColor[2] = 0; pRender->SetHandleStyle( HANDLE_RADIUS, CRender::HANDLE_SQUARE ); for ( int iHandle = 0; iHandle < OVERLAY_HANDLES_COUNT; iHandle++ ) { pRender->BeginRenderHitTarget( this, iHandle ); if ( m_Handles.m_iHit == iHandle ) { pRender->SetHandleColor( ucSelectColor[0], ucSelectColor[1], ucSelectColor[2] ); } else { pRender->SetHandleColor( ucColor[0], ucColor[1], ucColor[2] ); } pRender->DrawHandle( m_Handles.m_vec3D[iHandle] ); pRender->EndRenderHitTarget(); } pRender->PopRenderMode(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Handles_SurfToOverlayPlane( CMapFace *pFace, Vector const &vecSurf, Vector &vecPoint ) { Vector vecWorld; if ( pFace->HasDisp() ) { EditDispHandle_t handle = pFace->GetDisp(); CMapDisp *pDisp = EditDispMgr()->GetDisp( handle ); pDisp->SurfToBaseFacePlane( vecSurf, vecWorld ); } else { vecWorld = vecSurf; } WorldToOverlayPlane( vecWorld, vecPoint ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Handles_Copy( Handles_t *pSrc, Handles_t *pDst ) { pDst->m_iHit = pSrc->m_iHit; for ( int iHandle = 0; iHandle < OVERLAY_HANDLES_COUNT; ++iHandle ) { pDst->m_vecBasisCoords[iHandle] = pSrc->m_vecBasisCoords[iHandle]; pDst->m_vec3D[iHandle] = pSrc->m_vec3D[iHandle]; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Handles_UpdateParentKey( void ) { char szValue[80]; CMapEntity *pEntity = ( CMapEntity* )GetParent(); if ( pEntity ) { sprintf( szValue, "%g %g %g", m_Handles.m_vecBasisCoords[0].x, m_Handles.m_vecBasisCoords[0].y, ( float )m_Basis.m_nAxesFlip[0] ); pEntity->NotifyChildKeyChanged( this, "uv0", szValue ); sprintf( szValue, "%g %g %g", m_Handles.m_vecBasisCoords[1].x, m_Handles.m_vecBasisCoords[1].y, ( float )m_Basis.m_nAxesFlip[1] ); pEntity->NotifyChildKeyChanged( this, "uv1", szValue ); sprintf( szValue, "%g %g %g", m_Handles.m_vecBasisCoords[2].x, m_Handles.m_vecBasisCoords[2].y, ( float )m_Basis.m_nAxesFlip[2] ); pEntity->NotifyChildKeyChanged( this, "uv2", szValue ); sprintf( szValue, "%g %g %g", m_Handles.m_vecBasisCoords[3].x, m_Handles.m_vecBasisCoords[3].y, 0.0f ); pEntity->NotifyChildKeyChanged( this, "uv3", szValue ); } } //============================================================================= // // ClipFace Functions // //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CMapOverlay::ClipFace_t *CMapOverlay::ClipFace_Create( int nSize ) { ClipFace_t *pClipFace = new ClipFace_t; if ( pClipFace ) { pClipFace->m_nPointCount = nSize; if ( nSize > 0 ) { pClipFace->m_aPoints.SetSize( nSize ); pClipFace->m_aDispPointUVs.SetSize( nSize ); for ( int iCoord = 0; iCoord < NUM_CLIPFACE_TEXCOORDS; iCoord++ ) { pClipFace->m_aTexCoords[iCoord].SetSize( nSize ); } pClipFace->m_aBlends.SetSize( nSize ); for ( int iPoint = 0; iPoint < nSize; iPoint++ ) { pClipFace->m_aPoints[iPoint].Init(); pClipFace->m_aDispPointUVs[iPoint].Init(); pClipFace->m_aBlends[iPoint].Init(); for ( int iCoord = 0; iCoord < NUM_CLIPFACE_TEXCOORDS; iCoord++ ) { pClipFace->m_aTexCoords[iCoord][iPoint].Init(); } } } } return pClipFace; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::ClipFace_Destroy( ClipFace_t **ppClipFace ) { if( *ppClipFace ) { delete *ppClipFace; *ppClipFace = NULL; } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- CMapOverlay::ClipFace_t *CMapOverlay::ClipFace_Copy( ClipFace_t *pSrc ) { ClipFace_t *pDst = ClipFace_Create( pSrc->m_nPointCount ); if ( pDst ) { for ( int iPoint = 0; iPoint < pSrc->m_nPointCount; iPoint++ ) { pDst->m_aPoints[iPoint] = pSrc->m_aPoints[iPoint]; pDst->m_aDispPointUVs[iPoint] = pSrc->m_aDispPointUVs[iPoint]; for ( int iTexCoord=0; iTexCoord < NUM_CLIPFACE_TEXCOORDS; iTexCoord++ ) { pDst->m_aTexCoords[iTexCoord][iPoint] = pSrc->m_aTexCoords[iTexCoord][iPoint]; } pDst->m_aBlends[iPoint].m_nType = pSrc->m_aBlends[iPoint].m_nType; for ( int iBlend = 0; iBlend < 3; iBlend++ ) { pDst->m_aBlends[iPoint].m_iPoints[iBlend] = pSrc->m_aBlends[iPoint].m_iPoints[iBlend]; pDst->m_aBlends[iPoint].m_flBlends[iBlend] = pSrc->m_aBlends[iPoint].m_flBlends[iBlend]; } } } return pDst; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::ClipFace_GetBounds( ClipFace_t *pClipFace, Vector &vecMin, Vector &vecMax ) { if ( pClipFace ) { vecMin = vecMax = pClipFace->m_aPoints.Element( 0 ); for ( int iPoints = 1; iPoints < pClipFace->m_nPointCount; iPoints++ ) { Vector vecPoint = pClipFace->m_aPoints.Element( iPoints ); // Min if ( vecMin.x > vecPoint.x ) { vecMin.x = vecPoint.x; } if ( vecMin.y > vecPoint.y ) { vecMin.y = vecPoint.y; } if ( vecMin.z > vecPoint.z ) { vecMin.z = vecPoint.z; } // Max if ( vecMax.x < vecPoint.x ) { vecMax.x = vecPoint.x; } if ( vecMax.y < vecPoint.y ) { vecMax.y = vecPoint.y; } if ( vecMax.z < vecPoint.z ) { vecMax.z = vecPoint.z; } } } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::ClipFace_Clip( ClipFace_t *pClipFace, cplane_t *pClipPlane, float flEpsilon, ClipFace_t **ppFront, ClipFace_t **ppBack ) { if ( !pClipFace ) return; float flDists[128]; int nSides[128]; int nSideCounts[3]; // Initialize *ppFront = *ppBack = NULL; // Determine "sidedness" of all the polygon points. nSideCounts[0] = nSideCounts[1] = nSideCounts[2] = 0; int iPoint; for ( iPoint = 0; iPoint < pClipFace->m_nPointCount; iPoint++ ) { flDists[iPoint] = pClipPlane->normal.Dot( pClipFace->m_aPoints.Element( iPoint ) ) - pClipPlane->dist; if ( flDists[iPoint] > flEpsilon ) { nSides[iPoint] = SIDE_FRONT; } else if ( flDists[iPoint] < -flEpsilon ) { nSides[iPoint] = SIDE_BACK; } else { nSides[iPoint] = SIDE_ON; } nSideCounts[nSides[iPoint]]++; } // Wrap around (close the polygon). nSides[iPoint] = nSides[0]; flDists[iPoint] = flDists[0]; // All points in back - no split (copy face to back). if( !nSideCounts[SIDE_FRONT] ) { *ppBack = ClipFace_Copy( pClipFace ); return; } // All points in front - no split (copy face to front). if( !nSideCounts[SIDE_BACK] ) { *ppFront = ClipFace_Copy( pClipFace ); return; } // Build new front and back faces. Leave room for two extra points on each side because any // point might be on the plane, which would put it into both the front and back sides, and then // we need to allow for an additional vertex created by clipping. ClipFace_t *pFront = ClipFace_Create( pClipFace->m_nPointCount + 2 ); ClipFace_t *pBack = ClipFace_Create( pClipFace->m_nPointCount + 2 ); if ( !pFront || !pBack ) { ClipFace_Destroy( &pFront ); ClipFace_Destroy( &pBack ); return; } // Reset the counts as they are used to build the surface. pFront->m_nPointCount = 0; pBack->m_nPointCount = 0; // For every point on the face being clipped, determine which side of the clipping plane it is on // and add it to a either a front list or a back list. Points that are on the plane are added to // both lists. for ( iPoint = 0; iPoint < pClipFace->m_nPointCount; iPoint++ ) { // "On" clip plane. if ( nSides[iPoint] == SIDE_ON ) { pFront->m_aPoints[pFront->m_nPointCount] = pClipFace->m_aPoints[iPoint]; for ( int iTexCoord=0; iTexCoord < NUM_CLIPFACE_TEXCOORDS; iTexCoord++ ) pFront->m_aTexCoords[iTexCoord][pFront->m_nPointCount] = pClipFace->m_aTexCoords[iTexCoord][iPoint]; pFront->m_nPointCount++; pBack->m_aPoints[pBack->m_nPointCount] = pClipFace->m_aPoints[iPoint]; for ( int iTexCoord=0; iTexCoord < NUM_CLIPFACE_TEXCOORDS; iTexCoord++ ) pBack->m_aTexCoords[iTexCoord][pBack->m_nPointCount] = pClipFace->m_aTexCoords[iTexCoord][iPoint]; pBack->m_nPointCount++; continue; } // "In back" of clip plane. if ( nSides[iPoint] == SIDE_BACK ) { pBack->m_aPoints[pBack->m_nPointCount] = pClipFace->m_aPoints[iPoint]; for ( int iTexCoord=0; iTexCoord < NUM_CLIPFACE_TEXCOORDS; iTexCoord++ ) pBack->m_aTexCoords[iTexCoord][pBack->m_nPointCount] = pClipFace->m_aTexCoords[iTexCoord][iPoint]; pBack->m_nPointCount++; } // "In front" of clip plane. if ( nSides[iPoint] == SIDE_FRONT ) { pFront->m_aPoints[pFront->m_nPointCount] = pClipFace->m_aPoints[iPoint]; for ( int iTexCoord=0; iTexCoord < NUM_CLIPFACE_TEXCOORDS; iTexCoord++ ) pFront->m_aTexCoords[iTexCoord][pFront->m_nPointCount] = pClipFace->m_aTexCoords[iTexCoord][iPoint]; pFront->m_nPointCount++; } if ( nSides[iPoint+1] == SIDE_ON || nSides[iPoint+1] == nSides[iPoint] ) continue; // Split! float fraction = flDists[iPoint] / ( flDists[iPoint] - flDists[iPoint+1] ); Vector vecPoint = pClipFace->m_aPoints[iPoint] + ( pClipFace->m_aPoints[(iPoint+1)%pClipFace->m_nPointCount] - pClipFace->m_aPoints[iPoint] ) * fraction; for ( int iTexCoord=0; iTexCoord < NUM_CLIPFACE_TEXCOORDS; iTexCoord++ ) { Vector2D vecTexCoord = pClipFace->m_aTexCoords[iTexCoord][iPoint] + ( pClipFace->m_aTexCoords[iTexCoord][(iPoint+1)%pClipFace->m_nPointCount] - pClipFace->m_aTexCoords[iTexCoord][iPoint] ) * fraction; pFront->m_aTexCoords[iTexCoord][pFront->m_nPointCount] = vecTexCoord; pBack->m_aTexCoords[iTexCoord][pBack->m_nPointCount] = vecTexCoord; } pFront->m_aPoints[pFront->m_nPointCount] = vecPoint; pFront->m_nPointCount++; pBack->m_aPoints[pBack->m_nPointCount] = vecPoint; pBack->m_nPointCount++; } *ppFront = pFront; *ppBack = pBack; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::ClipFace_ClipBarycentric( ClipFace_t *pClipFace, cplane_t *pClipPlane, float flEpsilon, int iClip, CMapDisp *pDisp, ClipFace_t **ppFront, ClipFace_t **ppBack ) { if ( !pClipFace ) return; float flDists[128]; int nSides[128]; int nSideCounts[3]; // Determine "sidedness" of all the polygon points. nSideCounts[0] = nSideCounts[1] = nSideCounts[2] = 0; int iPoint; for ( iPoint = 0; iPoint < pClipFace->m_nPointCount; iPoint++ ) { flDists[iPoint] = pClipPlane->normal.Dot( pClipFace->m_aDispPointUVs.Element( iPoint ) ) - pClipPlane->dist; if ( flDists[iPoint] > flEpsilon ) { nSides[iPoint] = SIDE_FRONT; } else if ( flDists[iPoint] < -flEpsilon ) { nSides[iPoint] = SIDE_BACK; } else { nSides[iPoint] = SIDE_ON; } nSideCounts[nSides[iPoint]]++; } // Wrap around (close the polygon). nSides[iPoint] = nSides[0]; flDists[iPoint] = flDists[0]; // All points in back - no split (copy face to back). if( !nSideCounts[SIDE_FRONT] ) { *ppBack = ClipFace_Copy( pClipFace ); return; } // All points in front - no split (copy face to front). if( !nSideCounts[SIDE_BACK] ) { *ppFront = ClipFace_Copy( pClipFace ); return; } // Build new front and back faces. // NOTE: We are allowing to go over by 2 and then destroy the surface later. The old system // allowed for some bad data and we need to be able to load the map and destroy the surface! int nMaxPointCount = pClipFace->m_nPointCount + 1; ClipFace_t *pFront = ClipFace_Create( nMaxPointCount + 2 ); ClipFace_t *pBack = ClipFace_Create( nMaxPointCount + 2 ); if ( !pFront || !pBack ) { ClipFace_Destroy( &pFront ); ClipFace_Destroy( &pBack ); return; } // Reset the counts as they are used to build the surface. pFront->m_nPointCount = 0; pBack->m_nPointCount = 0; for ( iPoint = 0; iPoint < pClipFace->m_nPointCount; iPoint++ ) { // "On" clip plane. if ( nSides[iPoint] == SIDE_ON ) { pFront->m_aPoints[pFront->m_nPointCount] = pClipFace->m_aPoints[iPoint]; pFront->m_aDispPointUVs[pFront->m_nPointCount] = pClipFace->m_aDispPointUVs[iPoint]; for ( int iTexCoord=0; iTexCoord < NUM_CLIPFACE_TEXCOORDS; iTexCoord++ ) pFront->m_aTexCoords[iTexCoord][pFront->m_nPointCount] = pClipFace->m_aTexCoords[iTexCoord][iPoint]; ClipFace_CopyBlendFrom( pFront, &pClipFace->m_aBlends[iPoint] ); pFront->m_nPointCount++; pBack->m_aPoints[pBack->m_nPointCount] = pClipFace->m_aPoints[iPoint]; pBack->m_aDispPointUVs[pBack->m_nPointCount] = pClipFace->m_aDispPointUVs[iPoint]; for ( int iTexCoord=0; iTexCoord < NUM_CLIPFACE_TEXCOORDS; iTexCoord++ ) pBack->m_aTexCoords[iTexCoord][pBack->m_nPointCount] = pClipFace->m_aTexCoords[iTexCoord][iPoint]; ClipFace_CopyBlendFrom( pBack, &pClipFace->m_aBlends[iPoint] ); pBack->m_nPointCount++; continue; } // "In back" of clip plane. if ( nSides[iPoint] == SIDE_BACK ) { pBack->m_aPoints[pBack->m_nPointCount] = pClipFace->m_aPoints[iPoint]; pBack->m_aDispPointUVs[pBack->m_nPointCount] = pClipFace->m_aDispPointUVs[iPoint]; for ( int iTexCoord=0; iTexCoord < NUM_CLIPFACE_TEXCOORDS; iTexCoord++ ) pBack->m_aTexCoords[iTexCoord][pBack->m_nPointCount] = pClipFace->m_aTexCoords[iTexCoord][iPoint]; ClipFace_CopyBlendFrom( pBack, &pClipFace->m_aBlends[iPoint] ); pBack->m_nPointCount++; } // "In front" of clip plane. if ( nSides[iPoint] == SIDE_FRONT ) { pFront->m_aPoints[pFront->m_nPointCount] = pClipFace->m_aPoints[iPoint]; pFront->m_aDispPointUVs[pFront->m_nPointCount] = pClipFace->m_aDispPointUVs[iPoint]; for ( int iTexCoord=0; iTexCoord < NUM_CLIPFACE_TEXCOORDS; iTexCoord++ ) pFront->m_aTexCoords[iTexCoord][pFront->m_nPointCount] = pClipFace->m_aTexCoords[iTexCoord][iPoint]; ClipFace_CopyBlendFrom( pFront, &pClipFace->m_aBlends[iPoint] ); pFront->m_nPointCount++; } if ( nSides[iPoint+1] == SIDE_ON || nSides[iPoint+1] == nSides[iPoint] ) continue; // Split! float fraction = flDists[iPoint] / ( flDists[iPoint] - flDists[iPoint+1] ); Vector vecPoint = pClipFace->m_aPoints[iPoint] + ( pClipFace->m_aPoints[(iPoint+1)%pClipFace->m_nPointCount] - pClipFace->m_aPoints[iPoint] ) * fraction; Vector vecDispPointUV = pClipFace->m_aDispPointUVs[iPoint] + ( pClipFace->m_aDispPointUVs[(iPoint+1)%pClipFace->m_nPointCount] - pClipFace->m_aDispPointUVs[iPoint] ) * fraction; Vector2D vecUV, vecTexCoord; PointInQuadToBarycentric( m_pOverlayFace->m_aPoints[0], m_pOverlayFace->m_aPoints[3], m_pOverlayFace->m_aPoints[2], m_pOverlayFace->m_aPoints[1], vecPoint, vecUV ); vecUV.x = clamp( vecUV.x, 0.0f, 1.0f ); vecUV.y = clamp( vecUV.y, 0.0f, 1.0f ); for ( int iTexCoord=0; iTexCoord < NUM_CLIPFACE_TEXCOORDS; iTexCoord++ ) { TexCoordInQuadFromBarycentric( m_pOverlayFace->m_aTexCoords[iTexCoord][0], m_pOverlayFace->m_aTexCoords[iTexCoord][3], m_pOverlayFace->m_aTexCoords[iTexCoord][2], m_pOverlayFace->m_aTexCoords[iTexCoord][1], vecUV, vecTexCoord ); pFront->m_aTexCoords[iTexCoord][pFront->m_nPointCount] = vecTexCoord; pBack->m_aTexCoords[iTexCoord][pBack->m_nPointCount] = vecTexCoord; } pFront->m_aPoints[pFront->m_nPointCount] = vecPoint; pFront->m_aDispPointUVs[pFront->m_nPointCount] = vecDispPointUV; ClipFace_BuildBlend( pFront, pDisp, pClipPlane, iClip, vecDispPointUV, vecPoint ); pFront->m_nPointCount++; pBack->m_aPoints[pBack->m_nPointCount] = vecPoint; pBack->m_aDispPointUVs[pBack->m_nPointCount] = vecDispPointUV; ClipFace_BuildBlend( pBack, pDisp, pClipPlane, iClip, vecDispPointUV, vecPoint ); pBack->m_nPointCount++; } // Check for a bad surface. if ( ( pFront->m_nPointCount > nMaxPointCount ) || ( pBack->m_nPointCount > nMaxPointCount ) ) return; *ppFront = pFront; *ppBack = pBack; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::ClipFace_PreClipDisp( ClipFace_t *pClipFace, CMapDisp *pDisp ) { // Valid clip face and/or displacement surface. if ( !pClipFace || !pDisp ) return; // Transform all of the overlay points into disp uv space. for ( int iPoint = 0; iPoint < pClipFace->m_nPointCount; iPoint++ ) { Vector2D vecTmp; pDisp->BaseFacePlaneToDispUV( pClipFace->m_aPoints[iPoint], vecTmp ); pClipFace->m_aDispPointUVs[iPoint].x = clamp(vecTmp.x, 0.0f, 1.0f); pClipFace->m_aDispPointUVs[iPoint].y = clamp(vecTmp.y, 0.0f, 1.0f); pClipFace->m_aDispPointUVs[iPoint].z = 0.0f; } // Set initial point barycentric blend types. for ( int iPoint = 0; iPoint < pClipFace->m_nPointCount; ++iPoint ) { Vector2D vecDispUV; vecDispUV.x = pClipFace->m_aDispPointUVs[iPoint].x; vecDispUV.y = pClipFace->m_aDispPointUVs[iPoint].y; int iTris[3]; Vector2D vecVertsUV[3]; GetTriVerts( pDisp, vecDispUV, iTris, vecVertsUV ); float flCoefs[3]; if ( ClipFace_CalcBarycentricCooefs( pDisp, vecVertsUV, vecDispUV, flCoefs ) ) { ClipFace_ResolveBarycentricClip( pDisp, pClipFace, iPoint, vecDispUV, flCoefs, iTris, vecVertsUV ); } } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::ClipFace_PostClipDisp( void ) { } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CMapOverlay::ClipFace_CalcBarycentricCooefs( CMapDisp *pDisp, Vector2D *pVertsUV, const Vector2D &vecPointUV, float *pCoefs ) { // Area in disp UV space is always the same. float flTotalArea = 0.5f; float flOOTotalArea = 1.0f / flTotalArea; int nInterval = pDisp->GetWidth(); Vector2D vecScaledPointUV = vecPointUV * ( nInterval - 1.000001f ); Vector2D vecSegment0, vecSegment1; // Get the area for cooeficient 0 (pt, v1, v2). vecSegment0 = pVertsUV[1] - vecScaledPointUV; vecSegment1 = pVertsUV[2] - vecScaledPointUV; // Cross float flSubArea = ( ( vecSegment1.x * vecSegment0.y ) - ( vecSegment0.x * vecSegment1.y ) ) * 0.5f; pCoefs[0] = flSubArea * flOOTotalArea; // Get the area for cooeficient 1 (v0, pt, v2). vecSegment0 = vecScaledPointUV - pVertsUV[0]; vecSegment1 = pVertsUV[2] - pVertsUV[0]; // Cross flSubArea = ( ( vecSegment1.x * vecSegment0.y ) - ( vecSegment0.x * vecSegment1.y ) ) * 0.5f; pCoefs[1] = flSubArea * flOOTotalArea; // Get the area for cooeficient 2 (v0, v1, pt). vecSegment0 = pVertsUV[1] - pVertsUV[0]; vecSegment1 = vecScaledPointUV - pVertsUV[0]; // Cross flSubArea = ( ( vecSegment1.x * vecSegment0.y ) - ( vecSegment0.x * vecSegment1.y ) ) * 0.5f; pCoefs[2] = flSubArea * flOOTotalArea; float flCoefTotal = pCoefs[0] + pCoefs[1] + pCoefs[2]; if ( FloatMakePositive( 1.0f - flCoefTotal ) < 0.00001f ) return true; return false; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::ClipFace_ResolveBarycentricClip( CMapDisp *pDisp, ClipFace_t *pClipFace, int iClipFacePoint, const Vector2D &vecPointUV, float *pCoefs, int *pTris, Vector2D *pVertsUV ) { int nInterval = pDisp->GetWidth(); Vector2D vecScaledPointUV = vecPointUV * ( nInterval - 1.000001f ); // Find the number of coefficients "equal" to zero. int nZeroCount = 0; bool bZeroPoint[3]; for ( int iVert = 0; iVert < 3; ++iVert ) { bZeroPoint[iVert] = false; if ( fabs( pCoefs[iVert] ) < OVERLAY_BARYCENTRIC_EPSILON ) { nZeroCount++; bZeroPoint[iVert] = true; } } // Check for points - set to a point. if ( nZeroCount == 2 ) { for ( int iVert = 0; iVert < 3; ++iVert ) { if ( !bZeroPoint[iVert] ) { pClipFace->m_aBlends[iClipFacePoint].m_nType = OVERLAY_BLENDTYPE_VERT; pClipFace->m_aBlends[iClipFacePoint].m_iPoints[0] = pTris[iVert]; return; } } } // Check for edges - setup edge blend. if ( nZeroCount == 1 ) { for ( int iVert = 0; iVert < 3; ++iVert ) { if ( bZeroPoint[iVert] ) { pClipFace->m_aBlends[iClipFacePoint].m_nType = OVERLAY_BLENDTYPE_EDGE; pClipFace->m_aBlends[iClipFacePoint].m_iPoints[0] = pTris[(iVert+1)%3]; pClipFace->m_aBlends[iClipFacePoint].m_iPoints[1] = pTris[(iVert+2)%3]; Vector2D vecLength1, vecLength2; vecLength1 = vecScaledPointUV - pVertsUV[(iVert+1)%3]; vecLength2 = pVertsUV[(iVert+2)%3] - pVertsUV[(iVert+1)%3]; float flBlend = vecLength1.Length() / vecLength2.Length(); pClipFace->m_aBlends[iClipFacePoint].m_flBlends[0] = flBlend; return; } } } // Lies inside triangles - setup full barycentric blend. pClipFace->m_aBlends[iClipFacePoint].m_nType = OVERLAY_BLENDTYPE_BARY; pClipFace->m_aBlends[iClipFacePoint].m_iPoints[0] = pTris[0]; pClipFace->m_aBlends[iClipFacePoint].m_iPoints[1] = pTris[1]; pClipFace->m_aBlends[iClipFacePoint].m_iPoints[2] = pTris[2]; pClipFace->m_aBlends[iClipFacePoint].m_flBlends[0] = pCoefs[0]; pClipFace->m_aBlends[iClipFacePoint].m_flBlends[1] = pCoefs[1]; pClipFace->m_aBlends[iClipFacePoint].m_flBlends[2] = pCoefs[2]; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- int CMapOverlay::ClipFace_GetAxisType( cplane_t *pClipPlane ) { if ( pClipPlane->normal[0] == 1.0f ) { return OVERLAY_ANGLE90; } if ( pClipPlane->normal[1] == 1.0f ) { return OVERLAY_ANGLE0; } if ( ( pClipPlane->normal[0] == 0.707f ) && ( pClipPlane->normal[1] == 0.707f ) ) { return OVERLAY_ANGLE45; } if ( ( pClipPlane->normal[0] == -0.707f ) && ( pClipPlane->normal[1] == 0.707f ) ) { return OVERLAY_ANGLE135; } return OVERLAY_ANGLE0; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::ClipFace_BuildBlend( ClipFace_t *pClipFace, CMapDisp *pDisp, cplane_t *pClipPlane, int iClip, const Vector &vecUV, const Vector &vecPoint ) { // Get the displacement space interval. int nWidth = pDisp->GetWidth(); int nHeight = pDisp->GetHeight(); float flU = vecUV.x * ( nWidth - 1.000001f ); float flV = vecUV.y * ( nHeight - 1.000001f ); // find the triangle the "uv spot" resides in int nSnapU = static_cast( flU ); int nSnapV = static_cast( flV ); if ( nSnapU == ( nWidth - 1 ) ) { --nSnapU; } if ( nSnapV == ( nHeight - 1 ) ) { --nSnapV; } int nNextU = nSnapU + 1; int nNextV = nSnapV + 1; float flFracU = flU - static_cast( nSnapU ); float flFracV = flV - static_cast( nSnapV ); int iAxisType = ClipFace_GetAxisType( pClipPlane ); switch( iAxisType ) { case OVERLAY_ANGLE0: { // Vert type if ( fabs( flFracU ) < OVERLAY_DISPSPACE_EPSILON ) { pClipFace->m_aBlends[pClipFace->m_nPointCount].m_nType = OVERLAY_BLENDTYPE_VERT; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_iPoints[0] = ( nWidth * iClip ) + nSnapU; } // Edge type else { pClipFace->m_aBlends[pClipFace->m_nPointCount].m_nType = OVERLAY_BLENDTYPE_EDGE; int iPoint0 = ( nWidth * iClip ) + nSnapU; int iPoint1 = ( nWidth * iClip ) + nNextU; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_iPoints[0] = iPoint0; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_iPoints[1] = iPoint1; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_flBlends[0] = flFracU; } return; } case OVERLAY_ANGLE45: { // Vert type if ( ( fabs( flFracU ) < OVERLAY_DISPSPACE_EPSILON ) && ( fabs( flFracV ) < OVERLAY_DISPSPACE_EPSILON ) ) { pClipFace->m_aBlends[pClipFace->m_nPointCount].m_nType = OVERLAY_BLENDTYPE_VERT; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_iPoints[0] = ( nWidth * nSnapV ) + nSnapU; } // Edge type else { pClipFace->m_aBlends[pClipFace->m_nPointCount].m_nType = OVERLAY_BLENDTYPE_EDGE; int iPoint0 = ( nWidth * nNextV ) + nSnapU; int iPoint1 = ( nWidth * nSnapV ) + nNextU; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_iPoints[0] = iPoint0; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_iPoints[1] = iPoint1; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_flBlends[0] = flFracU; } return; } case OVERLAY_ANGLE90: { // Vert type if ( fabs( flFracV ) < OVERLAY_DISPSPACE_EPSILON ) { pClipFace->m_aBlends[pClipFace->m_nPointCount].m_nType = OVERLAY_BLENDTYPE_VERT; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_iPoints[0] = ( nWidth * nSnapV ) + iClip; } // Edge type else { pClipFace->m_aBlends[pClipFace->m_nPointCount].m_nType = OVERLAY_BLENDTYPE_EDGE; int iPoint0 = ( nWidth * nSnapV ) + iClip; int iPoint1 = ( nWidth * nNextV ) + iClip; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_iPoints[0] = iPoint0; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_iPoints[1] = iPoint1; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_flBlends[0] = flFracV; } return; } case OVERLAY_ANGLE135: { // Vert type if ( ( fabs( flFracU ) < OVERLAY_DISPSPACE_EPSILON ) && ( fabs( flFracV ) < OVERLAY_DISPSPACE_EPSILON ) ) { pClipFace->m_aBlends[pClipFace->m_nPointCount].m_nType = OVERLAY_BLENDTYPE_VERT; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_iPoints[0] = ( nWidth * nSnapV ) + nSnapU; } // Edge type else { pClipFace->m_aBlends[pClipFace->m_nPointCount].m_nType = OVERLAY_BLENDTYPE_EDGE; int iPoint0 = ( nWidth * nSnapV ) + nSnapU; int iPoint1 = ( nWidth * nNextV ) + nNextU; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_iPoints[0] = iPoint0; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_iPoints[1] = iPoint1; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_flBlends[0] = flFracU; } return; } } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::ClipFace_CopyBlendFrom( ClipFace_t *pClipFace, BlendData_t *pBlendFrom ) { pClipFace->m_aBlends[pClipFace->m_nPointCount].m_nType = pBlendFrom->m_nType; for ( int iPoint = 0; iPoint < 3; iPoint++ ) { pClipFace->m_aBlends[pClipFace->m_nPointCount].m_iPoints[iPoint] = pBlendFrom->m_iPoints[iPoint]; pClipFace->m_aBlends[pClipFace->m_nPointCount].m_flBlends[iPoint] = pBlendFrom->m_flBlends[iPoint]; } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::ClipFace_BuildFacesFromBlendedData( ClipFace_t *pClipFace ) { if( pClipFace->m_pBuildFace->HasDisp() ) { EditDispHandle_t handle = pClipFace->m_pBuildFace->GetDisp(); CMapDisp *pDisp = EditDispMgr()->GetDisp( handle ); Vector vecPos[3]; for ( int iPoint = 0; iPoint < pClipFace->m_nPointCount; iPoint++ ) { if ( pClipFace->m_aBlends[iPoint].m_nType == OVERLAY_BLENDTYPE_VERT ) { pDisp->GetVert( pClipFace->m_aBlends[iPoint].m_iPoints[0], vecPos[0] ); pClipFace->m_aPoints[iPoint] = vecPos[0]; } else if ( pClipFace->m_aBlends[iPoint].m_nType == OVERLAY_BLENDTYPE_EDGE ) { pDisp->GetVert( pClipFace->m_aBlends[iPoint].m_iPoints[0], vecPos[0] ); pDisp->GetVert( pClipFace->m_aBlends[iPoint].m_iPoints[1], vecPos[1] ); pClipFace->m_aPoints[iPoint] = vecPos[0] + ( vecPos[1] - vecPos[0] ) * pClipFace->m_aBlends[iPoint].m_flBlends[0]; } else if ( pClipFace->m_aBlends[iPoint].m_nType == OVERLAY_BLENDTYPE_BARY ) { pDisp->GetVert( pClipFace->m_aBlends[iPoint].m_iPoints[0], vecPos[0] ); pDisp->GetVert( pClipFace->m_aBlends[iPoint].m_iPoints[1], vecPos[1] ); pDisp->GetVert( pClipFace->m_aBlends[iPoint].m_iPoints[2], vecPos[2] ); pClipFace->m_aPoints[iPoint] = ( vecPos[0] * pClipFace->m_aBlends[iPoint].m_flBlends[0] ) + ( vecPos[1] * pClipFace->m_aBlends[iPoint].m_flBlends[1] ) + ( vecPos[2] * pClipFace->m_aBlends[iPoint].m_flBlends[2] ); } } } } //============================================================================= // // CMapOverlay Material Functions // int MaxComponent( const Vector &v0 ) { int nMax = 0; if ( FloatMakePositive( v0[1] ) > FloatMakePositive( v0[nMax] ) ) { nMax = 1; } if ( FloatMakePositive( v0[2] ) > FloatMakePositive( v0[nMax] ) ) { nMax = 2; } return nMax; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::Material_Clear( void ) { m_Material.m_pTexture = NULL; m_Material.m_vecTextureU.Init( 0.0f, 1.0f ); m_Material.m_vecTextureV.Init( 0.0f, 1.0f ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Material_TexCoordInit( void ) { int nMaxU = MaxComponent( m_Basis.m_vecAxes[OVERLAY_BASIS_U] ); int nMaxV = MaxComponent( m_Basis.m_vecAxes[OVERLAY_BASIS_V] ); bool bUPos = m_Basis.m_vecAxes[OVERLAY_BASIS_U][nMaxU] >= 0.0f; bool bVPos = m_Basis.m_vecAxes[OVERLAY_BASIS_V][nMaxV] >= 0.0f; m_Material.m_vecTextureU.Init( 0.0f, 1.0f ); m_Material.m_vecTextureV.Init( 1.0f, 0.0f ); if ( ( bUPos && !bVPos ) || ( !bUPos && bVPos ) ) { m_Material.m_vecTextureU.Init( 1.0f, 0.0f ); m_Material.m_vecTextureV.Init( 0.0f, 1.0f ); } Material_UpdateParentKey(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::Material_Copy( Material_t *pSrc, Material_t *pDst ) { pDst->m_pTexture = pSrc->m_pTexture; pDst->m_vecTextureU = pSrc->m_vecTextureU; pDst->m_vecTextureV = pSrc->m_vecTextureV; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Material_UpdateParentKey( void ) { char szValue[80]; CMapEntity *pEntity = ( CMapEntity* )GetParent(); if ( pEntity ) { sprintf( szValue, "%g", m_Material.m_vecTextureU.x ); pEntity->NotifyChildKeyChanged( this, "StartU", szValue ); sprintf( szValue, "%g", m_Material.m_vecTextureU.y ); pEntity->NotifyChildKeyChanged( this, "EndU", szValue ); sprintf( szValue, "%g", m_Material.m_vecTextureV.x ); pEntity->NotifyChildKeyChanged( this, "StartV", szValue ); sprintf( szValue, "%g", m_Material.m_vecTextureV.y ); pEntity->NotifyChildKeyChanged( this, "EndV", szValue ); } } //============================================================================= // // CMapOverlay Functions // //----------------------------------------------------------------------------- // Purpose: Construct a CMapOverlay instance. //----------------------------------------------------------------------------- CMapOverlay::CMapOverlay() : CMapSideList( "sides" ) { Basis_Clear(); Handles_Clear(); Material_Clear(); m_bLoaded = false; m_pOverlayFace = NULL; m_uiFlags = 0; } //----------------------------------------------------------------------------- // Purpose: Destruct a CMapOverlay instance. //----------------------------------------------------------------------------- CMapOverlay::~CMapOverlay() { ClipFace_Destroy( &m_pOverlayFace ); m_aRenderFaces.PurgeAndDeleteElements(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- CMapClass *CMapOverlay::CreateMapOverlay( CHelperInfo *pInfo, CMapEntity *pParent ) { CMapOverlay *pOverlay = new CMapOverlay; return pOverlay; } //----------------------------------------------------------------------------- // Purpose: Called after the entire map has been loaded. This allows the object // to perform any linking with other map objects or to do other operations // that require all world objects to be present. // Input : pWorld - The world that we are in. //----------------------------------------------------------------------------- void CMapOverlay::PostloadWorld( CMapWorld *pWorld ) { CMapSideList::PostloadWorld( pWorld ); // Support older overlay versions which didn't have specific basis axes. if ( !Basis_IsValid() ) { Basis_BuildFromSideList(); } Handles_Build3D(); DoClip(); CalcBounds(); m_bLoaded = true; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- CMapClass *CMapOverlay::Copy( bool bUpdateDependencies ) { CMapOverlay *pCopy = new CMapOverlay; if ( pCopy ) { pCopy->CopyFrom( this, bUpdateDependencies ); } return pCopy; } void CMapOverlay::Handles_FixOrder() { static bool s_FixingHandles = false; // make sure that handle order and plane normal are in sync so CCW culling works correctly Vector vNormal = GetNormalFromPoints( m_Handles.m_vec3D[0], m_Handles.m_vec3D[1], m_Handles.m_vec3D[2] ); if ( DotProduct( vNormal, m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL]) < 0.5 ) { // dont try to fix twice if ( s_FixingHandles ) { Assert( !s_FixingHandles ); return; } s_FixingHandles = true; // Flip handles. Vector2D vecCoords[OVERLAY_HANDLES_COUNT]; for ( int iHandle = 0; iHandle < OVERLAY_HANDLES_COUNT; iHandle++ ) { vecCoords[4-iHandle-1] = m_Handles.m_vecBasisCoords[iHandle]; } for ( int iHandle = 0; iHandle < OVERLAY_HANDLES_COUNT; iHandle++ ) { m_Handles.m_vecBasisCoords[iHandle] = vecCoords[iHandle]; } // rebuild handles Handles_Build3D(); s_FixingHandles = false; } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- CMapClass *CMapOverlay::CopyFrom( CMapClass *pObject, bool bUpdateDependencies ) { // Verify the object is of the correct type and cast. Assert( pObject->IsMapClass( MAPCLASS_TYPE( CMapOverlay ) ) ); CMapOverlay *pFrom = ( CMapOverlay* )pObject; if ( pFrom ) { // Copy the parent class data. CMapSideList::CopyFrom( pObject, bUpdateDependencies ); // Copy basis data. Basis_Copy( &pFrom->m_Basis, &m_Basis ); // Copy handle data. Handles_Copy( &pFrom->m_Handles, &m_Handles ); // Copy material data. Material_Copy( &pFrom->m_Material, &m_Material ); } return this; } //----------------------------------------------------------------------------- // Purpose: Notify me when a key has had a data change, so the overlay can // update itself appropriately. // Input: szKey - the key that changed // szValue - the new value (key/data pair) //----------------------------------------------------------------------------- void CMapOverlay::OnParentKeyChanged( const char* szKey, const char* szValue ) { // Pass this to the sidelist first. CMapSideList::OnParentKeyChanged( szKey, szValue ); // Read side data. if ( !stricmp( szKey, "sides" ) ) { if ( m_Faces.Count() > 0 ) { Basis_SetFace( m_Faces.Element( 0 ) ); } } // Read geometry data. float flDummy; if ( !stricmp( szKey, "uv0" ) ) { sscanf( szValue, "%f %f %f", &m_Handles.m_vecBasisCoords[0].x, &m_Handles.m_vecBasisCoords[0].y, &flDummy ); m_Basis.m_nAxesFlip[0] = ( int )flDummy; } if ( !stricmp( szKey, "uv1" ) ) { sscanf( szValue, "%f %f %f", &m_Handles.m_vecBasisCoords[1].x, &m_Handles.m_vecBasisCoords[1].y, &flDummy ); m_Basis.m_nAxesFlip[1] = ( int )flDummy; } if ( !stricmp( szKey, "uv2" ) ) { sscanf( szValue, "%f %f %f", &m_Handles.m_vecBasisCoords[2].x, &m_Handles.m_vecBasisCoords[2].y, &flDummy ); m_Basis.m_nAxesFlip[2] = ( int )flDummy; } if ( !stricmp( szKey, "uv3" ) ) { sscanf( szValue, "%f %f %f", &m_Handles.m_vecBasisCoords[3].x, &m_Handles.m_vecBasisCoords[3].y, &flDummy ); } // Read basis data. if ( !stricmp( szKey, "BasisOrigin" ) ) { sscanf( szValue, "%f %f %f", &m_Basis.m_vecOrigin.x, &m_Basis.m_vecOrigin.y, &m_Basis.m_vecOrigin.z ); } if ( !stricmp( szKey, "BasisU" ) ) { sscanf( szValue, "%f %f %f", &m_Basis.m_vecAxes[OVERLAY_BASIS_U].x, &m_Basis.m_vecAxes[OVERLAY_BASIS_U].y, &m_Basis.m_vecAxes[OVERLAY_BASIS_U].z ); } if ( !stricmp( szKey, "BasisV" ) ) { sscanf( szValue, "%f %f %f", &m_Basis.m_vecAxes[OVERLAY_BASIS_V].x, &m_Basis.m_vecAxes[OVERLAY_BASIS_V].y, &m_Basis.m_vecAxes[OVERLAY_BASIS_V].z ); } if ( !stricmp( szKey, "BasisNormal" ) ) { sscanf( szValue, "%f %f %f", &m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL].x, &m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL].y, &m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL].z ); } // Read material data. if ( !stricmp( szKey, "material" ) ) { // Get the new material. IEditorTexture *pTex = g_Textures.FindActiveTexture( szValue ); if ( !pTex ) return; // Save the new material. m_Material.m_pTexture = pTex; } if ( !stricmp( szKey, "StartU" ) ) { m_Material.m_vecTextureU.x = atof( szValue ); } if ( !stricmp( szKey, "EndU" ) ) { m_Material.m_vecTextureU.y = atof( szValue ); } if ( !stricmp( szKey, "StartV" ) ) { m_Material.m_vecTextureV.x = atof( szValue ); } if ( !stricmp( szKey, "EndV" ) ) { m_Material.m_vecTextureV.y = atof( szValue ); } if ( m_bLoaded ) { // Clip - this needs to be done for everything other than a material change, so go ahead. DoClip(); // Post updated. PostUpdate( Notify_Changed ); } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::OnUndoRedo( void ) { PostModified(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::CalcBounds( BOOL bFullUpdate ) { // Pass the info along. CMapSideList::CalcBounds( bFullUpdate ); // Verify that we have valid data. if ( !Basis_IsValid() ) return; // Calculate the 2d bounds. Vector vecMins, vecMaxs; vecMins = m_Origin - Vector( 2.0f, 2.0f, 2.0f ); vecMaxs = m_Origin + Vector( 2.0f, 2.0f, 2.0f ); // Reset bounds m_CullBox.ResetBounds(); m_Render2DBox.ResetBounds(); for ( int iHandle = 0; iHandle < 4; ++iHandle ) { for ( int iAxis = 0; iAxis < 3; ++iAxis ) { // Min if ( m_Handles.m_vec3D[iHandle][iAxis] < vecMins[iAxis] ) { vecMins[iAxis] = m_Handles.m_vec3D[iHandle][iAxis]; } // Max if ( m_Handles.m_vec3D[iHandle][iAxis] > vecMaxs[iAxis] ) { vecMaxs[iAxis] = m_Handles.m_vec3D[iHandle][iAxis]; } } } // Don't allow for NULL bounds. for ( int iAxis = 0; iAxis < 3; ++iAxis ) { if( ( vecMaxs[iAxis] - vecMins[iAxis] ) == 0.0f ) { vecMins[iAxis] -= 0.5f; vecMaxs[iAxis] += 0.5f; } } // Update the bounds. m_CullBox.UpdateBounds( vecMins, vecMaxs ); m_BoundingBox = m_CullBox; m_Render2DBox.UpdateBounds( vecMins, vecMaxs ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::PostModified( void ) { // update face and origin if ( m_Faces.Count() > 0 ) { Basis_SetFace( m_Faces.Element( 0 ) ); Basis_UpdateOrigin(); } else { m_Basis.m_pFace = NULL; } Handles_Build3D(); DoClip(); } //----------------------------------------------------------------------------- // Purpose: // Input : pTransBox - //----------------------------------------------------------------------------- void CMapOverlay::DoTransform( const VMatrix &matrix ) { BaseClass::DoTransform( matrix ); VMatrix tmpMatrix = matrix; // erase move component tmpMatrix.SetTranslation( vec3_origin ); // check if matrix would still change something if ( !tmpMatrix.IsIdentity() ) { // make sure axes are normalized (they should be anyways) m_Basis.m_vecAxes[OVERLAY_BASIS_U].NormalizeInPlace(); m_Basis.m_vecAxes[OVERLAY_BASIS_V].NormalizeInPlace(); Vector vecU = m_Basis.m_vecAxes[OVERLAY_BASIS_U]; Vector vecV = m_Basis.m_vecAxes[OVERLAY_BASIS_V]; Vector vecNormal = m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL]; TransformPoint( tmpMatrix, vecU ); TransformPoint( tmpMatrix, vecV ); TransformPoint( tmpMatrix, vecNormal ); float fScaleU = vecU.Length(); float fScaleV = vecV.Length(); float flScaleNormal = vecNormal.Length(); bool bIsUnit = ( fequal( fScaleU, 1.0f, 0.0001 ) && fequal( fScaleV, 1.0f, 0.0001 ) && fequal( flScaleNormal, 1.0f, 0.0001 ) ); bool bIsPerp = ( fequal( DotProduct( vecU, vecV ), 0.0f, 0.0025 ) && fequal( DotProduct( vecU, vecNormal ), 0.0f, 0.0025 ) && fequal( DotProduct( vecV, vecNormal ), 0.0f, 0.0025 ) ); // if ( fequal(fScaleU,1,0.0001) && fequal(fScaleV,1,0.0001) && fequal(DotProduct( vecU, vecV ),0,0.0025) ) if ( bIsUnit && bIsPerp ) { // transformation doesnt scale or shear anything, so just update base axes m_Basis.m_vecAxes[OVERLAY_BASIS_U] = vecU; m_Basis.m_vecAxes[OVERLAY_BASIS_V] = vecV; m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL] = vecNormal; } else { // more complex transformation, move UV coordinates, but leave base axes for ( int iHandle=0; iHandle( pCopy ); if ( pOverlay ) { pOverlay->Basis_BuildFromSideList(); pOverlay->PostModified(); } } //----------------------------------------------------------------------------- // Purpose: Notifies us that we created a copy of ourselves (a clone). //----------------------------------------------------------------------------- void CMapOverlay::OnClone( CMapClass *pClone, CMapWorld *pWorld, const CMapObjectList &OriginalList, CMapObjectList &NewList ) { CMapSideList::OnClone( pClone, pWorld, OriginalList, NewList ); CMapOverlay *pOverlay = dynamic_cast( pClone ); if ( pOverlay ) { if ( ( GetOverlayType() && OVERLAY_TYPE_SHORE ) == 0 ) { // Update the clone's solid dependencies (this doesn't happen on clone generally). int nFaceCount = pOverlay->GetFaceCount(); for ( int iFace = 0; iFace < nFaceCount; ++iFace ) { CMapFace *pFace = pOverlay->GetFace( iFace ); CMapSolid *pSolid = ( CMapSolid* )pFace->GetParent(); pOverlay->UpdateDependency( NULL, pSolid ); } } pOverlay->PostModified(); } } //----------------------------------------------------------------------------- // Purpose: Notifys this decal of a change to a solid that it is attached to. //----------------------------------------------------------------------------- void CMapOverlay::OnNotifyDependent( CMapClass *pObject, Notify_Dependent_t eNotifyType ) { // Chain to base class FIRST so it can rebuild the face list if necessary. CMapSideList::OnNotifyDependent( pObject, eNotifyType ); // // NOTE: the solid moving (changing) can update the overlay/solid(face) dependency // so "rebuild" the overlay // switch ( eNotifyType ) { case Notify_Changed: case Notify_Undo: case Notify_Transform: { PostModified(); break; } case Notify_Removed: case Notify_Clipped: { m_aRenderFaces.Purge(); PostModified(); break; } case Notify_Rebuild: { UpdateDispBarycentric(); break; } case Notify_Rebuild_Full: { DoClip(); CenterEntity(); Handles_Build3D(); break; } } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::Render3D( CRender3D *pRender ) { int nFaceCount = m_aRenderFaces.Count(); if ( nFaceCount != 0 ) { // dont draw textured during manipulating if ( GetSelectionState() != SELECT_MODIFY ) { // Bind the matrial -- if there is one!! bool bTextured = false; if ( m_Material.m_pTexture ) { pRender->BindTexture( m_Material.m_pTexture ); pRender->PushRenderMode( RENDER_MODE_TEXTURED ); bTextured = true; } else { // Default state. pRender->PushRenderMode( RENDER_MODE_FLAT ); } for ( int iFace = 0; iFace < nFaceCount; iFace++ ) { ClipFace_t *pRenderFace = m_aRenderFaces.Element( iFace ); if( !pRenderFace ) continue; MaterialPrimitiveType_t type = MATERIAL_POLYGON; // Get a dynamic mesh. CMeshBuilder meshBuilder; CMatRenderContextPtr pRenderContext( MaterialSystemInterface() ); IMesh* pMesh = pRenderContext->GetDynamicMesh(); meshBuilder.Begin( pMesh, type, pRenderFace->m_nPointCount ); for ( int iPoint = 0; iPoint < pRenderFace->m_nPointCount; iPoint++ ) { if ( !bTextured ) { meshBuilder.Color3ub( 0, 128, 0 ); } else { meshBuilder.TexCoord2f( 0, pRenderFace->m_aTexCoords[0][iPoint].x, pRenderFace->m_aTexCoords[0][iPoint].y ); meshBuilder.TexCoord2f( 2, pRenderFace->m_aTexCoords[1][iPoint].x, pRenderFace->m_aTexCoords[1][iPoint].y ); meshBuilder.Color4ub( 255, 255, 255, 255 ); } meshBuilder.Position3f( pRenderFace->m_aPoints[iPoint].x, pRenderFace->m_aPoints[iPoint].y, pRenderFace->m_aPoints[iPoint].z ); meshBuilder.AdvanceVertex(); } meshBuilder.End(); pMesh->Draw(); } pRender->PopRenderMode(); } // Render wireframe on top when seleted. if ( GetSelectionState() != SELECT_NONE ) { pRender->PushRenderMode( RENDER_MODE_WIREFRAME ); for ( int iFace = 0; iFace < nFaceCount; iFace++ ) { ClipFace_t *pRenderFace = m_aRenderFaces.Element( iFace ); if( !pRenderFace ) continue; MaterialPrimitiveType_t type = MATERIAL_LINE_LOOP; // get a dynamic mesh CMeshBuilder meshBuilder; CMatRenderContextPtr pRenderContext( MaterialSystemInterface() ); IMesh* pMesh = pRenderContext->GetDynamicMesh(); meshBuilder.Begin( pMesh, type, pRenderFace->m_nPointCount ); for( int iPoint = 0; iPoint < pRenderFace->m_nPointCount; iPoint++ ) { meshBuilder.Color3ub( 0, 255, 0 ); meshBuilder.Position3f( pRenderFace->m_aPoints[iPoint].x, pRenderFace->m_aPoints[iPoint].y, pRenderFace->m_aPoints[iPoint].z ); meshBuilder.AdvanceVertex(); } meshBuilder.End(); pMesh->Draw(); } pRender->PopRenderMode(); } } // Render the handles - if selected or in overlay tool mode. if ( ( ToolManager()->GetActiveToolID() == TOOL_OVERLAY ) && Basis_IsValid() && IsSelected() ) { Handles_Render3D( pRender ); } } //----------------------------------------------------------------------------- // Purpose: Clip the overlay "face" to all of the faces in the overlay sidelist. // The sidelist defines all faces affected by the "overlay." //----------------------------------------------------------------------------- void CMapOverlay::DoClip( void ) { // Check to see if we have any faces to clip against. int nFaceCount = m_Faces.Count(); if( nFaceCount == 0 ) return; // Destroy the render face cache. m_aRenderFaces.Purge(); // clip the overlay against all faces in the sidelist for ( int iFace = 0; iFace < nFaceCount; iFace++ ) { CMapFace *pFace = m_Faces.Element( iFace ); if ( pFace ) { PreClip(); DoClipFace( pFace ); PostClip(); } } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::PreClip( void ) { // // Create the initial face to be clipped - the overlay. // m_pOverlayFace = ClipFace_Create( OVERLAY_HANDLES_COUNT ); if ( m_pOverlayFace ) { for ( int iPoint = 0; iPoint < OVERLAY_HANDLES_COUNT; iPoint++ ) { OverlayUVToOverlayPlane( m_Handles.m_vecBasisCoords[iPoint], m_pOverlayFace->m_aPoints[iPoint] ); // translate texture UV to texture coords: Vector2D vTexCoord; switch( iPoint ) { case 0 : vTexCoord = Vector2D(m_Material.m_vecTextureU.x, m_Material.m_vecTextureV.x); break; case 1 : vTexCoord = Vector2D(m_Material.m_vecTextureU.x, m_Material.m_vecTextureV.y); break; case 2 : vTexCoord = Vector2D(m_Material.m_vecTextureU.y, m_Material.m_vecTextureV.y); break; case 3 : vTexCoord = Vector2D(m_Material.m_vecTextureU.y, m_Material.m_vecTextureV.x); break; default : Assert( iPoint <= OVERLAY_HANDLES_COUNT); } m_pOverlayFace->m_aTexCoords[0][iPoint] = vTexCoord; if ( m_Basis.m_pFace->HasDisp() ) { EditDispHandle_t handle = m_Basis.m_pFace->GetDisp(); CMapDisp *pDisp = EditDispMgr()->GetDisp( handle ); if ( pDisp ) { Vector2D vecTmp; pDisp->BaseFacePlaneToDispUV( m_pOverlayFace->m_aPoints[iPoint], vecTmp ); m_pOverlayFace->m_aDispPointUVs[iPoint].x = vecTmp.x; m_pOverlayFace->m_aDispPointUVs[iPoint].y = vecTmp.y; m_pOverlayFace->m_aDispPointUVs[iPoint].z = 0.0f; } } } // The second set of texcoords on the overlay is used for alpha by certain shaders, // and they want to stretch the texture across the whole overlay. m_pOverlayFace->m_aTexCoords[1][0].Init( 0, 0 ); m_pOverlayFace->m_aTexCoords[1][1].Init( 0, 1 ); m_pOverlayFace->m_aTexCoords[1][2].Init( 1, 1 ); m_pOverlayFace->m_aTexCoords[1][3].Init( 1, 0 ); } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::PostClip( void ) { ClipFace_Destroy( &m_pOverlayFace ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::DoClipFace( CMapFace *pFace ) { // Valid face? Assert( pFace != NULL ); if( !pFace ) return; // Copy the original overlay to the "clipped" overlay. ClipFace_t *pClippedFace = ClipFace_Copy( m_pOverlayFace ); if ( !pClippedFace ) return; // // Project all face points into the overlay plane. // int nPointCount = pFace->nPoints; Vector *pPoints = new Vector[nPointCount]; int nEdgePlaneCount = nPointCount; cplane_t *pEdgePlanes = new cplane_t[nEdgePlaneCount]; if ( !pPoints || !pEdgePlanes ) { delete [] pPoints; delete [] pEdgePlanes; return; } for ( int iPoint = 0; iPoint < nPointCount; iPoint++ ) { WorldToOverlayPlane( pFace->Points[iPoint], pPoints[iPoint] ); } // Create the face clipping planes (edges cross overlay plane normal). BuildEdgePlanes( pPoints, nPointCount, pEdgePlanes, nEdgePlaneCount ); // // Clip overlay against all the edge planes. // for ( int iClipPlane = 0; iClipPlane < nEdgePlaneCount; iClipPlane++ ) { ClipFace_t *pFront = NULL; ClipFace_t *pBack = NULL; if ( pClippedFace ) { // Clip the overlay and delete the data (we are done with it - we are only interested in what is left). ClipFace_Clip( pClippedFace, &pEdgePlanes[iClipPlane], OVERLAY_WORLDSPACE_EPSILON, &pFront, &pBack ); ClipFace_Destroy( &pClippedFace ); // Keep the backside -- if it exists and continue clipping. if ( pBack ) { pClippedFace = pBack; } // Destroy the front side -- if it exists. if ( pFront ) { ClipFace_Destroy( &pFront ); } } } // // Free temporary memory (clip planes and point). // delete [] pPoints; delete [] pEdgePlanes; // // If it exists, move points from the overlay plane back into // the base face plane. // if ( !pClippedFace ) return; for ( int iPoint = 0; iPoint < pClippedFace->m_nPointCount; iPoint++ ) { Vector2D vecUV; PointInQuadToBarycentric( m_pOverlayFace->m_aPoints[0], m_pOverlayFace->m_aPoints[3], m_pOverlayFace->m_aPoints[2], m_pOverlayFace->m_aPoints[1], pClippedFace->m_aPoints[iPoint], vecUV ); Vector vecTmp; OverlayPlaneToWorld( pFace, pClippedFace->m_aPoints[iPoint], vecTmp ); pClippedFace->m_aPoints[iPoint] = vecTmp; Vector2D vecTexCoord; for ( int iTexCoord=0; iTexCoord < NUM_CLIPFACE_TEXCOORDS; iTexCoord++ ) { TexCoordInQuadFromBarycentric( m_pOverlayFace->m_aTexCoords[iTexCoord][0], m_pOverlayFace->m_aTexCoords[iTexCoord][3], m_pOverlayFace->m_aTexCoords[iTexCoord][2], m_pOverlayFace->m_aTexCoords[iTexCoord][1], vecUV, vecTexCoord ); pClippedFace->m_aTexCoords[iTexCoord][iPoint] = vecTexCoord; } } // // If the face has a displacement map -- continue clipping. // if( pFace->HasDisp() ) { DoClipDisp( pFace, pClippedFace ); } // Done - save it! else { pClippedFace->m_pBuildFace = pFace; m_aRenderFaces.AddToTail( pClippedFace ); } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CMapOverlay::BuildEdgePlanes( Vector const *pPoints, int nPointCount, cplane_t *pEdgePlanes, int nEdgePlaneCount ) { for ( int iPoint = 0; iPoint < nPointCount; iPoint++ ) { Vector vecEdge; vecEdge = pPoints[(iPoint+1)%nPointCount] - pPoints[iPoint]; VectorNormalize( vecEdge ); pEdgePlanes[iPoint].normal = m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL].Cross( vecEdge ); pEdgePlanes[iPoint].dist = pEdgePlanes[iPoint].normal.Dot( pPoints[iPoint] ); // Check normal facing. float flDist = pEdgePlanes[iPoint].normal.Dot( pPoints[(iPoint+2)%nPointCount] ) - pEdgePlanes[iPoint].dist; if( flDist > 0.0f ) { // flip pEdgePlanes[iPoint].normal.Negate(); pEdgePlanes[iPoint].dist = -pEdgePlanes[iPoint].dist; } } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Disp_ClipFragments( CMapDisp *pDisp, ClipFaces_t &aDispFragments ) { cplane_t clipPlane; // Cache the displacement interval. int nInterval = pDisp->GetWidth() - 1; // Displacement-space clipping in V. clipPlane.normal.Init( 1.0f, 0.0f, 0.0f ); Disp_DoClip( pDisp, aDispFragments, clipPlane, 1.0f, nInterval, 1, nInterval, 1 ); // Displacement-space clipping in U. clipPlane.normal.Init( 0.0f, 1.0f, 0.0f ); Disp_DoClip( pDisp, aDispFragments, clipPlane, 1.0f, nInterval, 1, nInterval, 1 ); // Displacement-space clipping UV from top-left to bottom-right. clipPlane.normal.Init( 0.707f, 0.707f, 0.0f ); // 45 degrees Disp_DoClip( pDisp, aDispFragments, clipPlane, 0.707f, nInterval, 2, ( nInterval * 2 - 1 ), 2 ); // Displacement-space clipping UV from bottom-left to top-right. clipPlane.normal.Init( -0.707f, 0.707f, 0.0f ); // 135 degrees Disp_DoClip( pDisp, aDispFragments, clipPlane, 0.707f, nInterval, -( nInterval - 2 ), ( nInterval - 1 ), 2 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::Disp_DoClip( CMapDisp *pDisp, ClipFaces_t &aDispFragments, cplane_t &clipPlane, float clipDistStart, int nInterval, int nLoopStart, int nLoopEnd, int nLoopInc ) { // Setup interval information. float flInterval = static_cast( nInterval ); float flOOInterval = 1.0f / flInterval; // Holds the current set of clipped faces. ClipFaces_t aClippedFragments; for ( int iInterval = nLoopStart; iInterval < nLoopEnd; iInterval += nLoopInc ) { // Copy the current list to clipped face list. aClippedFragments.CopyArray( aDispFragments.Base(), aDispFragments.Count() ); aDispFragments.Purge(); // Clip in V. int nFragCount = aClippedFragments.Count(); for ( int iFrag = 0; iFrag < nFragCount; iFrag++ ) { ClipFace_t *pClipFrag = aClippedFragments[iFrag]; if ( pClipFrag ) { ClipFace_t *pFront = NULL, *pBack = NULL; clipPlane.dist = clipDistStart * ( ( float )iInterval * flOOInterval ); ClipFace_ClipBarycentric( pClipFrag, &clipPlane, OVERLAY_DISPSPACE_EPSILON, iInterval, pDisp, &pFront, &pBack ); ClipFace_Destroy( &pClipFrag ); if ( pFront ) { aDispFragments.AddToTail( pFront ); } if ( pBack ) { aDispFragments.AddToTail( pBack ); } } } } // Clean up! aClippedFragments.Purge(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::DoClipDisp( CMapFace *pFace, ClipFace_t *pClippedFace ) { // Get the displacement data. EditDispHandle_t handle = pFace->GetDisp(); CMapDisp *pDisp = EditDispMgr()->GetDisp( handle ); // Initialize local clip data. ClipFace_PreClipDisp( pClippedFace, pDisp ); // Setup clipped face lists. ClipFaces_t aCurrentFaces; aCurrentFaces.AddToTail( pClippedFace ); Disp_ClipFragments( pDisp, aCurrentFaces ); // // Project points back onto the displacement surface. // int nFaceCount = aCurrentFaces.Count(); for( int iFace = 0; iFace < nFaceCount; iFace++ ) { ClipFace_t *pClipFace = aCurrentFaces[iFace]; if ( pClipFace ) { // Save for re-building later! pClipFace->m_pBuildFace = pFace; m_aRenderFaces.AddToTail( aCurrentFaces[iFace] ); ClipFace_BuildFacesFromBlendedData( pClipFace ); } } // Clean up! aCurrentFaces.Purge(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::HandlesReset( void ) { m_Handles.m_iHit = -1; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CMapOverlay::HandlesHitTest( CMapView *pView, const Vector2D &vPoint ) { int handleRadius = 8; for ( int iPoint = 0; iPoint < 4; iPoint++ ) { Vector2D vHandle; pView->WorldToClient( vHandle, m_Handles.m_vec3D[iPoint] ); if ( vPoint.x < (vHandle.x-handleRadius) || vPoint.x > ( vHandle.x+handleRadius) ) continue; if ( vPoint.y < (vHandle.y-handleRadius) || vPoint.y > ( vHandle.y+handleRadius) ) continue; m_Handles.m_iHit = iPoint; return true; } return false; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::HandlesDragTo( Vector &vecImpact, CMapFace *pFace ) { // Check handle index range. if ( ( m_Handles.m_iHit < 0 ) || ( m_Handles.m_iHit > 3 ) ) return; // Save m_Handles.m_vec3D[m_Handles.m_iHit] = vecImpact; // Project the point into the overlay plane (from face/disp). Vector vecOverlay; Vector2D vecUVOverlay; Handles_SurfToOverlayPlane( pFace, vecImpact, vecOverlay ); OverlayPlaneToOverlayUV( vecOverlay, vecUVOverlay ); m_Handles.m_vecBasisCoords[m_Handles.m_iHit] = vecUVOverlay; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::HandleMoveTo( int iHandle, Vector &vecPoint, CMapFace *pFace ) { if ( ( iHandle < 0 ) || ( iHandle > 3 ) ) return; m_Handles.m_vec3D[iHandle] = vecPoint; // Project the point into the overlay plane (from face/disp). Vector vecOverlay; Vector2D vecUVOverlay; Handles_SurfToOverlayPlane( pFace, vecPoint, vecOverlay ); OverlayPlaneToOverlayUV( vecOverlay, vecUVOverlay ); m_Handles.m_vecBasisCoords[iHandle] = vecUVOverlay; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::SetTexCoords( Vector2D vecTexCoords[4] ) { m_Material.m_vecTextureU.x = vecTexCoords[0][0]; m_Material.m_vecTextureV.x = vecTexCoords[0][1]; // m_Material.m_vecTextureU.x = vecTexCoord[1][0]; m_Material.m_vecTextureV.y = vecTexCoords[1][1]; m_Material.m_vecTextureU.y = vecTexCoords[2][0]; // m_Material.m_vecTextureV.y = vecTexCoord[2][1]; // m_Material.m_vecTextureU.y = vecTexCoord[3][0]; // m_Material.m_vecTextureV.x = vecTexCoord[3][1]; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::UpdateDispBarycentric( void ) { // // Project points back onto the displacement surface. // int nFaceCount = m_aRenderFaces.Count(); for ( int iFace = 0; iFace < nFaceCount; iFace++ ) { // Get the current face and remove it from the list. ClipFace_t *pClipFace = m_aRenderFaces[iFace]; if ( pClipFace ) { if ( pClipFace->m_pBuildFace->HasDisp() ) { ClipFace_BuildFacesFromBlendedData( pClipFace ); } } } // Update the entity position. CenterEntity(); // Update the handles. Handles_Build3D(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::CenterEntity( void ) { // Center in overlay plane. Vector vecTotal; Vector vecHandle; vecTotal.Init(); for( int iHandle = 0; iHandle < OVERLAY_HANDLES_COUNT; ++iHandle ) { OverlayUVToOverlayPlane( m_Handles.m_vecBasisCoords[iHandle], vecHandle ); vecTotal += vecHandle; } vecTotal *= 0.25f; // Center in overlay uv-space. Vector2D vecNewCenter; OverlayPlaneToOverlayUV( vecTotal, vecNewCenter ); for( int iHandle = 0; iHandle < OVERLAY_HANDLES_COUNT; ++iHandle ) { m_Handles.m_vecBasisCoords[iHandle] -= vecNewCenter; } // Update the entity's origin. m_Basis.m_vecOrigin = vecTotal; CMapEntity *pEntity = ( CMapEntity* )GetParent(); if ( pEntity ) { Vector vecSurfPoint; OverlayPlaneToSurfFromList( vecTotal, vecSurfPoint ); pEntity->SetOrigin( vecSurfPoint ); } // Update the property box. Basis_UpdateParentKey(); Handles_UpdateParentKey(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::GetPlane( cplane_t &plane ) { plane.normal = m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL]; plane.dist = plane.normal.Dot( m_Basis.m_vecOrigin ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::GetHandlePos( int iHandle, Vector &vecPos ) { Assert( iHandle >= 0 ); Assert( iHandle < 4 ); vecPos = m_Handles.m_vec3D[iHandle]; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::SideList_Init( CMapFace *pFace ) { // Valid face? if ( !pFace ) return; // Purge side list as this should be the initial face! m_Faces.Purge(); m_Faces.AddToTail( pFace ); if ( ( GetOverlayType() && OVERLAY_TYPE_SHORE ) == 0 ) { // Update dependencies. UpdateDependency( NULL, ( CMapSolid* )pFace->GetParent() ); UpdateParentKey(); } // Initialize the overlay. Basis_Init( pFace ); PostModified(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::SideList_AddFace( CMapFace *pFace ) { // Valid face? if ( !pFace ) return; // Purge side list as this should be the initial face! m_Faces.AddToTail( pFace ); if ( ( GetOverlayType() && OVERLAY_TYPE_SHORE ) == 0 ) { // Update dependencies. UpdateDependency( NULL, ( CMapSolid* )pFace->GetParent() ); UpdateParentKey(); } PostModified(); } //============================================================================= // // Overlay Utility Functions // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::OverlayUVToOverlayPlane( const Vector2D &vecUV, Vector &vecOverlayPoint ) { vecOverlayPoint = ( vecUV.x * m_Basis.m_vecAxes[OVERLAY_BASIS_U] + vecUV.y * m_Basis.m_vecAxes[OVERLAY_BASIS_V] ); vecOverlayPoint += m_Basis.m_vecOrigin; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::OverlayPlaneToOverlayUV( const Vector &vecOverlayPoint, Vector2D &vecUV ) { Vector vecDelta; vecDelta = vecOverlayPoint - m_Basis.m_vecOrigin; vecUV.x = m_Basis.m_vecAxes[OVERLAY_BASIS_U].Dot( vecDelta ); vecUV.y = m_Basis.m_vecAxes[OVERLAY_BASIS_V].Dot( vecDelta ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::WorldToOverlayPlane( const Vector &vecWorldPoint, Vector &vecOverlayPoint ) { Vector vecDelta = vecWorldPoint - m_Basis.m_vecOrigin; float flDist = m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL].Dot( vecDelta ); vecOverlayPoint = vecWorldPoint - ( m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL] * flDist ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::OverlayPlaneToWorld( CMapFace *pFace, const Vector &vecOverlayPoint, Vector &vecWorldPoint ) { // Create the overlay plane - the base face plane. cplane_t surfacePlane; pFace->GetFaceNormal( surfacePlane.normal ); VectorNormalize( surfacePlane.normal ); Vector vecPoint; pFace->GetPoint( vecPoint, 0 ); surfacePlane.dist = surfacePlane.normal.Dot( vecPoint ); float flDistToSurface = surfacePlane.normal.Dot( vecOverlayPoint ) - surfacePlane.dist; float flDist = flDistToSurface; float flDot = surfacePlane.normal.Dot( m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL] ); if ( flDot != 0.0f ) { flDist = ( 1.0f / flDot ) * flDistToSurface; } vecWorldPoint = vecOverlayPoint - ( m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL] * flDist ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CMapOverlay::OverlayPlaneToSurfFromList( const Vector &vecOverlayPoint, Vector &vecSurfPoint ) { // Initialize the point with the overlay point. vecSurfPoint = vecOverlayPoint; int nFaceCount = GetFaceCount(); CUtlVector aPoints; CUtlVector aPlanes; for ( int iFace = 0; iFace < nFaceCount; ++iFace ) { CMapFace *pFace = GetFace( iFace ); if ( !pFace ) continue; // Set points. aPoints.Purge(); aPoints.SetSize( pFace->nPoints ); aPlanes.Purge(); aPlanes.SetSize( pFace->nPoints ); // Project all the face points into the overlay plane. for ( int iPoint = 0; iPoint < pFace->nPoints; ++iPoint ) { WorldToOverlayPlane( pFace->Points[iPoint], aPoints[iPoint] ); } // Create edge planes for clipping. BuildEdgePlanes( aPoints.Base(), aPoints.Count(), aPlanes.Base(), aPlanes.Count() ); // Check to see if a point lies behind all of the edge planes - this is our face. int iPlane; for ( iPlane = 0; iPlane < aPlanes.Count(); ++iPlane ) { float flDist = aPlanes[iPlane].normal.Dot( vecOverlayPoint ) - aPlanes[iPlane].dist; if( flDist >= 0.0f ) break; } // Point lies outside off at least one plane. if( iPlane != aPlanes.Count() ) { continue; } // Project the point up to the base face plane (displacement if necessary). OverlayPlaneToWorld( pFace, vecOverlayPoint, vecSurfPoint ); if( pFace->HasDisp() ) { Vector2D vecTmp; EditDispHandle_t handle = pFace->GetDisp(); CMapDisp *pDisp = EditDispMgr()->GetDisp( handle ); pDisp->BaseFacePlaneToDispUV( vecSurfPoint, vecTmp ); pDisp->DispUVToSurf( vecTmp, vecSurfPoint, NULL, NULL ); } // Clean-up. aPoints.Purge(); aPlanes.Purge(); return; } // Clean-up. aPoints.Purge(); aPlanes.Purge(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CMapOverlay::EntityOnSurfFromListToBaseFacePlane( const Vector &vecWorldPoint, Vector &vecBasePoint ) { int nFaceCount = GetFaceCount(); for ( int iFace = 0; iFace < nFaceCount; ++iFace ) { CMapFace *pFace = GetFace( iFace ); if ( !pFace ) continue; if ( !pFace->HasDisp() ) continue; EditDispHandle_t handle = pFace->GetDisp(); CMapDisp *pDisp = EditDispMgr()->GetDisp( handle ); if ( pDisp->SurfToBaseFacePlane( vecWorldPoint, vecBasePoint ) ) return true; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::GetTriVerts( CMapDisp *pDisp, const Vector2D &vecSurfUV, int *pTris, Vector2D *pVertsUV ) { // Get the displacement width. int nWidth = pDisp->GetWidth(); int nHeight = pDisp->GetHeight(); // scale the u, v coordinates the displacement grid size float flU = vecSurfUV.x * ( nWidth - 1.000001f ); float flV = vecSurfUV.y * ( nHeight - 1.000001f ); // find the triangle the "uv spot" resides in int nSnapU = static_cast( flU ); int nSnapV = static_cast( flV ); if ( nSnapU == ( nWidth - 1 ) ) { --nSnapU; } if ( nSnapV == ( nHeight - 1 ) ) { --nSnapV; } int nNextU = nSnapU + 1; int nNextV = nSnapV + 1; // Fractional portion float flFracU = flU - static_cast( nSnapU ); float flFracV = flV - static_cast( nSnapV ); bool bOdd = ( ( ( nSnapV * nWidth ) + nSnapU ) % 2 ) == 1; if ( bOdd ) { if( ( flFracU + flFracV ) >= ( 1.0f + OVERLAY_DISPSPACE_EPSILON ) ) { pVertsUV[0].x = nSnapU; pVertsUV[0].y = nNextV; pVertsUV[1].x = nNextU; pVertsUV[1].y = nNextV; pVertsUV[2].x = nNextU; pVertsUV[2].y = nSnapV; } else { pVertsUV[0].x = nSnapU; pVertsUV[0].y = nSnapV; pVertsUV[1].x = nSnapU; pVertsUV[1].y = nNextV; pVertsUV[2].x = nNextU; pVertsUV[2].y = nSnapV; } } else { if ( flFracU < flFracV ) { pVertsUV[0].x = nSnapU; pVertsUV[0].y = nSnapV; pVertsUV[1].x = nSnapU; pVertsUV[1].y = nNextV; pVertsUV[2].x = nNextU; pVertsUV[2].y = nNextV; } else { pVertsUV[0].x = nSnapU; pVertsUV[0].y = nSnapV; pVertsUV[1].x = nNextU; pVertsUV[1].y = nNextV; pVertsUV[2].x = nNextU; pVertsUV[2].y = nSnapV; } } // Calculate the triangle indices. for( int iVert = 0; iVert < 3; ++iVert ) { pTris[iVert] = pVertsUV[iVert].y * nWidth + pVertsUV[iVert].x; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapOverlay::SetMaterial( const char *szMaterialName ) { // Get the new material. IEditorTexture *pTex = g_Textures.FindActiveTexture( szMaterialName ); if ( !pTex ) return; // Save the new material. m_Material.m_pTexture = pTex; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- ChunkFileResult_t CMapOverlay::SaveDataToVMF( CChunkFile *pFile, CSaveInfo *pSaveInfo ) { ChunkFileResult_t eResult = pFile->BeginChunk("overlaydata"); // Save the material name. if ( eResult == ChunkFile_Ok ) { eResult = pFile->WriteKeyValue( "material", m_Material.m_pTexture->GetName() ); } // Save the u,v data. if ( eResult == ChunkFile_Ok ) { eResult = pFile->WriteKeyValueFloat( "StartU", m_Material.m_vecTextureU.x ); } if ( eResult == ChunkFile_Ok ) { eResult = pFile->WriteKeyValueFloat( "EndU", m_Material.m_vecTextureU.y ); } if ( eResult == ChunkFile_Ok ) { eResult = pFile->WriteKeyValueFloat( "StartV", m_Material.m_vecTextureV.x ); } if ( eResult == ChunkFile_Ok ) { eResult = pFile->WriteKeyValueFloat( "EndV", m_Material.m_vecTextureV.y ); } // Basis data. if ( eResult == ChunkFile_Ok ) { eResult = pFile->WriteKeyValueVector3( "BasisOrigin", m_Basis.m_vecOrigin ); } if ( eResult == ChunkFile_Ok ) { eResult = pFile->WriteKeyValueVector3( "BasisU", m_Basis.m_vecAxes[OVERLAY_BASIS_U] ); } if ( eResult == ChunkFile_Ok ) { eResult = pFile->WriteKeyValueVector3( "BasisV", m_Basis.m_vecAxes[OVERLAY_BASIS_V] ); } if ( eResult == ChunkFile_Ok ) { eResult = pFile->WriteKeyValueVector3( "BasisNormal", m_Basis.m_vecAxes[OVERLAY_BASIS_NORMAL] ); } if ( eResult == ChunkFile_Ok ) { Vector vecTmp( m_Handles.m_vecBasisCoords[0].x, m_Handles.m_vecBasisCoords[0].y, ( float )m_Basis.m_nAxesFlip[0] ); eResult = pFile->WriteKeyValueVector3( "uv0", vecTmp ); } if ( eResult == ChunkFile_Ok ) { Vector vecTmp( m_Handles.m_vecBasisCoords[1].x, m_Handles.m_vecBasisCoords[1].y, ( float )m_Basis.m_nAxesFlip[1] ); eResult = pFile->WriteKeyValueVector3( "uv1", vecTmp ); } if ( eResult == ChunkFile_Ok ) { Vector vecTmp( m_Handles.m_vecBasisCoords[2].x, m_Handles.m_vecBasisCoords[2].y, ( float )m_Basis.m_nAxesFlip[2] ); eResult = pFile->WriteKeyValueVector3( "uv2", vecTmp ); } if ( eResult == ChunkFile_Ok ) { Vector vecTmp( m_Handles.m_vecBasisCoords[3].x, m_Handles.m_vecBasisCoords[3].y, 0.0f ); eResult = pFile->WriteKeyValueVector3( "uv3", vecTmp ); } // Sidelist. if ( eResult == ChunkFile_Ok ) { char szSetValue[KEYVALUE_MAX_VALUE_LENGTH]; CMapWorld::FaceID_FaceListsToString( szSetValue, sizeof( szSetValue ), &m_Faces, NULL ); eResult = pFile->WriteKeyValue( "sides", szSetValue ); } if ( eResult == ChunkFile_Ok ) { eResult = pFile->EndChunk(); } return eResult; }