//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "ground_line.h" #include "mathlib/vplane.h" #include "beamdraw.h" #include "bitvec.h" #include "clientmode_commander.h" #include #include #include "clienteffectprecachesystem.h" #include "tier0/vprof.h" #define MAX_DOWN_DIST 300 #define MAX_UP_DIST 300 #define XY_PER_SEGMENT 100 CLIENTEFFECT_REGISTER_BEGIN( PrecacheGroundLine ) CLIENTEFFECT_MATERIAL( "player/support/mortarline" ) CLIENTEFFECT_REGISTER_END() static CUtlLinkedList< CGroundLine*, unsigned short > s_GroundLines; // ---------------------------------------------------------------------- // // Helpers. // ---------------------------------------------------------------------- // VPlane VPlaneFromCPlane(const cplane_t &plane) { if(plane.signbits) return VPlane(-plane.normal, -plane.dist); else return VPlane(plane.normal, plane.dist); } Vector ClipEndPos(const Vector &vStart, const Vector &vEnd, float clipDist, VPlane *pPlane) { trace_t trace; UTIL_TraceLine(vStart, vEnd, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace); if(trace.fraction < 1) { *pPlane = VPlaneFromCPlane(trace.plane); return trace.endpos + pPlane->m_Normal * clipDist; } else { pPlane->m_Normal.Init(0,0,1); pPlane->m_Dist = DotProduct(pPlane->m_Normal, vEnd); return vEnd; } } // Tries to find the closest surface point to the specified point. Vector FindBestSurfacePoint(const Vector &vPos) { static float stepDist = 500; static float flHeightAboveGround = 20; // First, find an inside point. // Test upwards. trace_t trace; UTIL_TraceLine( Vector(vPos[0], vPos[1], vPos[2] + stepDist), vPos, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace); if(trace.fraction < 1 && trace.fraction != 0) { return Vector(trace.endpos[0], trace.endpos[1], trace.endpos[2] + flHeightAboveGround ); } // Test down. UTIL_TraceLine( vPos, Vector(vPos[0], vPos[1], vPos[2] - stepDist), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace); if(trace.fraction < 1 && trace.fraction != 0) { return Vector(trace.endpos[0], trace.endpos[1], trace.endpos[2] + flHeightAboveGround ); } return vPos; } // Tries to find the place in the world geometry which blocks vStart from the line segment (vEnd1, vEnd2). // Uses a binary search so your error is |vEnd2 - vEnd1| ^ (1 / nIterations) bool BinSearchSegments(const Vector &vStart, const Vector &vEnd1, const Vector &vEnd2, int nIterations, Vector *out) { trace_t trace; // If what was passed into us already intersects then there's nothing we can do. UTIL_TraceLine(vStart, vEnd2, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace); if(trace.fraction < 1) return false; Vector vecs[2] = {vEnd1, vEnd2}; int iIntersect = 0; // Which vector intersects. for(int i=0; i < nIterations; i++) { // Test the midpoint. Vector mid = (vecs[0] + vecs[1]) * 0.5f; UTIL_TraceLine(vStart, mid, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace); if(trace.fraction < 1) vecs[iIntersect] = mid; else vecs[!iIntersect] = mid; } *out = (vecs[0] + vecs[1]) * 0.5f; return true; } // Tries to snap the point to its underlying plane's z. Vector SnapToPlane(const Vector &v) { return v; trace_t trace; UTIL_TraceLine(Vector(v[0], v[1], v[2] + 50), Vector(v[0], v[1], v[2] - 50), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace); if(trace.fraction < 1) return Vector(v[0], v[1], trace.endpos[2] + 3); else return v; } // ---------------------------------------------------------------------- // // CGroundLine implementation. // ---------------------------------------------------------------------- // CGroundLine::CGroundLine() : BaseClass( NULL, "CGroundLine" ) { m_pMaterial = NULL; m_ListHandle = s_GroundLines.AddToHead( this ); SetParent( CMinimapPanel::MinimapRootPanel() ); m_nPoints = 0; SetVisible( true ); SetPaintBackgroundEnabled( false ); } CGroundLine::~CGroundLine() { s_GroundLines.Remove( m_ListHandle ); m_vStart.Init(); m_vEnd.Init(); m_LineWidth = 1; } bool CGroundLine::Init(const char *pMaterialName) { m_pMaterial = materials->FindMaterial(pMaterialName, TEXTURE_GROUP_CLIENT_EFFECTS); return !!m_pMaterial; } void CGroundLine::SetParameters( const Vector &vStart, const Vector &vEnd, const Vector &vStartColor, // Color values 0-1 const Vector &vEndColor, float alpha, float lineWidth ) { m_vStart = vStart; m_vEnd = vEnd; m_vStartColor = vStartColor; m_vEndColor = vEndColor; m_Alpha = alpha; m_LineWidth = lineWidth; Vector vTo( vEnd.x - vStart.x, vEnd.y - vStart.y, 0 ); float flXYLen = vTo.Length(); // Recalculate our segment list. unsigned int nSteps = (int)flXYLen / XY_PER_SEGMENT; nSteps = clamp( nSteps, 8, MAX_GROUNDLINE_SEGMENTS ) & ~1; unsigned int nMaxSteps = nSteps / 2; // First generate the sequence. We generate every other point here so it can insert fixup points to prevent // it from crossing world geometry. Vector pt[MAX_GROUNDLINE_SEGMENTS]; Vector vStep = (Vector(m_vEnd[0], m_vEnd[1], 0) - Vector(m_vStart[0], m_vStart[1], 0)) / (nMaxSteps-1); pt[0] = FindBestSurfacePoint(m_vStart); unsigned int i; for(i=1; i < nMaxSteps; i++) pt[i<<1] = FindBestSurfacePoint(pt[(i-1)<<1] + vStep); CBitVec pointsUsed; pointsUsed.ClearAll(); // Now try to make sure they don't intersect the geometry. for(i=0; i < nMaxSteps-1; i++) { Vector &a = pt[i<<1]; Vector &b = pt[(i+1)<<1]; trace_t trace; UTIL_TraceLine(a, b, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace); if(trace.fraction < 1) { int cIndex = (i<<1)+1; Vector &c = pt[cIndex]; // Ok, this line segment intersects the world. Do a binary search to try to find the // point of intersection. Vector hi, lo; if(a.z < b.z) { hi = b; lo = a; } else { hi = a; lo = b; } if(BinSearchSegments(lo, hi, Vector(lo[0],lo[1],hi[2]), 15, &c)) { pointsUsed.Set( cIndex ); } else if(BinSearchSegments(lo, hi, Vector(hi[0],hi[1],hi[2]+500), 15, &c)) { pointsUsed.Set( cIndex ); } } } // Export the points. m_nPoints = 0; for(i=0; i < nSteps; i++) { // Every other point is always active. if( pointsUsed.Get( i ) || !(i & 1) ) { m_Points[m_nPoints] = pt[i]; ++m_nPoints; } } } //----------------------------------------------------------------------------- // Purpose: Set the visibility of the groundline //----------------------------------------------------------------------------- void CGroundLine::SetVisible( bool bVisible ) { m_bVisible = bVisible; } //----------------------------------------------------------------------------- // Purpose: Return true if the groundline's visible //----------------------------------------------------------------------------- bool CGroundLine::IsVisible( void ) { return m_bVisible; } void CGroundLine::DrawAllGroundLines() { VPROF("CGroundLine::DrawAllGroundLines()"); unsigned short i; for( i = s_GroundLines.Head(); i != s_GroundLines.InvalidIndex(); i = s_GroundLines.Next(i) ) { s_GroundLines[i]->Draw(); } } void CGroundLine::Draw() { if ( !m_pMaterial || m_nPoints < 2 ) return; if ( !IsVisible() ) return; float flAlpha = m_Alpha; if( g_pClientMode == ClientModeCommander() ) { flAlpha = 1; // draw bright.. } CBeamSegDraw beamDraw; beamDraw.Start( m_nPoints, m_pMaterial ); for( unsigned int i=0; i < m_nPoints; i++ ) { float t = (float)i / (m_nPoints - 1); CBeamSeg seg; seg.m_vPos = m_Points[i]; VectorLerp( m_vStartColor, m_vEndColor, t, seg.m_vColor ); seg.m_flTexCoord = 0; seg.m_flWidth = m_LineWidth; seg.m_flAlpha = m_Alpha; beamDraw.NextSeg( &seg ); } beamDraw.End(); } static inline bool ClipLine( float &x1, float &y1, float &x2, float &y2, float xClip, float sign ) { if( x1*sign < (xClip-0.001f)*sign ) { if( x2*sign > (xClip+0.001f)*sign ) { float t = (xClip-x1) / (x2 - x1); x1 = x1 + (x2 - x1) * t; y1 = y1 + (y2 - y1) * t; } else { return false; } } else if( x2*sign < (xClip-0.001f)*sign ) { if( x1*sign > (xClip+0.001f)*sign ) { float t = (xClip-x1) / (x2 - x1); x2 = x1 + (x2 - x1) * t; y2 = y1 + (y2 - y1) * t; } else { return false; } } return true; } void CGroundLine::Paint( ) { vgui::Panel *pPanel = GetParent(); int wide, tall; pPanel->GetSize( wide, tall ); float tPrev = 0; float xPrev, yPrev; CMinimapPanel::MinimapPanel()->WorldToMinimap( MINIMAP_NOCLIP, m_vStart, xPrev, yPrev ); int nSegs = 20; for( int iSeg=1; iSeg <= nSegs; iSeg++ ) { float t = (float)iSeg / nSegs; Vector v3DPos; VectorLerp( m_vStart, m_vEnd, t, v3DPos ); float x, y; CMinimapPanel::MinimapPanel()->WorldToMinimap( MINIMAP_NOCLIP, v3DPos, x, y ); // Clip the line segment on X, then Y. if( ClipLine( xPrev, yPrev, x, y, 0, 1 ) && ClipLine( xPrev, yPrev, x, y, wide, -1 ) && ClipLine( yPrev, xPrev, y, x, 0, 1 ) && ClipLine( yPrev, xPrev, y, x, tall, -1 ) ) { Vector vColor; VectorLerp( m_vStartColor, m_vEndColor, t, vColor ); vColor *= 255.9f; vgui::surface()->DrawSetColor( (unsigned char)RoundFloatToInt( vColor.x ), (unsigned char)RoundFloatToInt( vColor.y ), (unsigned char)RoundFloatToInt( vColor.z ), 255 ); vgui::surface()->DrawLine( xPrev, yPrev, x, y ); } tPrev = t; xPrev = x; yPrev = y; } // Draw a marker at the endpoint. float xEnd, yEnd; if( CMinimapPanel::MinimapPanel()->WorldToMinimap( MINIMAP_NOCLIP, m_vEnd, xEnd, yEnd ) ) { int ix = RoundFloatToInt( xEnd ); int iy = RoundFloatToInt( yEnd ); int rectSize=1; vgui::surface()->DrawSetColor( 255, 255, 255, 255 ); vgui::surface()->DrawOutlinedRect( ix-rectSize, iy-rectSize, ix+rectSize, iy+rectSize ); } }