//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //==========================================================================// #include "paint.h" #include "materialsystem/imaterial.h" #include "pixelwriter.h" #include "gl_model_private.h" #include "gl_matsysiface.h" #include "dt_common.h" #include "keyvalues.h" #include // RandomFloat #include "collisionutils.h" // ray triangle test #include "cmodel_private.h" // debug stuff #include "debugoverlay.h" #include "con_nprint.h" // src/public/ #include "game/shared/portal2/paint_enum.h" #include "tier1/callqueue.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" extern ConVar r_redownloadallpaintmaps; static const BYTE NUM_ALPHA_BITS = 5; static const BYTE PAINT_COLOR_BITS = 7 << NUM_ALPHA_BITS; // 224 static const BYTE PAINT_ALPHA_BITS = PAINT_COLOR_BITS ^ 0xFF; // 31 ConVar paint_max_surface_border_alpha("paint_max_surface_border_alpha", "0.7f", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED ); ConVar paint_alpha_offset_enabled("paint_alpha_offset_enabled", "1", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED ); ConVar paintsplat_bias("paintsplat_bias", "0.1f", FCVAR_REPLICATED | FCVAR_CHEAT, "Change bias value for computing circle buffer" ); ConVar paintsplat_noise_enabled("paintsplat_noise_enabled", "1", FCVAR_REPLICATED | FCVAR_CHEAT ); ConVar paintsplat_max_alpha_noise("paintsplat_max_alpha_noise", "0.1f", FCVAR_REPLICATED | FCVAR_CHEAT, "Max noise value of circle alpha" ); ConVar paint_min_valid_alpha_value("paint_min_valid_alpha_value", "0.7f", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY ); ConVar debug_paint_alpha("debug_paint_alpha", "0", FCVAR_DEVELOPMENTONLY ); int GetDiameter( int radius ) { return radius * 2 + 1; } BYTE GetColorIndex( BYTE byte ) { return ( PAINT_COLOR_BITS & byte ) >> NUM_ALPHA_BITS; } void SetColorBits( BYTE& byte, BYTE colorIndex, float alpha ) { BYTE nAlpha = static_cast< BYTE >( alpha * PAINT_ALPHA_BITS ); BYTE nColor = colorIndex << NUM_ALPHA_BITS; byte = nColor | nAlpha; } float GetAlpha( BYTE byte ) { float alpha = ( PAINT_ALPHA_BITS & byte ); alpha /= PAINT_ALPHA_BITS; alpha = clamp( alpha, 0.f, 1.f ); return alpha; } #if !defined( LINUX ) // draw a surface in random color void DebugDrawSurface( SurfaceHandle_t surfID ) { Color surfColor = Color( RandomInt(0, 255), RandomInt(0, 255), RandomInt(0, 255), 128 ); // check if the sphere actually intersecting with the surface using barycentric test int nFirstVertex = MSurf_FirstVertIndex( surfID ); int numVert = MSurf_VertCount( surfID ); int vertIndex = host_state.worldbrush->vertindices[nFirstVertex]; Vector vOrigin = host_state.worldbrush->vertexes[vertIndex].position; for (int v = 1; v < numVert - 1; ++v ) { vertIndex = host_state.worldbrush->vertindices[nFirstVertex+v]; Vector v1 = host_state.worldbrush->vertexes[vertIndex].position; vertIndex = host_state.worldbrush->vertindices[nFirstVertex+v+1]; Vector v2 = host_state.worldbrush->vertexes[vertIndex].position; CDebugOverlay::AddTriangleOverlay( vOrigin, v1, v2, surfColor.r(), surfColor.g(), surfColor.b(), 128, false, 0.1f ); } } #endif extern MaterialSystem_SortInfo_t *materialSortInfoArray; CPaintmapDataManager g_PaintManager; CPaintmapDataManager::CPaintmapDataManager( void ) : m_pPaintTextureDataArray( NULL ), m_iPaintmaps( 0 ), m_bShouldRegister( false ) { } CPaintmapDataManager::~CPaintmapDataManager( void ) { } void CPaintmapDataManager::DestroyPaintmapsData( void ) { if( m_pPaintTextureDataArray ) { for( int i = 0; i != m_iPaintmaps; ++i ) { m_pPaintTextureDataArray[i].Destroy(); } delete []m_pPaintTextureDataArray; m_pPaintTextureDataArray = NULL; m_iPaintmaps = 0; } } void R_UpdatePaintmapRect( int paintmap, BYTE* pPaintData, int numRects, Rect_t* pRects ) { materials->UpdatePaintmap( paintmap, pPaintData, numRects, pRects ); } void R_UpdatePaintmap( ICallQueue *pCallQueue, int paintmap, BYTE* pPaintData, int numRects, Rect_t* pRects ) { if ( !pCallQueue ) { R_UpdatePaintmapRect( paintmap, pPaintData, numRects, pRects ); } else { pCallQueue->QueueCall( R_UpdatePaintmapRect, paintmap, pPaintData, numRects, CUtlEnvelope( pRects, numRects ) ); } } void MarkSurfaceBrushes( int nSurfIndex, worldbrushdata_t *pData = host_state.worldbrush ) { if ( !pData->m_pSurfaceBrushList ) return; const dfacebrushlist_t &brushList = pData->m_pSurfaceBrushList[nSurfIndex]; const uint16 *pBrushIndex = (brushList.m_nFaceBrushCount <= 1) ? &brushList.m_nFaceBrushStart : &pData->m_pSurfaceBrushes[brushList.m_nFaceBrushStart]; CCollisionBSPData *pBSPData = GetCollisionBSPData(); for ( int i = 0; i < brushList.m_nFaceBrushCount; i++ ) { pBSPData->map_brushes[ pBrushIndex[i] ].contents |= CONTENTS_BRUSH_PAINT; } } void CPaintmapDataManager::UpdatePaintmapTextures() { // Can't build lightmaps if the source data has been dumped CMatRenderContextPtr pRenderContext( materials ); ICallQueue *pCallQueue = pRenderContext->GetCallQueue(); if( pCallQueue ) pCallQueue->QueueCall( materials, &IMaterialSystem::BeginUpdatePaintmaps ); else materials->BeginUpdatePaintmaps(); for( int paintmap=0; paintmap < m_iPaintmaps; ++paintmap ) { PaintDirtyFlags_t nDirtyFlag = m_pPaintTextureDataArray[paintmap].GetDirtyFlag(); if ( nDirtyFlag == PAINTMAP_CLEAN ) continue; if ( nDirtyFlag == PAINTMAP_DIRTY_FULLRECT ) { R_UpdatePaintmap( pCallQueue, paintmap, m_pPaintTextureDataArray[paintmap].GetPaintmapData(), 0, NULL ); } else { CUtlVectorFixedGrowable *pRects = m_pPaintTextureDataArray[paintmap].GetDirtyRectList(); int count = pRects->Count(); Assert( count > 0 ); R_UpdatePaintmap( pCallQueue, paintmap, m_pPaintTextureDataArray[paintmap].GetPaintmapData(), count, pRects->Base() ); } m_pPaintTextureDataArray[paintmap].RemoveDirty(); } if( pCallQueue ) pCallQueue->QueueCall( materials, &IMaterialSystem::EndUpdatePaintmaps ); else materials->EndUpdatePaintmaps(); } BYTE* CPaintmapDataManager::GetPaintmapData( int paintmap ) { if ( paintmap >= 0 && paintmap < m_iPaintmaps ) { return m_pPaintTextureDataArray[paintmap].GetPaintmapData(); } return NULL; } void CPaintmapDataManager::GetPaintmapSize( int paintmap, int& width, int& height ) { if ( paintmap >= 0 && paintmap < m_iPaintmaps ) { m_pPaintTextureDataArray[paintmap].GetPaintSize( &width, &height ); } } void CPaintmapDataManager::OnRestorePaintmaps() { for ( int i=0; inumbrushes; cbrush_t *pBrush = GetCollisionBSPData()->map_brushes.Base(); for ( int i = 0; i < nBrushCount; i++ ) { pBrush[i].contents &= ~CONTENTS_BRUSH_PAINT; } } void CPaintmapDataManager::RemovePaint( const model_t *pModel ) { Assert( pModel ); if ( !pModel ) return; Assert( m_pPaintTextureDataArray ); if ( !m_pPaintTextureDataArray ) return; BYTE noColor; SetColorBits( noColor, NO_POWER, 0.f ); for ( int i=0; ibrush.nummodelsurfaces; ++i ) { SurfaceHandle_t surfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface + i ); int lightmapID = materialSortInfoArray[MSurf_MaterialSortID( surfID )].lightmapPageID; CPaintTextureData& paintTexture = m_pPaintTextureDataArray[ lightmapID ]; Rect_t rect; rect.x = MSurf_OffsetIntoLightmapPage( surfID )[0]; rect.y = MSurf_OffsetIntoLightmapPage( surfID )[1]; rect.width = MSurf_LightmapExtents( surfID )[0] + 1; rect.height= MSurf_LightmapExtents( surfID )[1] + 1; for ( int y=0; ynumsurfaces; for ( int i=0; inumbrushes; cbrush_t *pBrush = GetCollisionBSPData()->map_brushes.Base(); for ( int i = 0; i < nBrushCount; i++ ) { pBrush[i].contents |= CONTENTS_BRUSH_PAINT; } } void CPaintmapDataManager::BeginPaintmapsDataAllocation( int iPaintmapCount ) { DestroyPaintmapsData(); m_iPaintmaps = iPaintmapCount; m_pPaintTextureDataArray = new CPaintTextureData[iPaintmapCount]; } void CPaintmapDataManager::AllocatePaintmapData( int iPaintmapID, int iCorrespondingLightMapWidth, int iCorrespondingLightMapHeight ) { m_pPaintTextureDataArray[iPaintmapID].Init( iCorrespondingLightMapWidth, iCorrespondingLightMapHeight, iPaintmapID ); } CPaintTextureData::CPaintTextureData() { m_nPaintWidth = m_nPaintHeight = 0; m_backbuffer = NULL; m_nDirtyFlag = PAINTMAP_CLEAN; } // Initializes, shuts down the material bool CPaintTextureData::Init( int width, int height, int lightmapPageID ) { m_nPaintWidth = width; m_nPaintHeight = height; m_lightmapPageID = lightmapPageID; m_backbuffer = new BYTE[ m_nPaintWidth * m_nPaintHeight ]; AssertMsg( m_backbuffer, "Failed to allocate paint texture data array" ); ClearBuffer(); return true; } void CPaintTextureData::Destroy() { if ( m_backbuffer ) { delete[] m_backbuffer; m_backbuffer = NULL; } } BYTE CPaintTextureData::GetPixel( int x, int y ) const { return m_backbuffer[ y * m_nPaintWidth + x ]; } void CPaintTextureData::SetPixel( int x, int y, BYTE color ) { m_backbuffer[ y * m_nPaintWidth + x ] = color; } bool CPaintTextureData::Paint( const PaintRect_t& paintRect ) { uint32 nChangeFlags = DrawCircle( paintRect ); if ( nChangeFlags & TEXEL_CHANGED ) { // no need for extra alpha if we are erasing if ( paint_alpha_offset_enabled.GetBool() && paintRect.colorIndex != NO_POWER ) { Rect_t rect = paintRect.rect; int sOffset = MSurf_OffsetIntoLightmapPage( paintRect.surfID )[0]; int tOffset = MSurf_OffsetIntoLightmapPage( paintRect.surfID )[1]; int sMax = ( MSurf_LightmapExtents( paintRect.surfID )[0] ); int tMax = ( MSurf_LightmapExtents( paintRect.surfID )[1] ); if ( paintRect.rect.x > sOffset ) { --rect.x; ++rect.width; } if ( paintRect.rect.y > tOffset ) { --rect.y; ++rect.height; } if ( paintRect.rect.x + paintRect.rect.width - 1 < sOffset + sMax ) { ++rect.width; } if ( paintRect.rect.y + paintRect.rect.height - 1 < tOffset + tMax ) { ++rect.height; } AddDirtyRect( rect ); } else { AddDirtyRect( paintRect.rect ); } } return ( nChangeFlags & PAINT_POWER_CHANGED ) != 0; } BYTE BlendColor( BYTE colorIndex, BYTE nPrePixel, float flAlpha, float flPaintCoatPercent, float flMaxAlpha ) { BYTE finalColor; // stomp erase color if ( colorIndex == NO_POWER ) { // if it's erase, the alpha is always 0.f or 1.f (0.f for surrounding edges) SetColorBits( finalColor, paint_alpha_offset_enabled.GetBool() ? GetColorIndex( nPrePixel ) : colorIndex, 0.f ); } else { float bAlpha = GetAlpha( nPrePixel ); float flNewAlpha = clamp( bAlpha + ( flPaintCoatPercent * flAlpha ), 0.f, flMaxAlpha ); SetColorBits( finalColor, colorIndex, flNewAlpha ); } return finalColor; } float ComputeCircleAlpha( const PaintRect_t& paintRect, int x, int y ) { float flPixelDist = Vector2D( x - paintRect.uvCenter.x, y - paintRect.uvCenter.y ).Length(); float flRadiusRatio = clamp( paintRect.flCenterAlpha + flPixelDist / paintRect.flCircleRadius, 0.f, 1.f ); float flAlpha = 1.f - Bias( flRadiusRatio, paintsplat_bias.GetFloat() ); if ( paintsplat_noise_enabled.GetBool() ) { float flNoise = paintsplat_max_alpha_noise.GetFloat(); flAlpha += RandomFloat( -flNoise, flNoise ); flAlpha = clamp( flAlpha, 0.f, 1.f ); } return flAlpha; } uint32 CPaintTextureData::BlendLuxel( const PaintRect_t& paintRect, int x, int y, float flNewAlpha, float flMaxAlpha /*= 1.f*/ ) { uint32 nChangeFlags = 0; BYTE nPrePixel = GetPixel( x, y ); BYTE nPostPixel = BlendColor( paintRect.colorIndex, nPrePixel, flNewAlpha, paintRect.flPaintCoatPercent, flMaxAlpha ); if ( nPrePixel != nPostPixel ) { SetPixel( x, y, nPostPixel ); nChangeFlags |= TEXEL_CHANGED; if ( GetColorIndex( nPrePixel ) != GetColorIndex( nPostPixel ) ) { nChangeFlags |= PAINT_POWER_CHANGED; } } return nChangeFlags; } uint32 CPaintTextureData::AddSurroundingAlpha( const PaintRect_t& paintRect, int x, int y ) { SurfaceHandle_t surfID = paintRect.surfID; int surroundCase = 0; int sOffset = MSurf_OffsetIntoLightmapPage( surfID )[0]; int tOffset = MSurf_OffsetIntoLightmapPage( surfID )[1]; int sMax = ( MSurf_LightmapExtents( surfID )[0] ); int tMax = ( MSurf_LightmapExtents( surfID )[1] ); surroundCase |= ( x > sOffset ) ? 1 : 0; // left surroundCase |= ( x < sOffset + sMax ) ? 2 : 0; // right surroundCase |= ( y > tOffset ) ? 4 : 0; // up surroundCase |= ( y < tOffset + tMax ) ? 8 : 0; // down uint32 nChangeFlags = 0; switch ( surroundCase ) { case 1: // left only { nChangeFlags |= BlendLuxel( paintRect, x - 1, y, 0.f ); break; } case 2: // right only { nChangeFlags |= BlendLuxel( paintRect, x + 1, y, 0.f ); break; } case 3: // left right { nChangeFlags |= BlendLuxel( paintRect, x - 1, y, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x + 1, y, 0.f ); break; } case 4: // up only { nChangeFlags |= BlendLuxel( paintRect, x, y - 1, 0.f ); break; } case 5: // left up { nChangeFlags |= BlendLuxel( paintRect, x - 1, y, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x - 1, y - 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x, y - 1, 0.f ); break; } case 6: // right up { nChangeFlags |= BlendLuxel( paintRect, x + 1, y, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x + 1, y - 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x, y - 1, 0.f ); break; } case 7: // left up right { nChangeFlags |= BlendLuxel( paintRect, x - 1, y, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x - 1, y - 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x, y - 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x + 1, y - 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x + 1, y, 0.f ); break; } case 8: // down only { nChangeFlags |= BlendLuxel( paintRect, x, y + 1, 0.f ); break; } case 9: // left down { nChangeFlags |= BlendLuxel( paintRect, x - 1, y, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x - 1, y + 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x, y + 1, 0.f ); break; } case 10: // right down { nChangeFlags |= BlendLuxel( paintRect, x + 1, y, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x + 1, y + 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x, y + 1, 0.f ); break; } case 11: // left down right { nChangeFlags |= BlendLuxel( paintRect, x - 1, y, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x - 1, y + 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x, y + 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x + 1, y + 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x + 1, y, 0.f ); break; } case 12: // up down { nChangeFlags |= BlendLuxel( paintRect, x, y - 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x, y + 1, 0.f ); break; } case 13: // up left down { nChangeFlags |= BlendLuxel( paintRect, x, y - 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x - 1, y - 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x - 1, y, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x - 1, y + 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x, y + 1, 0.f ); break; } case 14: // up right down { nChangeFlags |= BlendLuxel( paintRect, x, y - 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x + 1, y - 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x + 1, y, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x + 1, y + 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x, y + 1, 0.f ); break; } case 15: // all 8 surrounding luxels { nChangeFlags |= BlendLuxel( paintRect, x, y - 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x - 1, y - 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x - 1, y, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x - 1, y + 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x + 1, y - 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x + 1, y, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x + 1, y + 1, 0.f ); nChangeFlags |= BlendLuxel( paintRect, x, y + 1, 0.f ); break; } default: break; } return nChangeFlags; } uint32 CPaintTextureData::DrawLine( const PaintRect_t& paintRect, int x1, int x2, int y ) { uint32 nChangeFlags = 0; int sOffset = MSurf_OffsetIntoLightmapPage( paintRect.surfID )[0]; int tOffset = MSurf_OffsetIntoLightmapPage( paintRect.surfID )[1]; int sMax = ( MSurf_LightmapExtents( paintRect.surfID )[0] ); int tMax = ( MSurf_LightmapExtents( paintRect.surfID )[1] ); int start = x1; int end = x2; // clamp border luxel alpha below paint_max_surface_border_alpha threshold so the shader won't look up color from other surface border float flMaxAlpha = 1.f; if ( y == tOffset || y == tOffset + tMax ) { flMaxAlpha = paint_max_surface_border_alpha.GetFloat(); } else { if ( x1 == sOffset ) { float flAlpha = ( paintRect.colorIndex == NO_POWER ) ? 1.f : ComputeCircleAlpha( paintRect, x1, y ); nChangeFlags |= BlendLuxel( paintRect, x1, y, flAlpha, paint_max_surface_border_alpha.GetFloat() ); ++start; } if ( x2 == sOffset + sMax ) { float flAlpha = ( paintRect.colorIndex == NO_POWER ) ? 1.f : ComputeCircleAlpha( paintRect, x2, y ); nChangeFlags |= BlendLuxel( paintRect, x2, y, flAlpha, paint_max_surface_border_alpha.GetFloat() ); --end; } } for ( int x = start; x <= end; ++x ) { float flAlpha = ( paintRect.colorIndex == NO_POWER ) ? 1.f : ComputeCircleAlpha( paintRect, x, y ); nChangeFlags |= BlendLuxel( paintRect, x, y, flAlpha, flMaxAlpha ); } // don't add surrounding alpha if we are erasing if ( paint_alpha_offset_enabled.GetBool() && paintRect.colorIndex != NO_POWER ) { if ( x1 == x2 ) { nChangeFlags |= AddSurroundingAlpha( paintRect, x1, y ); } else { nChangeFlags |= AddSurroundingAlpha( paintRect, x1, y ); nChangeFlags |= AddSurroundingAlpha( paintRect, x2, y ); } } return nChangeFlags; } uint32 CPaintTextureData::Draw2Lines( const PaintRect_t& paintRect, float x, float y ) { const Rect_t& rect = paintRect.rect; int minX = rect.x; int minY = rect.y; int maxX = rect.x + rect.width - 1; int maxY = rect.y + rect.height - 1; const Vector2D& uvCenter = paintRect.uvCenter; int x1 = MAX( ( int )( uvCenter.x - x - 0.5f ), minX ); int y1 = MAX( ( int )( uvCenter.y - y - 0.5f ), minY ); int x2 = MIN( ( int )( uvCenter.x + x + 0.5f ), maxX ); int y2 = MIN( ( int )( uvCenter.y + y + 0.5f ), maxY ); // if line is outside the rect, don't do anything if ( x1 > maxX || x2 < minX ) return 0; uint32 nChangeFlags = 0; // draw line1 if ( minY <= y1 && y1 <= maxY ) { nChangeFlags |= DrawLine( paintRect, x1, x2, y1 ); } if ( y1 != y2 ) { if ( minY <= y2 && y2 <= maxY ) { nChangeFlags |= DrawLine( paintRect, x1, x2, y2 ); } } return nChangeFlags; } uint32 CPaintTextureData::Draw4Lines( const PaintRect_t& paintRect, float x, float y ) { uint32 nChangeFlags = 0; nChangeFlags |= Draw2Lines( paintRect, x, y ); if ( x != y ) { nChangeFlags |= Draw2Lines( paintRect, y, x ); } return nChangeFlags; } uint32 CPaintTextureData::DrawCircle( const PaintRect_t& paintRect ) { // assume uvExtents x and y are about the same float radius = paintRect.flCircleRadius; float error = -radius; float x = radius; float y = 0; uint32 nChangeFlags = 0; while (x >= y) { nChangeFlags |= Draw4Lines( paintRect, x, y ); error += y; ++y; error += y; if (error >= 0) { --x; error -= x; error -= x; } } return nChangeFlags; } // Returns the tecxoord range void CPaintTextureData::GetTexCoordRange( float *pMaxU, float *pMaxV ) { *pMaxU = *pMaxV = 1.0f; return; } // Returns the size of the paint texture (stored in a subrect of the material itself) void CPaintTextureData::GetPaintSize( int *pWidth, int *pHeight ) { *pWidth = m_nPaintWidth; *pHeight = m_nPaintHeight; } void CPaintTextureData::ClearTexture() { ClearBuffer(); MarkAsDirty(); } void CPaintTextureData::GetPixels( const Rect_t& splatRect, CUtlVector& surfColors ) { //sample color inside rect for (int y = 0; y < splatRect.height; ++y ) { for (int x = 0; x < splatRect.width; ++x ) { BYTE packedColor = GetPixel( splatRect.x + x, splatRect.y + y ); BYTE colorIndex = GetColorIndex( packedColor ); float flAlpha = GetAlpha( packedColor ); if( flAlpha > paint_min_valid_alpha_value.GetFloat() ) surfColors.AddToTail( colorIndex ); if( debug_paint_alpha.GetBool() ) { Con_NPrintf( y * splatRect.width + x, "(%d, %d), Alpha: %f\n", x, y, flAlpha ); } } } } void CPaintTextureData::ClearBuffer( BYTE *pByte ) { BYTE nPixel = ( pByte ) ? *pByte : NO_POWER << NUM_ALPHA_BITS; V_memset( m_backbuffer, nPixel, m_nPaintWidth * m_nPaintHeight ); } // paint all surfaces void CPaintTextureData::PaintAllSurfaces( BYTE colorIndex ) { BYTE nPixel; SetColorBits( nPixel, colorIndex, 1.f ); ClearBuffer( &nPixel ); MarkAsDirty(); } void CPaintTextureData::GetSurfacePaintData( SurfaceHandle_t surfID, CUtlVector< BYTE > &data ) const { if ( !m_backbuffer ) return; short x = MSurf_OffsetIntoLightmapPage( surfID )[0]; short y = MSurf_OffsetIntoLightmapPage( surfID )[1]; short width = MSurf_LightmapExtents( surfID )[0] + 1; short height = MSurf_LightmapExtents( surfID )[1] + 1; int nDataCount = width * height; // make sure alloc number is divisible by 4 uint nAllocSize = nDataCount + ( ( 4 - ( nDataCount % 4 ) ) % 4 ); Assert( ( nAllocSize % 4 ) == 0 ); data.EnsureCount( nAllocSize ); BYTE* pOut = data.Base(); const BYTE* pEnd = data.Base() + nDataCount; const BYTE* pRead = &m_backbuffer[ y * m_nPaintWidth + x ]; while( pOut < pEnd ) { V_memcpy( pOut, pRead, width ); pOut += width; pRead += m_nPaintWidth; } Assert( pOut == pEnd ); } void CPaintTextureData::SetSurfacePaintData( SurfaceHandle_t surfID, const CUtlVector< BYTE > &data ) { if ( !m_backbuffer ) return; Rect_t rect; rect.x = MSurf_OffsetIntoLightmapPage( surfID )[0]; rect.y = MSurf_OffsetIntoLightmapPage( surfID )[1]; rect.width = MSurf_LightmapExtents( surfID )[0] + 1; rect.height = MSurf_LightmapExtents( surfID )[1] + 1; uint nDataCount = rect.width * rect.height; const BYTE* pRead = data.Base(); const BYTE* pEnd = data.Base() + nDataCount; BYTE *pSet = &m_backbuffer[ rect.y * m_nPaintWidth + rect.x ]; while ( pRead < pEnd ) { V_memcpy( pSet, pRead, rect.width ); pRead += rect.width; pSet += m_nPaintWidth; } AddDirtyRect( rect ); // mark surf as painted MSurf_Flags( surfID ) |= SURFDRAW_PAINTED; MarkSurfaceBrushes( MSurf_Index(surfID) ); } void EncodeDataRLE( const uint32* pBuffer, uint32 nDwordCount, CUtlVector< uint32 > &data ) { if ( nDwordCount == 0 ) return; // first build a list of runs into a local utlvector CUtlVectorFixedGrowable outList; // this is the start of the run of unique data to copy const uint32 *pCopyStart = pBuffer; // we need to find at least a run of 3 in order for encoding a run to pay for itself, keep last two uint32 nSymbol0 = *pCopyStart; uint32 nSymbol1 = pCopyStart[1]; const uint32 *pEndOfData = pCopyStart + nDwordCount; // start here with the first two loaded up and ready to copy const uint32 *pCurrent = pCopyStart + 2; // this is the size in dwords of the rle output buffer uint32 nRLESize = 0; // anything that defines _GAMECONSOLE should support prefetches #if defined(_GAMECONSOLE) // prefetch the first 7 cache lines for ( int i = 0; i < 7; i++ ) { PREFETCH_128( pCurrent + (i * 32), 0 ); } #endif // this is the next read that will trigger a prefetch (not every read should prefetch) const uint32 *pNextPrefetch = pCurrent + 32; // this is the end of the space we'd want to prefetch ahead of const uint32 *pLastPrefetch = pEndOfData - (8 * 32); // this is how far ahead of the current read to prefetch #if defined(_GAMECONSOLE) const uint32 nPrefetchOffsetDwords = 7 * 32; // will cause an error if your platform has prefetch but not _GAMECONSOLE #endif while ( pCurrent < pEndOfData ) { uint32 nSymbol2 = *pCurrent; if ( nSymbol0 == nSymbol1 && nSymbol1 == nSymbol2 ) { // found a run const uint32 *pRunStart = pCurrent - 2; int nCopyOut = pRunStart - pCopyStart; // copy any previous run of non-similar values if ( nCopyOut ) { // note, we're building a list of copies, so just add a record outList.AddToTail( nCopyOut ); outList.AddToTail( intp(pCopyStart) ); nRLESize += nCopyOut + 1; pCopyStart = pRunStart; } // follow current run until its end pCurrent++; while ( pCurrent < pEndOfData && *pCurrent == nSymbol0 ) { pCurrent++; if ( pCurrent >= pNextPrefetch && pNextPrefetch < pLastPrefetch ) { // schedule prefetches 128 bytes (32 dwords) apart PREFETCH_128( pNextPrefetch + nPrefetchOffsetDwords, 0 ); pNextPrefetch += 32; } } // write out the run int nRunCount = pCurrent - pRunStart; outList.AddToTail( -nRunCount ); outList.AddToTail( nSymbol0 ); nRLESize += 2; if ( pCurrent < pEndOfData ) { // restart run detection nSymbol2 = *pCurrent; nSymbol1 = nSymbol2+1; // make these not equal so a run will not be detected } pCopyStart = pCurrent; } // advance read cursor and shift over previous run-detect state pCurrent++; nSymbol0 = nSymbol1; nSymbol1 = nSymbol2; if ( pCurrent >= pNextPrefetch && pNextPrefetch < pLastPrefetch ) { PREFETCH_128( pNextPrefetch + nPrefetchOffsetDwords, 0 ); pNextPrefetch += 32; } } int nCopyOut = pEndOfData - pCopyStart; if ( nCopyOut ) { // add another record if the map ends in a copy outList.AddToTail( nCopyOut ); outList.AddToTail( (intp)pCopyStart ); nRLESize += nCopyOut + 1; pCopyStart = pEndOfData; } // now build the full RLE output from the records we just made // we can allocate once because we know the output size data.SetCount( nRLESize ); uint32 *pOut = data.Base(); const intp *pRLE = outList.Base(); const intp *pEndRLE = pRLE + outList.Count(); while ( pRLE < pEndRLE ) { int nCount = *pRLE++; if ( nCount < 0 ) { // run, insert in output *pOut++ = uint32( nCount ); *pOut++ = uint32( *pRLE++ ); } else { // copy, do the actual copy now *pOut++ = uint32( nCount ); const uint32 *pSource = (const uint32 *)*pRLE++; for ( int i = 0; i < nCount; i++ ) { *pOut++ = *pSource++; } } } // make sure our buffer contains exactly what the records predicted Assert( (uint32)(pOut - data.Base()) == nRLESize ); } void CPaintmapDataManager::GetPaintmapDataRLE( CUtlVector< uint32 > &data ) const { for ( int i=0; i< host_state.worldbrush->numsurfaces; ++i ) { SurfaceHandle_t surfID = SurfaceHandleFromIndex( i ); if ( MSurf_Flags( surfID ) & SURFDRAW_PAINTED ) { int lightmapID = materialSortInfoArray[MSurf_MaterialSortID( surfID )].lightmapPageID; // get paint data from a surface CUtlVector< BYTE > surfPaintData; m_pPaintTextureDataArray[lightmapID].GetSurfacePaintData( surfID, surfPaintData ); // encode into RLE format CUtlVector< uint32 > rleData; uint nDwordCount = surfPaintData.Count() / 4; EncodeDataRLE( (uint32*)surfPaintData.Base(), nDwordCount, rleData ); // write surface index, RLE size, and RLE data to output data data.AddToTail( i ); data.AddToTail( rleData.Count() ); data.AddMultipleToTail( rleData.Count(), rleData.Base() ); } } } void DecodeDataRLE( const uint32* pRLEStart, uint rleCount, SurfaceHandle_t surfID, uint32* pOutput, uint nDwordCount ) { // NOTE: This assumes the size is always correct, will assert if not const uint32 *pRLE = pRLEStart; const uint32 *pEndOfData = pRLE + rleCount; #if _DEBUG const uint32* pOutStart = pOutput; #endif while ( pRLE < pEndOfData ) { int32 nCount = int32(*pRLE++); if ( nCount < 0 ) // run, output repeated value nCount times { nCount = -nCount; uint32 nRunVal = *pRLE++; for ( int i = 0; i < nCount; i++ ) { *pOutput++ = nRunVal; } } else // copy raw dwords from input { for ( int i = 0; i < nCount; i++ ) { *pOutput++ = *pRLE++; } } } Assert( pRLE == pEndOfData ); Assert( pOutput - pOutStart == nDwordCount ); } void CPaintmapDataManager::LoadPaintmapDataRLE( const CUtlVector< uint32 > &data ) { const uint32* pCurrent = data.Base(); const uint32* pEnd = pCurrent + data.Count(); while ( pCurrent < pEnd ) { // assumes that the surface index is always the same (it'll change if the map's changed) int surfIndex = *pCurrent++; SurfaceHandle_t surfID = SurfaceHandleFromIndex( surfIndex ); int lightmapID = materialSortInfoArray[MSurf_MaterialSortID( surfID )].lightmapPageID; uint nRLESize = *pCurrent++; // alloc output. make sure the size is divisible by 4 to copy data from array of uint32 CUtlVector< BYTE > rawPaintData; int width = MSurf_LightmapExtents( surfID )[0] + 1; int height = MSurf_LightmapExtents( surfID )[1] + 1; int nByteCount = width * height; uint nAllocSize = nByteCount + ( ( 4 - ( nByteCount % 4 ) ) % 4 ); Assert( ( nAllocSize % 4 ) == 0 ); rawPaintData.SetCount( nAllocSize ); uint nDWord = nAllocSize / 4; DecodeDataRLE( pCurrent, nRLESize, surfID, (uint32*)rawPaintData.Base(), nDWord ); pCurrent += nRLESize; m_pPaintTextureDataArray[lightmapID].SetSurfacePaintData( surfID, rawPaintData ); } } void CPaintTextureData::AddDirtyRect( const Rect_t& rect ) { MarkAsDirty( PAINTMAP_DIRTY_SUBRECT ); m_dirtyRects.AddToTail( rect ); } CUtlVectorFixedGrowable* CPaintTextureData::GetDirtyRectList() { return &m_dirtyRects; } void CPaintTextureData::RemoveDirty() { m_nDirtyFlag = PAINTMAP_CLEAN; m_dirtyRects.RemoveAll(); } PaintDirtyFlags_t CPaintTextureData::GetDirtyFlag() const { return m_nDirtyFlag; } void CPaintTextureData::MarkAsDirty( PaintDirtyFlags_t nDirtyFlag /*= PAINTMAP_DIRTY_FULLRECT*/ ) { m_nDirtyFlag = ( m_nDirtyFlag == PAINTMAP_DIRTY_FULLRECT ) ? m_nDirtyFlag : nDirtyFlag; } void ProjectPointOntoSurfaceTexture( const SurfaceCtx_t& ctx, SurfaceHandle_t surfID, const Vector& vPoint, Vector2D& uv ) { mtexinfo_t* pTexInfo = MSurf_TexInfo( surfID ); uv.x = DotProduct ( vPoint, pTexInfo->lightmapVecsLuxelsPerWorldUnits[0].AsVector3D()) + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][3]; uv.x -= MSurf_LightmapMins( surfID )[0]; uv.x += 0.5f; uv.y = DotProduct ( vPoint, pTexInfo->lightmapVecsLuxelsPerWorldUnits[1].AsVector3D()) + pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][3]; uv.y -= MSurf_LightmapMins( surfID )[1]; uv.y += 0.5f; uv *= ctx.m_Scale; uv += ctx.m_Offset; // convert back to the old space uv /= ctx.m_Scale; } bool ComputePaintRect( SurfaceHandle_t surfID, const Vector &vPosition, float flSphereRadius, PaintRect_t& paintRect ) { #if !defined( LINUX ) // find dist from plane VPlane forwardFacingPlane = MSurf_GetForwardFacingPlane( surfID ); float distFromPlane = forwardFacingPlane.DistTo( vPosition ); AssertMsg( distFromPlane < flSphereRadius, "How did this surface intersect with the query sphere?" ); float circleRadius = FastSqrt( flSphereRadius * flSphereRadius - distFromPlane * distFromPlane ); SurfaceCtx_t ctx; SurfSetupSurfaceContext( ctx, surfID ); Vector2D uvCenter, uvExtents; { ProjectPointOntoSurfaceTexture( ctx, surfID, vPosition, uvCenter ); mtexinfo_t *pTexInfo = MSurf_TexInfo( surfID ); uvExtents.x = circleRadius * pTexInfo->lightmapVecsLuxelsPerWorldUnits[0].AsVector3D().Length(); uvExtents.y = circleRadius * pTexInfo->lightmapVecsLuxelsPerWorldUnits[1].AsVector3D().Length(); } Assert( uvExtents.x > 0 && uvExtents.y > 0 ); Vector2D uvMins, uvMaxs; uvMins = uvCenter - uvExtents; uvMaxs = uvCenter + uvExtents; int sOffset = MSurf_OffsetIntoLightmapPage( surfID )[0]; int tOffset = MSurf_OffsetIntoLightmapPage( surfID )[1]; int sMax = ( MSurf_LightmapExtents( surfID )[0] ); int tMax = ( MSurf_LightmapExtents( surfID )[1] ); if ( ( sOffset <= uvMaxs.x && uvMins.x <= sOffset + sMax ) && ( tOffset <= uvMaxs.y && uvMins.y <= tOffset + tMax ) ) { // init paintRect float flRoundedCircleRadius = floor( fpmax( uvExtents.x, uvExtents.y ) + 0.5f ); int surfWidth = sMax + 1; int surfHeight = tMax + 1; paintRect.flCenterAlpha = distFromPlane / flSphereRadius; paintRect.flCircleRadius = flRoundedCircleRadius; paintRect.uvCenter = uvCenter; paintRect.surfID = surfID; int startX = MAX( ( int )( uvCenter.x - flRoundedCircleRadius - 0.5f ), sOffset ); int startY = MAX( ( int )( uvCenter.y - flRoundedCircleRadius - 0.5f ), tOffset ); int endX = MIN( ( int )( uvCenter.x + flRoundedCircleRadius + 0.5f ), sOffset + surfWidth ); int endY = MIN( ( int )( uvCenter.y + flRoundedCircleRadius + 0.5f ), tOffset + surfHeight ); Rect_t rect; rect.x = startX; rect.y = startY; rect.width = endX - startX; rect.height = endY - startY; paintRect.rect = rect; return ( rect.width > 0 && rect.height > 0 ); } #endif return false; } bool R_PaintSurface( SurfaceHandle_t surfID, const Vector &vPosition, float flSphereRadius, const VPlane& basePlane, BYTE colorIndex, float flPaintCoatPercent ) { bool bAddedPaint = false; int lightmapID = materialSortInfoArray[MSurf_MaterialSortID( surfID )].lightmapPageID; PaintRect_t paintRect; paintRect.colorIndex = colorIndex; paintRect.flPaintCoatPercent = flPaintCoatPercent; if ( ComputePaintRect( surfID, vPosition, flSphereRadius, paintRect ) ) { if ( g_PaintManager.m_pPaintTextureDataArray[ lightmapID ].Paint( paintRect ) ) { // HACK SUPER HACK! mark surface as painted to optimize rendering MSurf_Flags( surfID ) |= SURFDRAW_PAINTED; MarkSurfaceBrushes( MSurf_Index(surfID) ); bAddedPaint = true; } } return bAddedPaint; } //----------------------------------------------------------------------------- // find a surface to paint by traversing through all surfaces in a model //----------------------------------------------------------------------------- struct paintinfo_t { Vector m_vPosition; VPlane m_plane; // closest plane to the sphere worldbrushdata_t *m_pBrush; // The shared brush data for this model float m_flSize; // radius of surf searching sphere float m_flCurrentDistance; // distance away from the center of the sphere CUtlVector m_aApplySurfs; // debug stuff bool m_bPainting; }; Vector FindClosestPointToTriangle( const Vector& p, const Vector& a, const Vector& b, const Vector& c ) { Vector ab = b - a; Vector ac = c - a; Vector bc = c - b; // project p onto ab, // p' = a + projAB * ab, projAB = projABnom/(projABnom+projABdenom) float projABnom = DotProduct( p - a, ab ), projABdenom = DotProduct( p - b, a - b ); // project p onto ac, // p' = a + projAC * ac, = projACnom/(projACnom+projACdenom) float projACnom = DotProduct( p - a, ac ), projACdenom = DotProduct( p - c, a - c ); if ( projABnom <= 0.0f && projACnom <= 0.0f ) { return a; // Vertex region early out } // project p onto bc, // p' = b + projBC * bc, projBC = projBCnom/(projBCnom+projBCdenom) float projBCnom = DotProduct( p - b, bc ), projBCdenom = DotProduct( p - c, b - c ); if ( projABdenom <= 0.0f && projBCnom <= 0.0f ) { return b; // Vertex region early out } if ( projACdenom <= 0.0f && projBCdenom <= 0.0f ) { return c; // Vertex region early out } Vector n = CrossProduct( ab, ac ); // P is outside (or on) AB if the triple product [N PA PB] = N.(PAxPB) <= 0 float tpC = DotProduct( n, CrossProduct( a - p, b - p ) ); // If P outside AB and within feature region of AB, return projection of P onto AB if ( tpC <= 0.0f && projABnom >= 0.0f && projABdenom >= 0.0f ) { return a + projABnom / ( projABnom + projABdenom ) * ab; } // P is outside (or on) BC if the triple product [N PB PC] = N.(PBxPC) <= 0 float tpA = DotProduct( n, CrossProduct(b - p, c - p) ); // If P outside BC and within feature region of BC, return projection of P onto BC if ( tpA <= 0.0f && projBCnom >= 0.0f && projBCdenom >= 0.0f ) { return b + projBCnom / (projBCnom + projBCdenom) * bc; } // P is outside (or on) CA if the triple product [N PC PA] = N.(PCxPA) <= 0 float tpB = DotProduct( n, CrossProduct(c - p, a - p) ); // If P outside CA and within feature region of CA, return projection of P onto CA if ( tpB <= 0.0f && projACnom >= 0.0f && projACdenom >= 0.0f ) { return a + projACnom / ( projACnom + projACdenom ) * ac; } // P must project inside the triangle. Compute the position using barycentric coordinates float u = tpA / ( tpA + tpB + tpC ); float v = tpB / ( tpA + tpB + tpC ); float w = 1.0f - u - v; // = tpC / ( tpA + tpB + tpC ) return u * a + v * b + w * c; } bool IsSphereIntersectForwardFacingTriangle( const Vector& vCenter, const VPlane& plane, float flRadius, const Vector& a, const Vector& b, const Vector& c, Vector& vClosestPoint ) { vClosestPoint = FindClosestPointToTriangle( vCenter, a, b, c ); float flDistFromPlane = DotProduct( vClosestPoint, plane.m_Normal ) - plane.m_Dist; if ( flDistFromPlane < 0.f ) { vClosestPoint = vClosestPoint + ( flDistFromPlane + 0.1f ) * plane.m_Normal; } Vector vDist = vCenter - vClosestPoint; float flDistSquare = DotProduct( vDist, vDist ); return flDistSquare < Square( flRadius ); } void R_AddPaintToSurface( SurfaceHandle_t surfID, paintinfo_t *paintinfo ) { const unsigned int ignoreFlags = SURFDRAW_NOPAINT | SURFDRAW_TRANS; if ( MSurf_Flags( surfID ) & ignoreFlags ) return; // Displacement surfaces get decals in R_DecalLeaf. if ( SurfaceHasDispInfo( surfID ) ) return; if ( paintinfo->m_aApplySurfs.Find( surfID ) != -1 ) return; // in multiplayer, materialSortInfoArray is initialized after paint power user objects are active if ( materialSortInfoArray == NULL ) return; // don't do it if it's full bright if ( materialSortInfoArray[MSurf_MaterialSortID( surfID )].lightmapPageID < 0 ) return; VPlane plane = MSurf_GetForwardFacingPlane( surfID ); float flDistFromPlane = DotProduct( paintinfo->m_vPosition, plane.m_Normal ) - plane.m_Dist; // ignore if point is behind the plane or too far from the forward facing plane if ( flDistFromPlane > paintinfo->m_flSize || flDistFromPlane < 0.f ) return; // check if the sphere actually intersecting with the surface using barycentric test int nFirstVertex = MSurf_FirstVertIndex( surfID ); int numVert = MSurf_VertCount( surfID ); int vertIndex = host_state.worldbrush->vertindices[nFirstVertex]; Vector vOrigin = host_state.worldbrush->vertexes[vertIndex].position; bool bIntersect = false; Vector vClosestPoint; for (int v = 1; v < numVert - 1; ++v ) { vertIndex = host_state.worldbrush->vertindices[nFirstVertex+v]; Vector v1 = host_state.worldbrush->vertexes[vertIndex].position; vertIndex = host_state.worldbrush->vertindices[nFirstVertex+v+1]; Vector v2 = host_state.worldbrush->vertexes[vertIndex].position; Vector vIntersectPoint; if ( IsSphereIntersectForwardFacingTriangle( paintinfo->m_vPosition, plane, paintinfo->m_flSize, vOrigin, v1, v2, vIntersectPoint ) ) { if ( !bIntersect ) { vClosestPoint = vIntersectPoint; } else { Vector vecA = vClosestPoint - paintinfo->m_vPosition; Vector vecB = vIntersectPoint - paintinfo->m_vPosition; if ( DotProduct( vecB, vecB ) < DotProduct( vecA, vecA ) ) { vClosestPoint = vIntersectPoint; } } bIntersect = true; } } if ( bIntersect ) { Vector vDist = vClosestPoint - paintinfo->m_vPosition; float flDistFromClosestPointSquare = DotProduct( vDist, vDist ); if ( flDistFromClosestPointSquare < paintinfo->m_flCurrentDistance ) { paintinfo->m_flCurrentDistance = flDistFromClosestPointSquare; paintinfo->m_plane = plane; } paintinfo->m_aApplySurfs.AddToTail( surfID ); } } //----------------------------------------------------------------------------- // iterate over all surfaces on a model, looking for surfaces to paint //----------------------------------------------------------------------------- void R_PaintLeaf( mleaf_t *pLeaf, paintinfo_t *paintinfo ) { SurfaceHandle_t *pHandle = &host_state.worldbrush->marksurfaces[pLeaf->firstmarksurface]; for ( int i = 0; i < pLeaf->nummarksurfaces; i++ ) { SurfaceHandle_t surfID = pHandle[i]; R_AddPaintToSurface( surfID, paintinfo ); } } void R_PaintNode( mnode_t *node, paintinfo_t* paintinfo ) { cplane_t *splitplane; float dist; if (!node ) return; if ( node->contents >= 0 ) { R_PaintLeaf( (mleaf_t *)node, paintinfo ); return; } splitplane = node->plane; dist = DotProduct (paintinfo->m_vPosition, splitplane->normal) - splitplane->dist; if (dist > paintinfo->m_flSize) { R_PaintNode (node->children[0], paintinfo); } else if (dist < -paintinfo->m_flSize) { R_PaintNode (node->children[1], paintinfo); } else { R_PaintNode (node->children[0], paintinfo); R_PaintNode (node->children[1], paintinfo); } } bool IsSurfaceInFrontOfPlane( SurfaceHandle_t surfID, const VPlane& plane ) { // check if the surface is on the same plane as the main plane VPlane trianglePlane = MSurf_GetForwardFacingPlane( surfID ); if ( AlmostEqual( DotProduct( plane.m_Normal, trianglePlane.m_Normal ), 1.f ) && AlmostEqual( plane.m_Dist, trianglePlane.m_Dist) ) { return true; } // if center of the first triangle is behind the main plane, ignore it. int numVert = MSurf_VertCount( surfID ); if ( numVert < 3 ) { return false; } int nFirstVertex = MSurf_FirstVertIndex( surfID ); int vertIndex = host_state.worldbrush->vertindices[nFirstVertex]; Vector vOrigin = host_state.worldbrush->vertexes[vertIndex].position; vertIndex = host_state.worldbrush->vertindices[nFirstVertex + 1]; Vector v1 = host_state.worldbrush->vertexes[vertIndex].position; vertIndex = host_state.worldbrush->vertindices[nFirstVertex + 2]; Vector v2 = host_state.worldbrush->vertexes[vertIndex].position; Vector vCenter = vOrigin + 0.25f * ( ( v1 - vOrigin ) + ( v2 - vOrigin ) ); vCenter += 0.1f * trianglePlane.m_Normal; // push it a bit off the triangle plane to account for floating point error if ( plane.DistTo( vCenter ) < 0.f ) { return false; } // debug paint rejection /*{ CDebugOverlay::AddTriangleOverlay( vOrigin, v1, v2, 255, 0,0,128, true, 10.0f ); CDebugOverlay::AddBoxOverlay( vCenter, Vector(-1,-1,-1), Vector(1,1,1), vec3_angle, 255, 255,0,128, 10.0f ); }*/ return true; } bool ShootPaintSphere( const model_t *pModel, const Vector& vPosition, BYTE colorIndex, float flSphereRadius, float flPaintCoatPercent ) { if ( !g_PaintManager.m_bShouldRegister || g_PaintManager.m_pPaintTextureDataArray == NULL ) { return false; } Assert( pModel ); if ( !pModel ) return false; paintinfo_t paintinfo; paintinfo.m_vPosition = vPosition; paintinfo.m_flSize = flSphereRadius; paintinfo.m_flCurrentDistance = FLT_MAX; paintinfo.m_pBrush = pModel->brush.pShared; paintinfo.m_aApplySurfs.RemoveAll(); paintinfo.m_bPainting = true; mnode_t *pnodes = paintinfo.m_pBrush->nodes + pModel->brush.firstnode; R_PaintNode( pnodes, &paintinfo ); //do the actual painting bool bChangedPaint = false; int numSurf = paintinfo.m_aApplySurfs.Count(); for ( int i=0; i& surfColors ) { if ( (MSurf_Flags( surfID ) & SURFDRAW_PAINTED) == 0 ) return; // only detects paint from surface that has similar normal if ( DotProduct( vContactNormal, MSurf_GetForwardFacingPlane( surfID ).m_Normal ) < 0.9f ) return; int lightmapID = materialSortInfoArray[MSurf_MaterialSortID( surfID )].lightmapPageID; PaintRect_t paintRect; if ( ComputePaintRect( surfID, vPosition, flSphereRadius, paintRect ) ) { g_PaintManager.m_pPaintTextureDataArray[ lightmapID ].GetPixels( paintRect.rect, surfColors ); } } void TracePaintSphere( const model_t *pModel, const Vector& vPosition, const Vector& vContactNormal, float flSphereRadius, CUtlVector& surfColors ) { if ( !g_PaintManager.m_bShouldRegister || g_PaintManager.m_pPaintTextureDataArray == NULL ) { return; } Assert( pModel ); if ( !pModel ) return; //clear paint surfColors.RemoveAll(); paintinfo_t paintinfo; paintinfo.m_vPosition = vPosition; paintinfo.m_flSize = flSphereRadius; paintinfo.m_flCurrentDistance = FLT_MAX; paintinfo.m_pBrush = pModel->brush.pShared; paintinfo.m_aApplySurfs.RemoveAll(); paintinfo.m_bPainting = false; mnode_t *pnodes = paintinfo.m_pBrush->nodes + pModel->brush.firstnode; R_PaintNode( pnodes, &paintinfo ); int numSurf = paintinfo.m_aApplySurfs.Count(); for ( int i=0; iWriteFile( filename, NULL, buf ); buf.Purge(); } } #endif