//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include #include "DispPaint.h" #include "ToolDisplace.h" #include "CollisionUtils.h" #include "DispManager.h" #include "MapDoc.h" #include "MapDisp.h" #include "GlobalFunctions.h" #include "History.h" #include "DispSew.h" // memdbgon must be the last include file in a .cpp file!!! #include #define DISPPAINT_RADIUS_OUTER_CLAMP 0.01f //----------------------------------------------------------------------------- // Purpose: constructor //----------------------------------------------------------------------------- CDispPaintMgr::CDispPaintMgr() { } //----------------------------------------------------------------------------- // Purpose: destructor //----------------------------------------------------------------------------- CDispPaintMgr::~CDispPaintMgr() { m_aNudgeData.Purge(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CDispPaintMgr::Paint( SpatialPaintData_t &spatialData, bool bAutoSew ) { // Setup painting. if ( !PrePaint( spatialData ) ) return false; // Handle painting. if ( !DoPaint( spatialData ) ) return false; // Finish painting. if ( !PostPaint( bAutoSew ) ) return false; // Successful paint operation. return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CDispPaintMgr::PrePaint( SpatialPaintData_t &spatialData ) { // Generate cached spatial data. spatialData.m_flRadius2 = ( spatialData.m_flRadius * spatialData.m_flRadius ); spatialData.m_flOORadius2 = 1.0f / spatialData.m_flRadius2; // Setup nudge data. if ( spatialData.m_bNudgeInit ) { m_aNudgeData.RemoveAll(); } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CDispPaintMgr::PostPaint( bool bAutoSew ) { // Get the displacement manager from the active map document. IWorldEditDispMgr *pDispMgr = GetActiveWorldEditDispManager(); if( !pDispMgr ) return false; // Update the modified displacements. int nDispCount = pDispMgr->SelectCount(); for ( int iDisp = 0; iDisp < nDispCount; iDisp++ ) { CMapDisp *pDisp = pDispMgr->GetFromSelect( iDisp ); if ( pDisp ) { pDisp->Paint_Update( false ); } } // Auto "sew" if necessary. if ( bAutoSew ) { FaceListSewEdges(); } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CDispPaintMgr::DoPaint( SpatialPaintData_t &spatialData ) { // Get the displacement manager from the active map document. IWorldEditDispMgr *pDispMgr = GetActiveWorldEditDispManager(); if( !pDispMgr ) return false; // Special case - nudging! if ( spatialData.m_bNudge && !spatialData.m_bNudgeInit ) { DoNudgeAdd( spatialData ); return true; } // For each displacement surface is the selection list attempt to paint on it. int nDispCount = pDispMgr->SelectCount(); for ( int iDisp = 0; iDisp < nDispCount; iDisp++ ) { CMapDisp *pDisp = pDispMgr->GetFromSelect( iDisp ); if ( pDisp ) { // Test paint sphere displacement bbox for overlap. Vector vBBoxMin, vBBoxMax; pDisp->GetBoundingBox( vBBoxMin, vBBoxMax ); if ( PaintSphereDispBBoxOverlap( spatialData.m_vCenter, spatialData.m_flRadius, vBBoxMin, vBBoxMax ) ) { // Paint with the correct effect switch ( spatialData.m_nEffect ) { case DISPPAINT_EFFECT_RAISELOWER: { DoPaintAdd( spatialData, pDisp ); break; } case DISPPAINT_EFFECT_RAISETO: { DoPaintEqual( spatialData, pDisp ); break; } case DISPPAINT_EFFECT_SMOOTH: { DoPaintSmooth( spatialData, pDisp ); break; } } } } } // Successful paint. return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDispPaintMgr::NudgeAdd( CMapDisp *pDisp, int iVert ) { int iNudge = m_aNudgeData.AddToTail(); m_aNudgeData[iNudge].m_hDisp = pDisp->GetEditHandle(); m_aNudgeData[iNudge].m_iVert = iVert; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDispPaintMgr::DoNudgeAdd( SpatialPaintData_t &spatialData ) { Vector vPaintPos, vVert; float flDistance2; int nNudgeCount = m_aNudgeData.Count(); for ( int iNudge = 0; iNudge < nNudgeCount; iNudge++ ) { DispVertPair_t *pPairData = &m_aNudgeData[iNudge]; // Get the current vert. CMapDisp *pDisp = EditDispMgr()->GetDisp( pPairData->m_hDisp ); pDisp->GetVert( pPairData->m_iVert, vVert ); if ( IsInSphereRadius( spatialData.m_vCenter, spatialData.m_flRadius2, vVert, flDistance2 ) ) { // Build the new position (paint value) and set it. if ( spatialData.m_uiBrushType == DISPPAINT_BRUSHTYPE_SOFT ) { DoPaintOneOverR( spatialData, vVert, flDistance2, vPaintPos ); } else if ( spatialData.m_uiBrushType == DISPPAINT_BRUSHTYPE_HARD ) { DoPaintOne( spatialData, vVert, vPaintPos ); } AddToUndo( &pDisp ); pDisp->Paint_SetValue( pPairData->m_iVert, vPaintPos ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CDispPaintMgr::PaintSphereDispBBoxOverlap( const Vector &vCenter, float flRadius, const Vector &vBBoxMin, const Vector &vBBoxMax ) { return IsBoxIntersectingSphere( vBBoxMin, vBBoxMax, vCenter, flRadius ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CDispPaintMgr::IsInSphereRadius( const Vector &vCenter, float flRadius2, const Vector &vPos, float &flDistance2 ) { Vector vTmp; VectorSubtract( vPos, vCenter, vTmp ); flDistance2 = ( vTmp.x * vTmp.x ) + ( vTmp.y * vTmp.y ) + ( vTmp.z * vTmp.z ); return ( flDistance2 < flRadius2 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDispPaintMgr::AddToUndo( CMapDisp **pDisp ) { CMapDisp *pUndoDisp = *pDisp; if ( pUndoDisp->Paint_IsDirty() ) return; IWorldEditDispMgr *pDispMgr = GetActiveWorldEditDispManager(); if( pDispMgr ) { EditDispHandle_t handle = pUndoDisp->GetEditHandle(); pDispMgr->Undo( handle, false ); *pDisp = EditDispMgr()->GetDisp( handle ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDispPaintMgr::DoPaintAdd( SpatialPaintData_t &spatialData, CMapDisp *pDisp ) { Vector vPaintPos, vVert; float flDistance2; int nVertCount = pDisp->GetSize(); for ( int iVert = 0; iVert < nVertCount; iVert++ ) { // Get the current vert. pDisp->GetVert( iVert, vVert ); if ( IsInSphereRadius( spatialData.m_vCenter, spatialData.m_flRadius2, vVert, flDistance2 ) ) { // Build the new position (paint value) and set it. if ( spatialData.m_uiBrushType == DISPPAINT_BRUSHTYPE_SOFT ) { DoPaintOneOverR( spatialData, vVert, flDistance2, vPaintPos ); } else if ( spatialData.m_uiBrushType == DISPPAINT_BRUSHTYPE_HARD ) { DoPaintOne( spatialData, vVert, vPaintPos ); } AddToUndo( &pDisp ); pDisp->Paint_SetValue( iVert, vPaintPos ); // Add data to nudge list. if ( spatialData.m_bNudgeInit ) { NudgeAdd( pDisp, iVert ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDispPaintMgr::DoPaintEqual( SpatialPaintData_t &spatialData, CMapDisp *pDisp ) { Vector vPaintPos, vVert, vFlatVert; float flDistance2; int nVertCount = pDisp->GetSize(); for ( int iVert = 0; iVert < nVertCount; iVert++ ) { // Get the current vert. pDisp->GetVert( iVert, vVert ); if ( IsInSphereRadius( spatialData.m_vCenter, spatialData.m_flRadius2, vVert, flDistance2 ) ) { // Get the base vert. pDisp->GetFlatVert( iVert, vFlatVert ); // Build the new position (paint value) and set it. DoPaintOne( spatialData, vFlatVert, vPaintPos ); AddToUndo( &pDisp ); pDisp->Paint_SetValue( iVert, vPaintPos ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDispPaintMgr::DoPaintSmooth( SpatialPaintData_t &spatialData, CMapDisp *pDisp ) { Vector vPaintPos, vVert; float flDistance2; int nVertCount = pDisp->GetSize(); for ( int iVert = 0; iVert < nVertCount; iVert++ ) { // Get the current vert. pDisp->GetVert( iVert, vVert ); if ( IsInSphereRadius( spatialData.m_vCenter, spatialData.m_flRadius2, vVert, flDistance2 ) ) { // Build the new smoothed position and set it. if ( DoPaintSmoothOneOverExp( spatialData, vVert, vPaintPos ) ) { AddToUndo( &pDisp ); pDisp->Paint_SetValue( iVert, vPaintPos ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CDispPaintMgr::CalcSmoothRadius2( const SpatialPaintData_t &spatialData, const Vector &vPoint ) { Vector vTmp; VectorSubtract( spatialData.m_vCenter, vPoint, vTmp ); float flDistance2 = ( vTmp.x * vTmp.x ) + ( vTmp.y * vTmp.y ) + ( vTmp.z * vTmp.z ); float flRatio = flDistance2 / spatialData.m_flRadius2; flRatio = 1.0f - flRatio; float flRadius = flRatio * spatialData.m_flRadius; return ( flRadius * flRadius ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CDispPaintMgr::DoPaintSmoothOneOverExp( const SpatialPaintData_t &spatialData, const Vector &vNewCenter, Vector &vPaintPos ) { // Get the displacement manager from the active map document. IWorldEditDispMgr *pDispMgr = GetActiveWorldEditDispManager(); if( !pDispMgr ) return false; // Calculate the smoothing radius. float flNewRadius2 = CalcSmoothRadius2( spatialData, vNewCenter ); float flNewRadius = ( float )sqrt( flNewRadius2 ); // Test all selected surfaces for smoothing. float flWeight = 0.0f; float flSmoothDist = 0.0f; // Calculate the plane dist. float flPaintDist = spatialData.m_vPaintAxis.Dot( vNewCenter ); int nDispCount = pDispMgr->SelectCount(); for ( int iDisp = 0; iDisp < nDispCount; iDisp++ ) { CMapDisp *pDisp = pDispMgr->GetFromSelect( iDisp ); if ( pDisp ) { // Test paint sphere displacement bbox for overlap. Vector vBBoxMin, vBBoxMax; pDisp->GetBoundingBox( vBBoxMin, vBBoxMax ); if ( PaintSphereDispBBoxOverlap( vNewCenter, flNewRadius, vBBoxMin, vBBoxMax ) ) { Vector vVert; int nVertCount = pDisp->GetSize(); for ( int iVert = 0; iVert < nVertCount; iVert++ ) { // Get the current vert. pDisp->GetVert( iVert, vVert ); float flDistance2 = 0.0f; if ( IsInSphereRadius( vNewCenter, flNewRadius2, vVert, flDistance2 ) ) { float flRatio = flDistance2 / flNewRadius2; float flFactor = 1.0f / exp( flRatio ); if ( flFactor != 1.0f ) { flFactor *= 1.0f / ( spatialData.m_flScalar * 2.0f ); } Vector vProjectVert; float flProjectDist = DotProduct( vVert, spatialData.m_vPaintAxis ) - flPaintDist; flSmoothDist += ( flProjectDist * flFactor ); flWeight += flFactor; } } } } } // Re-normalize the smoothing position. flSmoothDist /= flWeight; vPaintPos = vNewCenter + ( spatialData.m_vPaintAxis * flSmoothDist ); return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDispPaintMgr::DoPaintOneOverR( const SpatialPaintData_t &spatialData, const Vector &vPos, float flDistance2, Vector &vNewPos ) { float flValue = 1.0f - ( flDistance2 * spatialData.m_flOORadius2 ); flValue *= spatialData.m_flScalar; VectorScale( spatialData.m_vPaintAxis, flValue, vNewPos ); VectorAdd( vNewPos, vPos, vNewPos ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDispPaintMgr::DoPaintOne( const SpatialPaintData_t &spatialData, const Vector &vPos, Vector &vNewPos ) { float flValue = spatialData.m_flScalar; VectorScale( spatialData.m_vPaintAxis, flValue, vNewPos ); VectorAdd( vNewPos, vPos, vNewPos ); }