//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: BSP collision! // // $NoKeywords: $ //=============================================================================// #include "cmodel_engine.h" #include "cmodel_private.h" #include "dispcoll_common.h" #include "coordsize.h" #include "quakedef.h" #include #include #include "mathlib/mathlib.h" #include "common.h" #include "sysexternal.h" #include "zone.h" #include "utlvector.h" #include "const.h" #include "gl_model_private.h" #include "vphysics_interface.h" #include "icliententity.h" #include "engine/ICollideable.h" #include "enginethreads.h" #include "sys_dll.h" #include "collisionutils.h" #include "tier0/tslist.h" #include "tier0/vprof.h" #include "tier1/fmtstr.h" #include "engine/IEngineTrace.h" #include "mathlib/aabb.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" CCollisionBSPData g_BSPData; // the global collision bsp #define g_BSPData dont_use_g_BSPData_directly #ifdef COUNT_COLLISIONS CCollisionCounts g_CollisionCounts; // collision test counters #endif static const float kBoxCheckFloatEpsilon = 0.01f; // Used for box trace assert checks below. csurface_t CCollisionBSPData::nullsurface = { "**empty**", 0, 0 }; // generic null collision model surface csurface_t *CCollisionBSPData::GetSurfaceAtIndex( unsigned short surfaceIndex ) { if ( surfaceIndex == SURFACE_INDEX_INVALID ) { return &nullsurface; } return &map_surfaces[surfaceIndex]; } CTSPool g_TraceInfoPool; TraceInfo_t *BeginTrace() { TraceInfo_t *pTraceInfo = g_TraceInfoPool.GetObject(); if ( pTraceInfo->m_BrushCounters[0].Count() != GetCollisionBSPData()->numbrushes + 1 ) { memset( pTraceInfo->m_Count, 0, sizeof( pTraceInfo->m_Count ) ); pTraceInfo->m_nCheckDepth = -1; for ( int i = 0; i < MAX_CHECK_COUNT_DEPTH; i++ ) { pTraceInfo->m_BrushCounters[i].SetCount( GetCollisionBSPData()->numbrushes + 1 ); pTraceInfo->m_DispCounters[i].SetCount( g_DispCollTreeCount ); memset( pTraceInfo->m_BrushCounters[i].Base(), 0, pTraceInfo->m_BrushCounters[i].Count() * sizeof(TraceCounter_t) ); memset( pTraceInfo->m_DispCounters[i].Base(), 0, pTraceInfo->m_DispCounters[i].Count() * sizeof(TraceCounter_t) ); } } PushTraceVisits( pTraceInfo ); pTraceInfo->m_pBSPData = GetCollisionBSPData(); return pTraceInfo; } void PushTraceVisits( TraceInfo_t *pTraceInfo ) { ++pTraceInfo->m_nCheckDepth; Assert( (pTraceInfo->m_nCheckDepth >= 0) && (pTraceInfo->m_nCheckDepth < MAX_CHECK_COUNT_DEPTH) ); int i = pTraceInfo->m_nCheckDepth; pTraceInfo->m_Count[i]++; if ( pTraceInfo->m_Count[i] == 0 ) { pTraceInfo->m_Count[i]++; memset( pTraceInfo->m_BrushCounters[i].Base(), 0, pTraceInfo->m_BrushCounters[i].Count() * sizeof(TraceCounter_t) ); memset( pTraceInfo->m_DispCounters[i].Base(), 0, pTraceInfo->m_DispCounters[i].Count() * sizeof(TraceCounter_t) ); } } void PopTraceVisits( TraceInfo_t *pTraceInfo ) { --pTraceInfo->m_nCheckDepth; Assert( pTraceInfo->m_nCheckDepth >= -1 ); } void EndTrace( TraceInfo_t *&pTraceInfo ) { PopTraceVisits( pTraceInfo ); Assert( pTraceInfo->m_nCheckDepth == -1 ); g_TraceInfoPool.PutObject( pTraceInfo ); pTraceInfo = NULL; } static ConVar map_noareas( "map_noareas", "0", 0, "Disable area to area connection testing." ); void FloodAreaConnections (CCollisionBSPData *pBSPData); //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- vcollide_t *CM_GetVCollide( int modelIndex ) { cmodel_t *pModel = CM_InlineModelNumber( modelIndex ); if( !pModel ) return NULL; // return the model's collision data return &pModel->vcollisionData; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- cmodel_t *CM_InlineModel( const char *name ) { // error checking! if( !name ) return NULL; // JAYHL2: HACKHACK Get rid of this if( StringHasPrefix( name, "maps/" ) ) return CM_InlineModelNumber( 0 ); // check for valid name if( name[0] != '*' ) Sys_Error( "CM_InlineModel: bad model name!" ); // check for valid model int ndxModel = atoi( name + 1 ); if( ( ndxModel < 1 ) || ( ndxModel >= GetCollisionBSPData()->numcmodels ) ) Sys_Error( "CM_InlineModel: bad model number!" ); return CM_InlineModelNumber( ndxModel ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- cmodel_t *CM_InlineModelNumber( int index ) { CCollisionBSPData *pBSPDataData = GetCollisionBSPData(); if( ( index < 0 ) || ( index > pBSPDataData->numcmodels ) ) return NULL; return ( &pBSPDataData->map_cmodels[ index ] ); } int CM_BrushContents_r( CCollisionBSPData *pBSPData, int nodenum ) { int contents = 0; while (1) { if (nodenum < 0) { int leafIndex = -1 - nodenum; cleaf_t &leaf = pBSPData->map_leafs[leafIndex]; for ( int i = 0; i < leaf.numleafbrushes; i++ ) { unsigned short brushIndex = pBSPData->map_leafbrushes[ leaf.firstleafbrush + i ]; contents |= pBSPData->map_brushes[brushIndex].contents; } return contents; } cnode_t &node = pBSPData->map_rootnode[nodenum]; contents |= CM_BrushContents_r( pBSPData, node.children[0] ); nodenum = node.children[1]; } return contents; } int CM_InlineModelContents( int index ) { cmodel_t *pModel = CM_InlineModelNumber( index ); if ( !pModel ) return 0; return CM_BrushContents_r( GetCollisionBSPData(), pModel->headnode ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- int CM_NumClusters( void ) { return GetCollisionBSPData()->numclusters; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- char *CM_EntityString( void ) { return GetCollisionBSPData()->map_entitystring.Get(); } void CM_DiscardEntityString( void ) { GetCollisionBSPData()->map_entitystring.Discard(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- int CM_LeafContents( int leafnum ) { const CCollisionBSPData *pBSPData = GetCollisionBSPData(); Assert( leafnum >= 0 ); Assert( leafnum < pBSPData->numleafs ); return pBSPData->map_leafs[leafnum].contents; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- int CM_LeafCluster( int leafnum ) { const CCollisionBSPData *pBSPData = GetCollisionBSPData(); Assert( leafnum >= 0 ); Assert( leafnum < pBSPData->numleafs ); return pBSPData->map_leafs[leafnum].cluster; } int CM_LeafFlags( int leafnum ) { const CCollisionBSPData *pBSPData = GetCollisionBSPData(); Assert( leafnum >= 0 ); Assert( leafnum < pBSPData->numleafs ); return pBSPData->map_leafs[leafnum].flags; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- int CM_LeafArea( int leafnum ) { const CCollisionBSPData *pBSPData = GetCollisionBSPData(); Assert( leafnum >= 0 ); Assert( leafnum < pBSPData->numleafs ); return pBSPData->map_leafs[leafnum].area; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CM_FreeMap(void) { // get the current collision bsp -- there is only one! CCollisionBSPData *pBSPData = GetCollisionBSPData(); // free the collision bsp data CollisionBSPData_Destroy( pBSPData ); } // This turns on all the area portals that are "always on" in the map. void CM_InitPortalOpenState( CCollisionBSPData *pBSPData ) { for ( int i=0; i < pBSPData->numportalopen; i++ ) { pBSPData->portalopen[i] = false; } } /* ================== CM_LoadMap Loads in the map and all submodels ================== */ cmodel_t *CM_LoadMap( const char *pPathName, bool allowReusePrevious, texinfo_t *pTexinfo, int texinfoCount, unsigned *checksum ) { static unsigned int last_checksum = 0xFFFFFFFF; // get the current bsp -- there is currently only one! CCollisionBSPData *pBSPData = GetCollisionBSPData(); Assert( physcollision ); if( !strcmp( pBSPData->mapPathName, pPathName ) && allowReusePrevious ) { *checksum = last_checksum; return &pBSPData->map_cmodels[0]; // still have the right version } // only pre-load if the map doesn't already exist CollisionBSPData_PreLoad( pBSPData ); if ( !pPathName || !pPathName[0] ) { *checksum = 0; return &pBSPData->map_cmodels[0]; // cinematic servers won't have anything at all } // read in the collision model data CMapLoadHelper::Init( 0, pPathName ); CollisionBSPData_Load( pPathName, pBSPData, pTexinfo, texinfoCount ); CMapLoadHelper::Shutdown( ); // Push the displacement bounding boxes down the tree and set leaf data. CM_DispTreeLeafnum( pBSPData ); CM_InitPortalOpenState( pBSPData ); FloodAreaConnections( pBSPData ); #ifdef COUNT_COLLISIONS // initialize counters CollisionCounts_Init( &g_CollisionCounts ); #endif return &pBSPData->map_cmodels[0]; } //----------------------------------------------------------------------------- // // Methods associated with colliding against the world + models // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // returns a vcollide that can be used to collide against this model //----------------------------------------------------------------------------- vcollide_t* CM_VCollideForModel( int modelindex, const model_t* pModel ) { switch( pModel->type ) { case mod_brush: return CM_GetVCollide( modelindex-1 ); case mod_studio: Assert( modelloader->IsLoaded( pModel ) ); return g_pMDLCache->GetVCollide( pModel->studio ); } return 0; } //======================================================================= /* ================== CM_PointLeafnum_r ================== */ int CM_PointLeafnumMinDistSqr_r( CCollisionBSPData *pBSPData, const Vector& p, int num, float &minDistSqr ) { float d; cnode_t *node; cplane_t *plane; while (num >= 0) { node = pBSPData->map_rootnode + num; plane = node->plane; if (plane->type < 3) d = p[plane->type] - plane->dist; else d = DotProduct (plane->normal, p) - plane->dist; minDistSqr = fpmin( d*d, minDistSqr ); if (d < 0) num = node->children[1]; else num = node->children[0]; } #ifdef COUNT_COLLISIONS g_CollisionCounts.m_PointContents++; // optimize counter #endif return -1 - num; } int CM_PointLeafnum_r( CCollisionBSPData *pBSPData, const Vector& p, int num) { float d; cnode_t *node; cplane_t *plane; while (num >= 0) { node = pBSPData->map_rootnode + num; plane = node->plane; if (plane->type < 3) d = p[plane->type] - plane->dist; else d = DotProduct (plane->normal, p) - plane->dist; if (d < 0) num = node->children[1]; else num = node->children[0]; } #ifdef COUNT_COLLISIONS g_CollisionCounts.m_PointContents++; // optimize counter #endif return -1 - num; } int CM_PointLeafnum (const Vector& p) { // get the current collision bsp -- there is only one! CCollisionBSPData *pBSPData = GetCollisionBSPData(); if (!pBSPData->numplanes) return 0; // sound may call this without map loaded return CM_PointLeafnum_r (pBSPData, p, 0); } void CM_SnapPointToReferenceLeaf_r( CCollisionBSPData *pBSPData, const Vector& p, int num, float tolerance, Vector *pSnapPoint ) { float d, snapDist; cnode_t *node; cplane_t *plane; while (num >= 0) { node = pBSPData->map_rootnode + num; plane = node->plane; if (plane->type < 3) { d = p[plane->type] - plane->dist; snapDist = (*pSnapPoint)[plane->type] - plane->dist; } else { d = DotProduct (plane->normal, p) - plane->dist; snapDist = DotProduct (plane->normal, *pSnapPoint) - plane->dist; } if (d < 0) { num = node->children[1]; if ( snapDist > 0 ) { *pSnapPoint -= plane->normal * (snapDist + tolerance); } } else { num = node->children[0]; if ( snapDist < 0 ) { *pSnapPoint += plane->normal * (-snapDist + tolerance); } } } } void CM_SnapPointToReferenceLeaf(const Vector &referenceLeafPoint, float tolerance, Vector *pSnapPoint) { // get the current collision bsp -- there is only one! CCollisionBSPData *pBSPData = GetCollisionBSPData(); if (pBSPData->numplanes) { CM_SnapPointToReferenceLeaf_r(pBSPData, referenceLeafPoint, 0, tolerance, pSnapPoint); } } /* ============= CM_BoxLeafnums Fills in a list of all the leafs touched ============= */ struct leafnums_t { int leafTopNode; int leafMaxCount; int *pLeafList; CCollisionBSPData *pBSPData; }; int CM_BoxLeafnums( leafnums_t &context, const Vector ¢er, const Vector &extents, int nodenum ) { int leafCount = 0; const int NODELIST_MAX = 1024; int nodeList[NODELIST_MAX]; int nodeReadIndex = 0; int nodeWriteIndex = 0; cplane_t *plane; cnode_t *node; int prev_topnode = -1; while (1) { if (nodenum < 0) { // This handles the case when the box lies completely // within a single node. In that case, the top node should be // the parent of the leaf if (context.leafTopNode == -1) context.leafTopNode = prev_topnode; if (leafCount < context.leafMaxCount) { context.pLeafList[leafCount] = -1 - nodenum; leafCount++; } if ( nodeReadIndex == nodeWriteIndex ) return leafCount; nodenum = nodeList[nodeReadIndex]; nodeReadIndex = (nodeReadIndex+1) & (NODELIST_MAX-1); } else { node = &context.pBSPData->map_rootnode[nodenum]; plane = node->plane; // s = BoxOnPlaneSide (leaf_mins, leaf_maxs, plane); // s = BOX_ON_PLANE_SIDE(*leaf_mins, *leaf_maxs, plane); float d0 = DotProduct( plane->normal, center ) - plane->dist; float d1 = DotProductAbs( plane->normal, extents ); prev_topnode = nodenum; if (d0 >= d1) nodenum = node->children[0]; else if (d0 < -d1) nodenum = node->children[1]; else { // go down both if (context.leafTopNode == -1) context.leafTopNode = nodenum; nodeList[nodeWriteIndex] = node->children[0]; nodeWriteIndex = (nodeWriteIndex+1) & (NODELIST_MAX-1); // check for overflow of the ring buffer Assert(nodeWriteIndex != nodeReadIndex); nodenum = node->children[1]; } } } } int CM_BoxLeafnums ( const Vector& mins, const Vector& maxs, int *list, int listsize, int *topnode, int cmodelIndex ) { leafnums_t context; context.pLeafList = list; context.leafTopNode = -1; context.leafMaxCount = listsize; // get the current collision bsp -- there is only one! context.pBSPData = GetCollisionBSPData(); Vector center = (mins+maxs)*0.5f; Vector extents = maxs - center; AssertMsg( cmodelIndex >= 0 && cmodelIndex < context.pBSPData->numcmodels, "Collision model index out of bounds." ); int leafCount = 0; if( cmodelIndex >= 0 && cmodelIndex < context.pBSPData->numcmodels ) leafCount = CM_BoxLeafnums(context, center, extents, context.pBSPData->map_cmodels[cmodelIndex].headnode ); if( topnode ) *topnode = context.leafTopNode; return leafCount; } // UNDONE: This is a version that returns only leaves with valid clusters // UNDONE: Use this in the PVS calcs for networking #if 0 int CM_BoxClusters( leafnums_t * RESTRICT pContext, const Vector ¢er, const Vector &extents, int nodenum ) { const int NODELIST_MAX = 1024; int nodeList[NODELIST_MAX]; int nodeReadIndex = 0; int nodeWriteIndex = 0; cplane_t *RESTRICT plane; cnode_t *RESTRICT node; int prev_topnode = -1; int leafCount = 0; while (1) { if (nodenum < 0) { int leafIndex = -1 - nodenum; // This handles the case when the box lies completely // within a single node. In that case, the top node should be // the parent of the leaf if (pContext->leafTopNode == -1) pContext->leafTopNode = prev_topnode; if (leafCount < pContext->leafMaxCount) { cleaf_t *RESTRICT pLeaf = &pContext->pBSPData->map_leafs[leafIndex]; if ( pLeaf->cluster >= 0 ) { pContext->pLeafList[leafCount] = leafIndex; leafCount++; } } if ( nodeReadIndex == nodeWriteIndex ) return leafCount; nodenum = nodeList[nodeReadIndex]; nodeReadIndex = (nodeReadIndex+1) & (NODELIST_MAX-1); } else { node = &pContext->pBSPData->map_rootnode[nodenum]; plane = node->plane; float d0 = DotProduct( plane->normal, center ) - plane->dist; float d1 = DotProductAbs( plane->normal, extents ); prev_topnode = nodenum; if (d0 >= d1) nodenum = node->children[0]; else if (d0 < -d1) nodenum = node->children[1]; else { // go down both if (pContext->leafTopNode == -1) pContext->leafTopNode = nodenum; nodenum = node->children[0]; nodeList[nodeWriteIndex] = node->children[1]; nodeWriteIndex = (nodeWriteIndex+1) & (NODELIST_MAX-1); // check for overflow of the ring buffer Assert(nodeWriteIndex != nodeReadIndex); } } } } int CM_BoxClusters_headnode ( CCollisionBSPData *pBSPData, const Vector& mins, const Vector& maxs, int *list, int listsize, int nodenum, int *topnode) { leafnums_t context; context.pLeafList = list; context.leafTopNode = -1; context.leafMaxCount = listsize; Vector center = 0.5f * (mins + maxs); Vector extents = maxs - center; context.pBSPData = pBSPData; int leafCount = CM_BoxClusters( &context, center, extents, nodenum ); if (topnode) *topnode = context.leafTopNode; return leafCount; } #endif static int FASTCALL CM_BrushBoxContents( CCollisionBSPData *pBSPData, const Vector &vMins, const Vector &vMaxs, cbrush_t *pBrush ) { if ( pBrush->IsBox()) { cboxbrush_t *pBox = &pBSPData->map_boxbrushes[pBrush->GetBox()]; if ( !IsBoxIntersectingBox( vMins, vMaxs, pBox->mins, pBox->maxs ) ) return 0; } else { if (!pBrush->numsides) return 0; Vector vCenter = 0.5f *(vMins + vMaxs); Vector vExt = vMaxs - vCenter; int i, j; cplane_t *plane; float dist; Vector vOffset; float d1; cbrushside_t *side; for (i=0 ; inumsides ; i++) { side = &pBSPData->map_brushsides[pBrush->firstbrushside+i]; plane = side->plane; // FIXME: special case for axial // general box case // push the plane out appropriately for mins/maxs // FIXME: use signbits into 8 way lookup for each mins/maxs for (j=0 ; j<3 ; j++) { if (plane->normal[j] < 0) vOffset[j] = vExt[j]; else vOffset[j] = -vExt[j]; } dist = DotProduct (vOffset, plane->normal); dist = plane->dist - dist; d1 = DotProduct (vCenter, plane->normal) - dist; // if completely in front of face, no intersection if (d1 > 0) return 0; } } // inside this brush return pBrush->contents; } static int FASTCALL CM_BrushPointContents( CCollisionBSPData *pBSPData, const Vector &vPos, cbrush_t *pBrush ) { if ( pBrush->IsBox()) { cboxbrush_t *pBox = &pBSPData->map_boxbrushes[pBrush->GetBox()]; if ( !IsPointInBox( vPos, pBox->mins, pBox->maxs ) ) return 0; } else { if (!pBrush->numsides) return 0; cplane_t *plane; cbrushside_t *side; for ( int i = 0 ; i < pBrush->numsides; i++ ) { side = &pBSPData->map_brushsides[pBrush->firstbrushside+i]; plane = side->plane; float flDist = DotProduct (vPos, plane->normal) - plane->dist; // if completely in front of face, no intersection if (flDist > 0) return 0; } } // inside this brush return pBrush->contents; } /* ================== CM_PointContents ================== */ int CM_PointContents ( const Vector &p, int headnode, int contentsMask ) { int l; // get the current collision bsp -- there is only one! CCollisionBSPData *pBSPData = GetCollisionBSPData(); if (!pBSPData->numnodes ) // map not loaded return 0; if ( !(pBSPData->allcontents & contentsMask) ) { return 0; } l = CM_PointLeafnum_r (pBSPData, p, headnode); // iterate the leaf brushes and check for intersection with each one cleaf_t &leaf = pBSPData->map_leafs[l]; if ( leaf.cluster < 0 ) return leaf.contents; int nContents = 0; const unsigned short *pBrushList = &pBSPData->map_leafbrushes[leaf.firstleafbrush]; for ( int i = 0; i < leaf.numleafbrushes; i++ ) { cbrush_t *pBrush = &pBSPData->map_brushes[ pBrushList[i] ]; // only consider brushes that have contents if ( !pBrush->contents ) continue; if ( pBrush->IsBox() ) { // special case for box brush cboxbrush_t *pBox = &pBSPData->map_boxbrushes[pBrush->GetBox()]; if ( IsPointInBox( p, pBox->mins, pBox->maxs ) ) { nContents |= pBrush->contents; } } else { // must be on the back of each brush side to be inside, skip bevels because they aren't necessary for testing points cbrushside_t * RESTRICT pSide = &pBSPData->map_brushsides[pBrush->firstbrushside]; bool bInside = true; for ( const cbrushside_t * const pSideLimit = pSide + pBrush->numsides; pSide < pSideLimit; pSide++ ) { if ( pSide->bBevel ) continue; float flDist = DotProduct( pSide->plane->normal, p ) - pSide->plane->dist; // outside plane, no intersection if ( flDist > 0.0f ) { bInside = false; break; } } if ( bInside ) { nContents |= pBrush->contents; } } } // point wasn't inside any brushes so return empty return nContents; } /* ================== CM_TransformedPointContents Handles offseting and rotation of the end points for moving and rotating entities ================== */ int CM_TransformedPointContents ( const Vector& p, int headnode, const Vector& origin, QAngle const& angles) { Vector p_l; Vector temp; Vector forward, right, up; int l; // get the current collision bsp -- there is only one! CCollisionBSPData *pBSPData = GetCollisionBSPData(); // subtract origin offset VectorSubtract (p, origin, p_l); // rotate start and end into the models frame of reference if ( angles[0] || angles[1] || angles[2] ) { AngleVectors (angles, &forward, &right, &up); VectorCopy (p_l, temp); p_l[0] = DotProduct (temp, forward); p_l[1] = -DotProduct (temp, right); p_l[2] = DotProduct (temp, up); } l = CM_PointLeafnum_r (pBSPData, p_l, headnode); return pBSPData->map_leafs[l].contents; } /* =============================================================================== BOX TRACING =============================================================================== */ // Custom SIMD implementation for box brushes const fltx4 Four_DistEpsilons={DIST_EPSILON,DIST_EPSILON,DIST_EPSILON,DIST_EPSILON}; const int32 ALIGN16 g_CubeFaceIndex0[4] ALIGN16_POST = {0,1,2,-1}; const int32 ALIGN16 g_CubeFaceIndex1[4] ALIGN16_POST = {3,4,5,-1}; bool IntersectRayWithBoxBrush( TraceInfo_t *pTraceInfo, const cbrush_t *pBrush, cboxbrush_t *pBox ) { // Load the unaligned ray/box parameters into SIMD registers fltx4 start = LoadUnaligned3SIMD(pTraceInfo->m_start.Base()); fltx4 extents = LoadUnaligned3SIMD(pTraceInfo->m_extents.Base()); fltx4 delta = LoadUnaligned3SIMD(pTraceInfo->m_delta.Base()); fltx4 boxMins = LoadAlignedSIMD( pBox->mins.Base() ); fltx4 boxMaxs = LoadAlignedSIMD( pBox->maxs.Base() ); // compute the mins/maxs of the box expanded by the ray extents // relocate the problem so that the ray start is at the origin. fltx4 offsetMins = SubSIMD(boxMins, start); fltx4 offsetMaxs = SubSIMD(boxMaxs, start); fltx4 offsetMinsExpanded = SubSIMD( offsetMins, extents ); fltx4 offsetMaxsExpanded = AddSIMD( offsetMaxs, extents ); // Check to see if both the origin (start point) and the end point (delta) are on the front side // of any of the box sides - if so there can be no intersection bi32x4 startOutMins = CmpLtSIMD(Four_Zeros, offsetMinsExpanded); bi32x4 endOutMins = CmpLtSIMD(delta,offsetMinsExpanded); bi32x4 minsMask = AndSIMD( startOutMins, endOutMins ); bi32x4 startOutMaxs = CmpGtSIMD(Four_Zeros, offsetMaxsExpanded); bi32x4 endOutMaxs = CmpGtSIMD(delta,offsetMaxsExpanded); bi32x4 maxsMask = AndSIMD( startOutMaxs, endOutMaxs ); if ( IsAnyNegative(SetWToZeroSIMD(OrSIMD(minsMask,maxsMask)))) return false; bi32x4 crossPlane = OrSIMD(XorSIMD(startOutMins,endOutMins), XorSIMD(startOutMaxs,endOutMaxs)); // now build the per-axis interval of t for intersections fltx4 invDelta = LoadUnaligned3SIMD(pTraceInfo->m_invDelta.Base()); fltx4 tmins = MulSIMD( offsetMinsExpanded, invDelta ); fltx4 tmaxs = MulSIMD( offsetMaxsExpanded, invDelta ); // now sort the interval per axis fltx4 mint = MinSIMD( tmins, tmaxs ); fltx4 maxt = MaxSIMD( tmins, tmaxs ); // only axes where we cross a plane are relevant mint = MaskedAssign( crossPlane, mint, Four_Negative_FLT_MAX ); maxt = MaskedAssign( crossPlane, maxt, Four_FLT_MAX ); // now find the intersection of the intervals on all axes fltx4 firstOut = FindLowestSIMD3(maxt); fltx4 lastIn = FindHighestSIMD3(mint); // NOTE: This is really a scalar quantity now [t0,t1] == [lastIn,firstOut] firstOut = MinSIMD(firstOut, Four_Ones); lastIn = MaxSIMD(lastIn, Four_Zeros); // If the final interval is valid lastInm_trace; // this condition is copied from the brush case to avoid hitting an assert and // overwriting a previous start solid with a new shorter fraction if ( bStartOut && pTraceInfo->m_ispoint && pTrace->fractionleftsolid > t1 ) { bStartOut = false; } if ( !bStartOut ) { float t2 = SubFloat(firstOut,0); pTrace->startsolid = true; pTrace->contents = pBrush->contents; if ( t2 >= 1.0f ) { pTrace->allsolid = true; pTrace->fraction = 0.0f; } else if ( t2 > pTrace->fractionleftsolid ) { pTrace->fractionleftsolid = t2; if (pTrace->fraction <= t2) { pTrace->fraction = 1.0f; pTrace->surface = pTraceInfo->m_pBSPData->nullsurface; } } } else { static const int signbits[3]={1,2,4}; if ( t1 < pTrace->fraction ) { pTraceInfo->m_bDispHit = false; pTrace->fraction = t1; pTrace->plane.normal = vec3_origin; pTrace->surface = *pTraceInfo->m_pBSPData->GetSurfaceAtIndex( pBox->surfaceIndex[faceIndex] ); if ( faceIndex >= 3 ) { faceIndex -= 3; pTrace->plane.dist = pBox->maxs[faceIndex]; pTrace->plane.normal[faceIndex] = 1.0f; pTrace->plane.signbits = 0; } else { pTrace->plane.dist = -pBox->mins[faceIndex]; pTrace->plane.normal[faceIndex] = -1.0f; pTrace->plane.signbits = signbits[faceIndex]; } pTrace->plane.type = faceIndex; pTrace->contents = pBrush->contents; return true; } } } } return false; } // slightly different version of the above. This folds in more of the trace_t output because CM_ComputeTraceEndpts() isn't called after this // so this routine needs to properly compute start/end points and fractions in all cases bool IntersectRayWithBox( const Ray_t &ray, const VectorAligned &inInvDelta, const VectorAligned &inBoxMins, const VectorAligned &inBoxMaxs, trace_t *RESTRICT pTrace ) { // mark trace as not hitting pTrace->startsolid = false; pTrace->allsolid = false; pTrace->fraction = 1.0f; // Load the unaligned ray/box parameters into SIMD registers fltx4 start = LoadUnaligned3SIMD(ray.m_Start.Base()); fltx4 extents = LoadUnaligned3SIMD(ray.m_Extents.Base()); fltx4 delta = LoadUnaligned3SIMD(ray.m_Delta.Base()); fltx4 boxMins = LoadAlignedSIMD( inBoxMins.Base() ); fltx4 boxMaxs = LoadAlignedSIMD( inBoxMaxs.Base() ); // compute the mins/maxs of the box expanded by the ray extents // relocate the problem so that the ray start is at the origin. fltx4 offsetMins = SubSIMD(boxMins, start); fltx4 offsetMaxs = SubSIMD(boxMaxs, start); fltx4 offsetMinsExpanded = SubSIMD(offsetMins, extents); fltx4 offsetMaxsExpanded = AddSIMD(offsetMaxs, extents); // Check to see if both the origin (start point) and the end point (delta) are on the front side // of any of the box sides - if so there can be no intersection bi32x4 startOutMins = CmpLtSIMD(Four_Zeros, offsetMinsExpanded); bi32x4 endOutMins = CmpLtSIMD(delta,offsetMinsExpanded); bi32x4 minsMask = AndSIMD( startOutMins, endOutMins ); bi32x4 startOutMaxs = CmpGtSIMD(Four_Zeros, offsetMaxsExpanded); bi32x4 endOutMaxs = CmpGtSIMD(delta,offsetMaxsExpanded); bi32x4 maxsMask = AndSIMD( startOutMaxs, endOutMaxs ); if ( IsAnyNegative(SetWToZeroSIMD(OrSIMD(minsMask,maxsMask)))) return false; bi32x4 crossPlane = OrSIMD(XorSIMD(startOutMins,endOutMins), XorSIMD(startOutMaxs,endOutMaxs)); // now build the per-axis interval of t for intersections fltx4 invDelta = LoadAlignedSIMD(inInvDelta.Base()); fltx4 tmins = MulSIMD( offsetMinsExpanded, invDelta ); fltx4 tmaxs = MulSIMD( offsetMaxsExpanded, invDelta ); // now sort the interval per axis fltx4 mint = MinSIMD( tmins, tmaxs ); fltx4 maxt = MaxSIMD( tmins, tmaxs ); // only axes where we cross a plane are relevant mint = MaskedAssign( crossPlane, mint, Four_Negative_FLT_MAX ); maxt = MaskedAssign( crossPlane, maxt, Four_FLT_MAX ); // now find the intersection of the intervals on all axes fltx4 firstOut = FindLowestSIMD3(maxt); fltx4 lastIn = FindHighestSIMD3(mint); // NOTE: This is really a scalar quantity now [t0,t1] == [lastIn,firstOut] firstOut = MinSIMD(firstOut, Four_Ones); lastIn = MaxSIMD(lastIn, Four_Zeros); // If the final interval is valid lastInfractionleftsolid > t1 ) { bStartOut = false; } if ( !bStartOut ) { float t2 = SubFloat(firstOut,0); pTrace->startsolid = true; pTrace->contents = CONTENTS_SOLID; pTrace->fraction = 0.0f; pTrace->startpos = ray.m_Start + ray.m_StartOffset; pTrace->endpos = pTrace->startpos; if ( t2 >= 1.0f ) { pTrace->allsolid = true; } else if ( t2 > pTrace->fractionleftsolid ) { pTrace->fractionleftsolid = t2; pTrace->startpos += ray.m_Delta * pTrace->fractionleftsolid; } return true; } else { static const int signbits[3]={1,2,4}; if ( t1 <= 1.0f ) { pTrace->fraction = t1; pTrace->plane.normal = vec3_origin; if ( faceIndex >= 3 ) { faceIndex -= 3; pTrace->plane.dist = inBoxMaxs[faceIndex]; pTrace->plane.normal[faceIndex] = 1.0f; pTrace->plane.signbits = 0; } else { pTrace->plane.dist = -inBoxMins[faceIndex]; pTrace->plane.normal[faceIndex] = -1.0f; pTrace->plane.signbits = signbits[faceIndex]; } pTrace->plane.type = faceIndex; pTrace->contents = CONTENTS_SOLID; Vector start; VectorAdd( ray.m_Start, ray.m_StartOffset, start ); if (pTrace->fraction == 1) { VectorAdd(start, ray.m_Delta, pTrace->endpos); } else { VectorMA( start, pTrace->fraction, ray.m_Delta, pTrace->endpos ); } return true; } } } } return false; } //const fltx4 Four_DistEpsilons = { DIST_EPSILON, DIST_EPSILON, DIST_EPSILON, DIST_EPSILON }; const fltx4 Four_NegDistEpsilons = { -DIST_EPSILON, -DIST_EPSILON, -DIST_EPSILON, -DIST_EPSILON }; const fltx4 Four_NegEpsilons = { -FLT_EPSILON, -FLT_EPSILON, -FLT_EPSILON, -FLT_EPSILON }; const fltx4 Four_NegOnes = { -1.0, -1.0, -1.0, -1.0 }; FORCEINLINE fltx4 ShuffleABXY( const fltx4 &abcd, const fltx4 &xyzw ) { return _mm_shuffle_ps( abcd, xyzw, MM_SHUFFLE_REV( 0, 1, 0, 1 ) ); } FORCEINLINE fltx4 ShuffleCDZW( const fltx4 &abcd, const fltx4 &xyzw ) { return _mm_shuffle_ps( abcd, xyzw, MM_SHUFFLE_REV( 2,3,2,3 ) ); } FORCEINLINE fltx4 ShuffleXZYW( const fltx4 &xyzw ) { return _mm_shuffle_ps( xyzw, xyzw, MM_SHUFFLE_REV( 0,2,1,3 ) ); } FORCEINLINE fltx4 ShuffleYWXZ( const fltx4 &xyzw ) { return _mm_shuffle_ps( xyzw, xyzw, MM_SHUFFLE_REV( 1, 3, 0, 2 ) ); } FORCEINLINE fltx4 ShuffleZWXY( const fltx4 &xyzw ) { return _mm_shuffle_ps( xyzw, xyzw, MM_SHUFFLE_REV( 2, 3, 0, 1 ) ); } FORCEINLINE fltx4 ShuffleYZWX( const fltx4 &xyzw ) { return _mm_shuffle_ps( xyzw, xyzw, MM_SHUFFLE_REV( 1, 2, 3, 0 ) ); } FORCEINLINE fltx4 ShuffleAAXX( const fltx4 &abcd, const fltx4 &xyzw ) { return _mm_shuffle_ps( abcd, xyzw, MM_SHUFFLE_REV( 0,0, 0,0 ) ); } FORCEINLINE fltx4 ShuffleCCZZ( const fltx4 &abcd, const fltx4 &xyzw ) { return _mm_shuffle_ps( abcd, xyzw, MM_SHUFFLE_REV( 2,2,2,2 ) ); } FORCEINLINE int IntersectOcclusionInterval( fltx4 d0[ 2 ], fltx4 d1[ 2 ], fltx4 f4EnterNum[ 2 ], fltx4 f4EnterDenum[ 2 ], fltx4 f4LeaveNum[ 2 ], fltx4 f4LeaveDenum[ 2 ] ) { // We enter the polytope if d0-d1 >= 0, and leave if d0-d1 < 0. Note that the math works when d0-d1==0: we enter at -inf or +inf correctly. -inf is ignored, +inf means we never enter, the line is outside of the polytope and we can terminate. The same line of reasoning works with the Leave side fltx4 delta[ 2 ] = { d0[ 0 ] - d1[ 0 ], d0[ 1 ] - d1[ 1 ] }; fltx4 isLeaving[ 2 ] = { CmpLtSIMD( delta[ 0 ], Four_Zeros ), CmpLtSIMD( delta[ 1 ], Four_Zeros ) }; // is the ray leaving the hemispace (moving outside the plane, moving along the normal; movement is from d0 to d1)? fltx4 gtEnter[ 2 ] = { CmpLtSIMD( f4EnterNum[ 0 ] * delta[ 0 ], d0[ 0 ] * f4EnterDenum[ 0 ] ), CmpLtSIMD( f4EnterNum[ 1 ] * delta[ 1 ], d0[ 1 ] * f4EnterDenum[ 1 ] ) }; // valid when is Entering (!isLeaving) only fltx4 ltLeave[ 2 ] = { CmpGtSIMD( f4LeaveNum[ 0 ] * delta[ 0 ], d0[ 0 ] * f4LeaveDenum[ 0 ] ), CmpGtSIMD( f4LeaveNum[ 1 ] * delta[ 1 ], d0[ 1 ] * f4LeaveDenum[ 1 ] ) }; // valid when isLeaving only : is the d0/delta < Leave? then we'll update the Leave value fltx4 maskNewLeave[ 2 ] = { AndSIMD( isLeaving[ 0 ], ltLeave[ 0 ] ), AndSIMD( isLeaving[ 1 ], ltLeave[ 1 ] ) }; f4LeaveNum[ 0 ] = MaskedAssign( maskNewLeave[ 0 ], d0[ 0 ], f4LeaveNum[ 0 ] ); f4LeaveNum[ 1 ] = MaskedAssign( maskNewLeave[ 1 ], d0[ 1 ], f4LeaveNum[ 1 ] ); f4LeaveDenum[ 0 ] = MaskedAssign( maskNewLeave[ 0 ], delta[ 0 ], f4LeaveDenum[ 0 ] ); f4LeaveDenum[ 1 ] = MaskedAssign( maskNewLeave[ 1 ], delta[ 1 ], f4LeaveDenum[ 1 ] ); fltx4 maskNewEnter[ 2 ] = { AndNotSIMD( isLeaving[ 0 ], gtEnter[ 0 ] ), AndNotSIMD( isLeaving[ 1 ], gtEnter[ 1 ] ) }; f4EnterNum[ 0 ] = MaskedAssign( maskNewEnter[ 0 ], d0[ 0 ], f4EnterNum[ 0 ] ); f4EnterNum[ 1 ] = MaskedAssign( maskNewEnter[ 1 ], d0[ 1 ], f4EnterNum[ 1 ] ); f4EnterDenum[ 0 ] = MaskedAssign( maskNewEnter[ 0 ], delta[ 0 ], f4EnterDenum[ 0 ] ); f4EnterDenum[ 1 ] = MaskedAssign( maskNewEnter[ 1 ], delta[ 1 ], f4EnterDenum[ 1 ] ); fltx4 maskEnterAfterLeave[ 2 ] = { CmpLtSIMD( f4EnterNum[ 0 ] * f4LeaveDenum[ 0 ], f4EnterDenum[ 0 ] * f4LeaveNum[ 0 ] ), CmpLtSIMD( f4EnterNum[ 1 ] * f4LeaveDenum[ 1 ], f4EnterDenum[ 1 ] * f4LeaveNum[ 1 ] ) }; return TestSignSIMD( OrSIMD( maskEnterAfterLeave[ 0 ], maskEnterAfterLeave[ 1 ] ) ); // non-0 means one of the rays enters after it leaves the polytope, i.e. it's not intersected by polytope, so exit early (ish) // non-0 means "not occluded" } static fltx4 Four_OneAndRcpMargin = { 1.001f, 1.001f, 1.001f, 1.001f }; static fltx4 Four_NegRcpMargin = { -0.001f, -0.001f, -0.001f, -0.001f }; bool CM_BrushOcclusionPass( COcclusionInfo &oi, const cbrush_t * RESTRICT brush ) { fltx4 fracHit[ 2 ] = { Four_Ones, Four_Ones }; if ( brush->IsBox() ) { cboxbrush_t *pBox = &oi.m_pBSPData->map_boxbrushes[ brush->GetBox() ]; /* if ( oi.m_pDebugLog ) { oi.m_pDebugLog->AddBox( CFmtStr( "hit%04d_box_brush%d", oi.m_pDebugLog->GetPrimCount(), brush->GetBox() ).Get(), "relevant", pBox->mins, pBox->maxs ); } */ /* if ( !oi.m_pResults ) { const char *pCodePath; bool bFullyOccluded = OccludeWithBoxBrush( oi, brush, pBox, pCodePath ); return bFullyOccluded; } */ fltx4 f4Mins = LoadUnaligned3SIMD( &pBox->mins ); fltx4 f4Maxs = LoadUnaligned3SIMD( &pBox->maxs ); // d12_XnXYnY means d1 (the height of the end point), above the plane 2 (+Y: {+X,-X,+Y,-Y,+Z,-Z}[2]), kept in SIMD in the order {+X,-X,+Y,-Y} // for +X plane: (+Y is the same) // d0 = start.x - max.x, d1 = end.x - max.x fltx4 maxXXYY = ShuffleXXYY( f4Maxs ); fltx4 d0U = oi.m_StartXnXYnY - maxXXYY, d1U = oi.m_EndXnXYnY - maxXXYY; // U = 0_XnXYnY, plane {+X,+X,+Y,+Y} dot point {posX, negX, posY, negY} // for -X plane: (-Y is the same) // d0 = min.x - start.x, d1 = min.x - end.x fltx4 minXXYY = ShuffleXXYY( f4Mins ); fltx4 d0V = minXXYY - oi.m_StartXnXYnY, d1V = minXXYY - oi.m_EndXnXYnY; // V = 1_XnXYnY, plane {-X,-X,-Y,-Y} dot point {posX, negX, posY, negY} // for +Z plane: // d01 = {start.Z - max.z, start.nZ - max.z, end.Z - max.z, end.nZ - max.z } fltx4 d01Z = oi.m_StartEndZnZ - SplatZSIMD( f4Maxs ), d01nZ = SplatZSIMD( f4Mins ) - oi.m_StartEndZnZ; fltx4 d0ZnZ = ShuffleABXY(d01Z, d01nZ), d1ZnZ = ShuffleCDZW(d01Z, d01nZ); // ZnZZnZ, plane { +Z, +Z, -Z, -Z } dot point {posZ, negZ, posZ, negZ} // every single ray/interval must lie, at least partially, behind each plane (negatively). At least one of d0 and d1, for both X and nX shifts, must be negative. // If there's a single one interval with both d0 >= 0 and d1 >= 0 it means there's a ray that misses this box completely, and we don't have full occlusion if ( TestSignSIMD( AndSIMD( AndSIMD( OrSIMD( d0U, d1U ), OrSIMD( d0V, d1V ) ), OrSIMD( d0ZnZ, d1ZnZ ) ) ) != 15 ) { // there's a separating axis along one of the orts, both d0 and d1 lie on one side of it return false; } // PERF: Even though the following code is gigantic, it's not executed for many boxes // We enter the polytope if d1-d2 >= 0, and leave if d1-d2 < 0. Note that the math works when d1-d2==0: we enter at -inf or +inf correctly. -inf is ignored, +inf means we never enter, the line is outside of the polytope and we can terminate. The same line of reasoning works with the Leave side { fltx4 deltaU = d0U - d1U; // this should be the same as deltaV, with flipped signs Assert( IsAllGreaterThanOrEq( Four_DistEpsilons, fabs( deltaU + ( d0V - d1V ) ) ) ); fltx4 deltaZ = d0ZnZ - d1ZnZ; // same with +Z and -Z planes Assert( IsAllGreaterThanOrEq( Four_DistEpsilons, fabs( deltaZ + ShuffleZWXY( deltaZ ) ) ) ); //fltx4 isDirPosU = CmpGtSIMD( deltaU, Four_Zeros );// , isDirNegU = CmpLtSIMD( deltaU, Four_NegEpsilons ); // Pos => use U over V; Neg => use V over U //fltx4 isDirPosZ = CmpGtSIMD( deltaZ, Four_Zeros );// , isDirNegZ = CmpLtSIMD( deltaZ, Four_NegEpsilons ); // note: this mask is valid in the XY and inverted in ZW //fltx4 deltaUabs = fabs( deltaU ), deltaZabs = fabs( deltaZ ); //fltx4 isNonParallelU = CmpGtSIMD( deltaUabs, Four_Epsilons ), isNonParallelZ = CmpGtSIMD( deltaZabs, Four_Epsilons ); fltx4 deltaUrcp = ReciprocalEstSIMD( deltaU ), deltaZrcp = ReciprocalEstSIMD( deltaZ ); //fltx4 fracU = AndSIMD( isNonParallelU, MaskedAssign( isDirPosU, d0U, d0V ) * deltaUrcp ), fracZ = AndSIMD( isNonParallelZ, MaskedAssign( isDirPosZ, d0ZnZ, ShuffleZWXY( d0ZnZ ) ) * deltaZrcp ); fltx4 f4Size = f4Maxs - f4Mins; fltx4 fracUa = d0U * deltaUrcp, fracUb = ( d0U + ShuffleXXYY( f4Size ) ) * deltaUrcp; fltx4 fracUmin = MinSIMD( fracUa, fracUb ), fracUmax = MaxSIMD( fracUa, fracUb ); fltx4 fracZa = d01Z * deltaZrcp, fracZb = ( d01Z + SplatZSIMD( f4Size ) ) * deltaZrcp; fltx4 fracZmin = MinSIMD( fracZa, fracZb ), fracZmax = MaxSIMD( fracZa, fracZb ); Assert( IsAllGreaterThanOrEq( Four_OneAndRcpMargin, MaxSIMD( fracUmin, fracZmin ) ) ); // at least one ray starts further than 1 along at least one dimension? This shouldn't happen // There may be large-ish round-off errors due to using reciprocal estimates (deltaUrcp and deltaZrcp) // There are 8 rays, in all combinations of +- with X, Y, Z - those are the intervals we'll need to intersect: // +-,+-,+-: fracU01, fracU23, fracZ01 // so, fracMin[2] = { { max(u0,u2,z0),max(u1,u2,z0),max(u0,u3,z0), max(u1, u3, z0) }, { max(u0,u2,z1),max(u1,u2,z1),max(u0,u3,z1), max(u1, u3, z1) } } fltx4 fracUminTmp = MaxSIMD( ShuffleXYXY( fracUmin ), ShuffleZZWW( fracUmin ) ), fracUmaxTmp = MinSIMD( ShuffleXYXY( fracUmax ), ShuffleZZWW( fracUmax ) ); fracHit[ 0 ] = MaxSIMD( Four_Zeros, MaxSIMD( fracUminTmp, SplatXSIMD( fracZmin ) ) ); fracHit[ 1 ] = MaxSIMD( Four_Zeros, MaxSIMD( fracUminTmp, SplatYSIMD( fracZmin ) ) ); fltx4 fracHitMax[ 2 ] = { MinSIMD( fracUmaxTmp, SplatXSIMD( fracZmax ) ), MinSIMD( fracUmaxTmp, SplatYSIMD( fracZmax ) ) }; // now we need to intersect all found intervals, and if we end up with empty intersection or starting beyond +1, then there's at least one ray that doesn't get blocked and we have no full occlusion if ( TestSignSIMD( OrSIMD( fracHitMax[ 0 ] - fracHit[ 0 ], fracHitMax[ 1 ] - fracHit[ 1 ] ) ) ) { // one of the intervals is empty, ray hasn't hit the box, exit return false; } } } else// support for non-box brushes { cbrushside_t * RESTRICT pSidesBegin = &oi.m_pBSPData->map_brushsides[ brush->firstbrushside ], *pSide = pSidesBegin; /* if ( oi.m_pDebugLog ) { oi.m_pDebugLog->AddBrush( CFmtStr( "hit%04d_brush%d_%dside", oi.m_pDebugLog->GetPrimCount(), brush->firstbrushside, brush->numsides ).Get(), "relevant", pSidesBegin, brush->numsides ); } */ fltx4 f4EnterNum[ 2 ] = { Four_Zeros, Four_Zeros }, f4EnterDenum[ 2 ] = { Four_Ones, Four_Ones }; fltx4 f4LeaveNum[ 2 ] = { Four_NegOnes, Four_NegOnes }, f4LeaveDenum[ 2 ] = { Four_NegOnes, Four_NegOnes }; for ( const cbrushside_t * const pSidesEnd = pSide + brush->numsides; pSide < pSidesEnd; pSide++ ) { // don't trace rays against bevel planes if ( pSide->bBevel ) continue; DbgAssert( uintp( &pSide->plane->dist ) - uintp( &pSide->plane->normal ) == 12 ); // dist must follow the normal exactly fltx4 plane = LoadUnalignedSIMD( &pSide->plane->normal ); //fltx4 dist = SplatWSIMD( plane ); //fltx4 planeNormalX = SplatXSIMD( plane ), planeNormalY = SplatYSIMD( plane ), planeNormalZ = SplatZSIMD( plane ); // fltx4 d0[ 2 ] = { oi.GetStartX() * planeNormalX + oi.GetStartY() * planeNormalY + oi.GetStartZ0() * planeNormalZ - dist, oi.GetStartX() * planeNormalX + oi.GetStartY() * planeNormalY + oi.GetStartZ1() * planeNormalZ - dist }; fltx4 planeXXYY = ShuffleXXYY( plane ); fltx4 startPlaneXnXYnY = oi.m_StartXnXYnY * planeXXYY, endPlaneXnXYnY = oi.m_EndXnXYnY * planeXXYY, startEndPlaneZnZ_dist = oi.m_StartEndZnZ * SplatZSIMD( plane ) - SplatWSIMD( plane ); fltx4 d0xy = ShuffleXYXY( startPlaneXnXYnY ) + ShuffleZZWW( startPlaneXnXYnY ); fltx4 d0[ 2 ] = { d0xy + SplatXSIMD( startEndPlaneZnZ_dist ), d0xy + SplatYSIMD( startEndPlaneZnZ_dist ) }; fltx4 d1xy = ShuffleXYXY( endPlaneXnXYnY ) + ShuffleZZWW( endPlaneXnXYnY ); fltx4 d1[ 2 ] = { d1xy + SplatZSIMD( startEndPlaneZnZ_dist ), d1xy + SplatWSIMD( startEndPlaneZnZ_dist ) }; if ( ( TestSignSIMD( OrSIMD( d0[ 0 ], d1[ 0 ] ) ) & TestSignSIMD( OrSIMD( d0[ 1 ], d1[ 1 ] ) ) ) != 15 ) { // we found the separating plane for one of the 8 rays. // That ray's d1 >= 0 && d2 >= 0, it lies completely outside the brush side, so it doesn't intersect the brush, guaranteed. // Therefore the whole probe is not occluded. // Therefore we can return false ("not occluded") return false; } if ( IntersectOcclusionInterval( d0, d1, f4EnterNum, f4EnterDenum, f4LeaveNum, f4LeaveDenum ) ) { // non-0 means one of the rays enters after it leaves the polytope, i.e. it's not intersected by polytope, so exit early (ish) // non-0 means "not occluded" return false; } } fracHit[ 0 ] = f4EnterNum[ 0 ] * ReciprocalEstSIMD( f4EnterDenum[ 0 ] ); fracHit[ 1 ] = f4EnterNum[ 1 ] * ReciprocalEstSIMD( f4EnterDenum[ 1 ] ); }; if ( oi.m_pResults ) { // compute the bbox of the ends of the rays. This is only executed at most once per occlusion query, so it's not a critical path fltx4 fracAft[ 2 ] = { Four_Ones - fracHit[ 0 ], Four_Ones - fracHit[ 1 ] }; Assert( IsAllGreaterThanOrEq( fracHit[ 0 ], Four_Zeros ) && IsAllGreaterThanOrEq( fracHit[ 1 ], Four_Zeros ) && IsAllGreaterThanOrEq( Four_OneAndRcpMargin, fracHit[ 0 ] ) && IsAllGreaterThanOrEq( Four_OneAndRcpMargin, fracHit[ 1 ] ) ); Assert( IsAllGreaterThanOrEq( fracAft[ 0 ], Four_NegRcpMargin ) && IsAllGreaterThanOrEq( fracAft[ 1 ], Four_NegRcpMargin ) && IsAllGreaterThanOrEq( Four_Ones, fracAft[ 0 ] ) && IsAllGreaterThanOrEq( Four_Ones, fracAft[ 1 ] ) ); // the 8 start points' coordinates will need to multiply by enter; the end points' coordinates will need to multiply by 1-enter fltx4 startX = oi.GetStartX(), endX = oi.GetEndX(); fltx4 hitX[ 2 ] = { fracHit[ 0 ] * endX + fracAft[ 0 ] * startX, fracHit[ 1 ] * endX + fracAft[ 1 ] * startX }; fltx4 minXXXX = MinSIMD( hitX[ 0 ], hitX[ 1 ] ), maxXXXX = MaxSIMD( hitX[ 0 ], hitX[ 1 ] ); // 4 values will need to be collapsed to the horizontal min/max in each register fltx4 startY = oi.GetStartY(), endY = oi.GetEndY(); fltx4 hitY[ 2 ] = { fracHit[ 0 ] * endY + fracAft[ 0 ] * startY, fracHit[ 1 ] * endY + fracAft[ 1 ] * startY }; fltx4 minYYYY = MinSIMD( hitY[ 0 ], hitY[ 1 ] ), maxYYYY = MaxSIMD( hitY[ 0 ], hitY[ 1 ] ); // 4 values will need to be collapsed to the horizontal min/max in each register fltx4 minXXYY = MinSIMD( ShuffleABXY( minXXXX, minYYYY ), ShuffleCDZW( minXXXX, minYYYY ) ), maxXXYY = MaxSIMD( ShuffleABXY( maxXXXX, maxYYYY ), ShuffleCDZW( maxXXXX, maxYYYY ) ); fltx4 minXYXY = MinSIMD( ShuffleXZYW( minXXYY ), ShuffleYWXZ( minXXYY ) ), maxXYXY = MaxSIMD( ShuffleXZYW( maxXXYY ), ShuffleYWXZ( maxXXYY ) ); fltx4 hitZ[ 2 ] = { fracHit[ 0 ] * oi.GetEndZ0() + fracAft[ 0 ] * oi.GetStartZ0(), fracHit[ 1 ] * oi.GetEndZ1() + fracAft[ 1 ] * oi.GetStartZ1() }; fltx4 minZZZZ = MinSIMD( hitZ[ 0 ], hitZ[ 1 ] ), maxZZZZ = MaxSIMD( hitZ[ 0 ], hitZ[ 1 ] ); // 4 values will need to be collapsed to the horizontal min/max in each register fltx4 minZZ = MinSIMD( minZZZZ, ShuffleZWXY( minZZZZ ) ), maxZZ = MaxSIMD( maxZZZZ, ShuffleZWXY( maxZZZZ ) ); fltx4 minZ = MinSIMD( minZZ, ShuffleYZWX( minZZ ) ), maxZ = MaxSIMD( maxZZ, ShuffleYZWX( maxZZ ) ); fltx4 minXYZZ = ShuffleABXY( minXYXY, minZ ); StoreAligned3SIMD( &oi.m_pResults->vEndMin, minXYZZ ); fltx4 maxXYZZ = ShuffleABXY( maxXYXY, maxZ ); StoreAligned3SIMD( &oi.m_pResults->vEndMax, maxXYZZ ); Assert( IsAllGreaterThanOrEq( maxXYZZ, minXYZZ ) ); } return true; // yes, we didn't exit early, so it means all rays are intersected by the polytope, which means we're completely occluded } /* ================ CM_ClipBoxToBrush ================ */ template void FASTCALL CM_ClipBoxToBrush( TraceInfo_t * RESTRICT pTraceInfo, const cbrush_t * RESTRICT brush ) { if ( brush->IsBox() ) { cboxbrush_t *pBox = &pTraceInfo->m_pBSPData->map_boxbrushes[brush->GetBox()]; IntersectRayWithBoxBrush( pTraceInfo, brush, pBox ); return; } if (!brush->numsides) return; trace_t * RESTRICT trace = &pTraceInfo->m_trace; const Vector& p1 = pTraceInfo->m_start; const Vector& p2= pTraceInfo->m_end; int brushContents = brush->contents; #ifdef COUNT_COLLISIONS g_CollisionCounts.m_BrushTraces++; #endif float enterfrac = NEVER_UPDATED; float leavefrac = 1.f; bool getout = false; bool startout = false; cbrushside_t* leadside = NULL; float dist; cbrushside_t * RESTRICT side = &pTraceInfo->m_pBSPData->map_brushsides[brush->firstbrushside]; for ( const cbrushside_t * const sidelimit = side + brush->numsides; side < sidelimit; side++ ) { cplane_t *plane = side->plane; const Vector &planeNormal = plane->normal; if (!IS_POINT) { // general box case // push the plane out apropriately for mins/maxs dist = DotProductAbs( planeNormal, pTraceInfo->m_extents ); dist = plane->dist + dist; } else { // special point case dist = plane->dist; // don't trace rays against bevel planes if ( side->bBevel ) continue; } float d1 = DotProduct (p1, planeNormal) - dist; float d2 = DotProduct (p2, planeNormal) - dist; // if completely in front of face, no intersection if( d1 > 0.f ) { startout = true; // d1 > 0.f && d2 > 0.f if( d2 > 0.f ) return; } else { // d1 <= 0.f && d2 <= 0.f if( d2 <= 0.f ) continue; // d2 > 0.f getout = true; } // crosses face if (d1 > d2) { // enter // NOTE: This could be negative if d1 is less than the epsilon. // If the trace is short (d1-d2 is small) then it could produce a large // negative fraction. float f = (d1-DIST_EPSILON); if ( f < 0.f ) f = 0.f; f = f / (d1-d2); if (f > enterfrac) { enterfrac = f; leadside = side; } } else { // leave float f = (d1+DIST_EPSILON) / (d1-d2); if (f < leavefrac) leavefrac = f; } } // when this happens, we entered the brush *after* leaving the previous brush. // Therefore, we're still outside! // NOTE: We only do this test against points because fractionleftsolid is // not possible to compute for brush sweeps without a *lot* more computation // So, client code will never get fractionleftsolid for box sweeps if (IS_POINT && startout) { // Add a little sludge. The sludge should already be in the fractionleftsolid // (for all intents and purposes is a leavefrac value) and enterfrac values. // Both of these values have +/- DIST_EPSILON values calculated in. Thus, I // think the test should be against "0.0." If we experience new "left solid" // problems you may want to take a closer look here! // if ((trace->fractionleftsolid - enterfrac) > -1e-6) if ((trace->fractionleftsolid - enterfrac) > 0.0f ) startout = false; } if (!startout) { // original point was inside brush trace->startsolid = true; // return starting contents trace->contents = brushContents; if (!getout) { trace->allsolid = true; trace->fraction = 0.0f; trace->fractionleftsolid = 1.0f; } else { // if leavefrac == 1, this means it's never been updated or we're in allsolid // the allsolid case was handled above if ((leavefrac != 1) && (leavefrac > trace->fractionleftsolid)) { trace->fractionleftsolid = leavefrac; // This could occur if a previous trace didn't start us in solid if (trace->fraction <= leavefrac) { trace->fraction = 1.0f; trace->surface = pTraceInfo->m_pBSPData->nullsurface; } } } return; } // We haven't hit anything at all until we've left... if (enterfrac < leavefrac) { if (enterfrac > NEVER_UPDATED && enterfrac < trace->fraction) { // WE HIT SOMETHING!!!!! if (enterfrac < 0) enterfrac = 0; trace->fraction = enterfrac; pTraceInfo->m_bDispHit = false; trace->plane = *(leadside->plane); trace->surface = *pTraceInfo->m_pBSPData->GetSurfaceAtIndex( leadside->surfaceIndex ); trace->contents = brushContents; } } } inline bool IsTraceBoxIntersectingBoxBrush( TraceInfo_t *pTraceInfo, cboxbrush_t *pBox ) { fltx4 start = LoadUnaligned3SIMD(pTraceInfo->m_start.Base()); fltx4 mins = LoadUnaligned3SIMD(pTraceInfo->m_mins.Base()); fltx4 maxs = LoadUnaligned3SIMD(pTraceInfo->m_maxs.Base()); fltx4 boxMins = LoadAlignedSIMD( pBox->mins.Base() ); fltx4 boxMaxs = LoadAlignedSIMD( pBox->maxs.Base() ); fltx4 offsetMins = AddSIMD(mins, start); fltx4 offsetMaxs = AddSIMD(maxs,start); fltx4 minsOut = MaxSIMD(boxMins, offsetMins); fltx4 maxsOut = MinSIMD(boxMaxs, offsetMaxs); bi32x4 separated = CmpGtSIMD(minsOut, maxsOut); bi32x4 sep3 = SetWToZeroSIMD(separated); return IsAllZeros(sep3); } /* ================ CM_TestBoxInBrush ================ */ void FASTCALL CM_TestBoxInBrush( TraceInfo_t *pTraceInfo, const cbrush_t *brush ) { if ( brush->IsBox()) { cboxbrush_t *pBox = &pTraceInfo->m_pBSPData->map_boxbrushes[brush->GetBox()]; if ( !IsTraceBoxIntersectingBoxBrush( pTraceInfo, pBox ) ) return; } else { if (!brush->numsides) return; const Vector& mins = pTraceInfo->m_mins; const Vector& maxs = pTraceInfo->m_maxs; const Vector& p1 = pTraceInfo->m_start; int i, j; cplane_t *plane; float dist; Vector ofs(0,0,0); float d1; cbrushside_t *side; for (i=0 ; inumsides ; i++) { side = &pTraceInfo->m_pBSPData->map_brushsides[brush->firstbrushside+i]; plane = side->plane; // FIXME: special case for axial // general box case // push the plane out appropriately for mins/maxs // FIXME: use signbits into 8 way lookup for each mins/maxs for (j=0 ; j<3 ; j++) { if (plane->normal[j] < 0) ofs[j] = maxs[j]; else ofs[j] = mins[j]; } dist = DotProduct (ofs, plane->normal); dist = plane->dist - dist; d1 = DotProduct (p1, plane->normal) - dist; // if completely in front of face, no intersection if (d1 > 0) return; } } // inside this brush trace_t *trace = &pTraceInfo->m_trace; trace->startsolid = trace->allsolid = true; trace->fraction = 0; trace->fractionleftsolid = 1.0f; trace->contents = brush->contents; } template FORCEINLINE_TEMPLATE void CM_TraceToDispList( TraceInfo_t * RESTRICT pTraceInfo, const unsigned short *pDispList, int dispListCount, float startFrac, float endFrac ) { VPROF("CM_TraceToDispList"); // // trace ray/swept box against all displacement surfaces in this leaf // TraceCounter_t * RESTRICT pCounters = 0; TraceCounter_t count = 0; if ( CHECK_COUNTERS ) { pCounters = pTraceInfo->GetDispCounters(); count = pTraceInfo->GetCount(); } if ( IsX360() || IsPS3() ) { // set up some relatively constant variables we'll use in the loop below fltx4 traceStart = LoadUnaligned3SIMD(pTraceInfo->m_start.Base()); fltx4 traceDelta = LoadUnaligned3SIMD(pTraceInfo->m_delta.Base()); fltx4 traceInvDelta = LoadUnaligned3SIMD(pTraceInfo->m_invDelta.Base()); static const fltx4 vecEpsilon = {DISPCOLL_DIST_EPSILON,DISPCOLL_DIST_EPSILON,DISPCOLL_DIST_EPSILON,DISPCOLL_DIST_EPSILON}; // only used in !IS_POINT version: fltx4 extents; if (!IS_POINT) { extents = LoadUnaligned3SIMD(pTraceInfo->m_extents.Base()); } // TODO: this loop probably ought to be unrolled so that we can make a more efficient // job of intersecting rays against boxes. The simple SIMD version used here, // though about 6x faster than the fpu version, is slower still than intersecting // against four boxes simultaneously. for( int i = 0; i < dispListCount; i++ ) { int dispIndex = pDispList[i]; alignedbbox_t * RESTRICT pDispBounds = &g_pDispBounds[dispIndex]; // only collide with objects you are interested in if( !( pDispBounds->GetContents() & pTraceInfo->m_contents ) ) continue; if( CHECK_COUNTERS && pTraceInfo->m_isswept ) { // make sure we only check this brush once per trace/stab if ( !pTraceInfo->Visit( pDispBounds->GetCounter(), count, pCounters ) ) continue; } if ( IS_POINT ) { if (!IsBoxIntersectingRay( LoadAlignedSIMD(pDispBounds->mins.Base()), LoadAlignedSIMD(pDispBounds->maxs.Base()), traceStart, traceDelta, traceInvDelta, vecEpsilon )) continue; } else { fltx4 mins = SubSIMD(LoadAlignedSIMD(pDispBounds->mins.Base()),extents); fltx4 maxs = AddSIMD(LoadAlignedSIMD(pDispBounds->maxs.Base()),extents); if (!IsBoxIntersectingRay( mins, maxs, traceStart, traceDelta, traceInvDelta, vecEpsilon )) continue; } CDispCollTree * RESTRICT pDispTree = &g_pDispCollTrees[dispIndex]; CM_TraceToDispTree( pTraceInfo, pDispTree, startFrac, endFrac ); if( !pTraceInfo->m_trace.fraction ) break; } } else { // utterly nonoptimal FPU pathway for( int i = 0; i < dispListCount; i++ ) { int dispIndex = pDispList[i]; alignedbbox_t * RESTRICT pDispBounds = &g_pDispBounds[dispIndex]; // only collide with objects you are interested in if( !( pDispBounds->GetContents() & pTraceInfo->m_contents ) ) continue; if( CHECK_COUNTERS && pTraceInfo->m_isswept ) { // make sure we only check this brush once per trace/stab if ( !pTraceInfo->Visit( pDispBounds->GetCounter(), count, pCounters ) ) continue; } if ( IS_POINT && !IsBoxIntersectingRay( pDispBounds->mins, pDispBounds->maxs, pTraceInfo->m_start, pTraceInfo->m_delta, pTraceInfo->m_invDelta, DISPCOLL_DIST_EPSILON ) ) { continue; } if ( !IS_POINT && !IsBoxIntersectingRay( pDispBounds->mins - pTraceInfo->m_extents, pDispBounds->maxs + pTraceInfo->m_extents, pTraceInfo->m_start, pTraceInfo->m_delta, pTraceInfo->m_invDelta, DISPCOLL_DIST_EPSILON ) ) { continue; } CDispCollTree * RESTRICT pDispTree = &g_pDispCollTrees[dispIndex]; CM_TraceToDispTree( pTraceInfo, pDispTree, startFrac, endFrac ); if( !pTraceInfo->m_trace.fraction ) break; } } CM_PostTraceToDispTree( pTraceInfo ); } bool IsNoDrawBrush( CCollisionBSPData *pBSPData, const int relevantContents, const int traceContents, const cbrush_t * RESTRICT pBrush ) { // Many traces rely on CONTENTS_OPAQUE always being hit, even if it is nodraw. AI blocklos brushes // need this, for instance. CS and Terror visibility checks don't want this behavior, since // blocklight brushes also are CONTENTS_OPAQUE and SURF_NODRAW, and are actually in the playable // area in several maps. // NOTE: This is no longer true - no traces should rely on hitting CONTENTS_OPAQUE unless they // actually want to hit blocklight brushes. No other brushes are marked with those bits // so it should be renamed CONTENTS_BLOCKLIGHT. CONTENTS_BLOCKLOS has its own field now // so there is no reason to ignore nodraw opaques since you can merely remove CONTENTS_OPAQUE to // get that behavior if ( relevantContents == CONTENTS_OPAQUE && ( traceContents & CONTENTS_IGNORE_NODRAW_OPAQUE ) ) { // if the only reason we hit this brush is because it is opaque, make sure it isn't nodraw if ( pBrush->IsBox() ) { cboxbrush_t *pBox = &pBSPData->map_boxbrushes[ pBrush->GetBox() ]; for ( int i = 0; i < 6; i++ ) { csurface_t *surface = pBSPData->GetSurfaceAtIndex( pBox->surfaceIndex[ i ] ); if ( surface->flags & SURF_NODRAW ) { return true; } } } else { cbrushside_t *side = &pBSPData->map_brushsides[ pBrush->firstbrushside ]; for ( int i = 0; i < pBrush->numsides; i++, side++ ) { csurface_t *surface = pBSPData->GetSurfaceAtIndex( side->surfaceIndex ); if ( surface->flags & SURF_NODRAW ) { return true; } } } } return false; } FORCEINLINE_TEMPLATE bool CM_BrushListOcclusionPass( COcclusionInfo &oi, const unsigned short *pBrushList, int brushListCount ) { // // trace ray/box sweep against all brushes in this leaf // CRangeValidatedArray & map_brushes = oi.m_pBSPData->map_brushes; for ( int ndxLeafBrush = 0; ndxLeafBrush < brushListCount; ndxLeafBrush++ ) { // get the current brush int ndxBrush = pBrushList[ ndxLeafBrush ]; cbrush_t * RESTRICT pBrush = &map_brushes[ ndxBrush ]; const int traceContents = oi.m_contents; const int relevantContents = ( pBrush->contents & traceContents ); // only collide with objects you are interested in if ( !relevantContents ) continue; if ( !oi.PrepareCheckBrush( ndxBrush ) ) continue; // already checked this brush if ( IsNoDrawBrush( oi.m_pBSPData, relevantContents, traceContents, pBrush ) ) continue; // trace against the brush and find impact point -- if any? // NOTE: pTraceInfo->m_trace.fraction == 0.0f only when trace starts inside of a brush! if( CM_BrushOcclusionPass( oi, pBrush ) ) return true; } return false; // nothing fully occludes the path } template FORCEINLINE_TEMPLATE void CM_TraceToBrushList( TraceInfo_t * RESTRICT pTraceInfo, const unsigned short *pBrushList, int brushListCount ) { // // trace ray/box sweep against all brushes in this leaf // CRangeValidatedArray & map_brushes = pTraceInfo->m_pBSPData->map_brushes; TraceCounter_t * RESTRICT pCounters = NULL; TraceCounter_t count = 0; if ( CHECK_COUNTERS ) { pCounters = pTraceInfo->GetBrushCounters(); count = pTraceInfo->GetCount(); } for( int ndxLeafBrush = 0; ndxLeafBrush < brushListCount; ndxLeafBrush++ ) { // get the current brush int ndxBrush = pBrushList[ndxLeafBrush]; cbrush_t * RESTRICT pBrush = &map_brushes[ndxBrush]; // make sure we only check this brush once per trace/stab if ( CHECK_COUNTERS && !pTraceInfo->Visit( pBrush, ndxBrush, count, pCounters ) ) continue; const int traceContents = pTraceInfo->m_contents; const int releventContents = ( pBrush->contents & traceContents ); // only collide with objects you are interested in if( !releventContents ) continue; // Many traces rely on CONTENTS_OPAQUE always being hit, even if it is nodraw. AI blocklos brushes // need this, for instance. CS and Terror visibility checks don't want this behavior, since // blocklight brushes also are CONTENTS_OPAQUE and SURF_NODRAW, and are actually in the playable // area in several maps. // NOTE: This is no longer true - no traces should rely on hitting CONTENTS_OPAQUE unless they // actually want to hit blocklight brushes. No other brushes are marked with those bits // so it should be renamed CONTENTS_BLOCKLIGHT. CONTENTS_BLOCKLOS has its own field now // so there is no reason to ignore nodraw opaques since you can merely remove CONTENTS_OPAQUE to // get that behavior if ( releventContents == CONTENTS_OPAQUE && (traceContents & CONTENTS_IGNORE_NODRAW_OPAQUE) ) { // if the only reason we hit this brush is because it is opaque, make sure it isn't nodraw bool isNoDraw = false; if ( pBrush->IsBox()) { cboxbrush_t *pBox = &pTraceInfo->m_pBSPData->map_boxbrushes[pBrush->GetBox()]; for (int i=0 ; i<6 && !isNoDraw ;i++) { csurface_t *surface = pTraceInfo->m_pBSPData->GetSurfaceAtIndex( pBox->surfaceIndex[i] ); if ( surface->flags & SURF_NODRAW ) { isNoDraw = true; break; } } } else { cbrushside_t *side = &pTraceInfo->m_pBSPData->map_brushsides[pBrush->firstbrushside]; for (int i=0 ; inumsides && !isNoDraw ;i++, side++) { csurface_t *surface = pTraceInfo->m_pBSPData->GetSurfaceAtIndex( side->surfaceIndex ); if ( surface->flags & SURF_NODRAW ) { isNoDraw = true; break; } } } if ( isNoDraw ) { continue; } } // trace against the brush and find impact point -- if any? // NOTE: pTraceInfo->m_trace.fraction == 0.0f only when trace starts inside of a brush! CM_ClipBoxToBrush( pTraceInfo, pBrush ); if( !pTraceInfo->m_trace.fraction ) return; } } bool FASTCALL CM_LeafOcclusionPass( COcclusionInfo &oi, int ndxLeaf, float startFrac, float endFrac ) { // get the leaf cleaf_t * RESTRICT pLeaf = &oi.m_pBSPData->map_leafs[ ndxLeaf ]; if ( pLeaf->numleafbrushes ) { const unsigned short *pBrushList = &oi.m_pBSPData->map_leafbrushes[pLeaf->firstleafbrush]; return CM_BrushListOcclusionPass( oi, pBrushList, pLeaf->numleafbrushes ); } // ToDo: A pass over displacement surfaces in this leaf. return false; } /* ================ CM_TraceToLeaf ================ */ template void FASTCALL CM_TraceToLeaf( TraceInfo_t * RESTRICT pTraceInfo, int ndxLeaf, float startFrac, float endFrac ) { VPROF("CM_TraceToLeaf"); // get the leaf cleaf_t * RESTRICT pLeaf = &pTraceInfo->m_pBSPData->map_leafs[ndxLeaf]; if ( pLeaf->numleafbrushes ) { const unsigned short *pBrushList = &pTraceInfo->m_pBSPData->map_leafbrushes[pLeaf->firstleafbrush]; CM_TraceToBrushList( pTraceInfo, pBrushList, pLeaf->numleafbrushes ); // TODO: this may be redundant if( pTraceInfo->m_trace.startsolid ) return; } // Collide (test) against displacement surfaces in this leaf. if( pLeaf->dispCount ) { unsigned short *pDispList = &pTraceInfo->m_pBSPData->map_dispList[pLeaf->dispListStart]; CM_TraceToDispList( pTraceInfo, pDispList, pLeaf->dispCount, startFrac, endFrac ); } } void FASTCALL CM_GetTraceDataForLeaf( TraceInfo_t * RESTRICT pTraceInfo, int ndxLeaf, CTraceListData &traceData ) { // get the leaf cleaf_t * RESTRICT pLeaf = &pTraceInfo->m_pBSPData->map_leafs[ndxLeaf]; if ((pLeaf->contents & CONTENTS_SOLID) == 0) { traceData.m_bFoundNonSolidLeaf = true; } // // add brushes to list if ( 1 ) { const int numleafbrushes = pLeaf->numleafbrushes; const int lastleafbrush = pLeaf->firstleafbrush + numleafbrushes; const CRangeValidatedArray &map_leafbrushes = pTraceInfo->m_pBSPData->map_leafbrushes; CRangeValidatedArray & map_brushes = pTraceInfo->m_pBSPData->map_brushes; TraceCounter_t * RESTRICT pCounters = pTraceInfo->GetBrushCounters(); TraceCounter_t count = pTraceInfo->GetCount(); for( int ndxLeafBrush = pLeaf->firstleafbrush; ndxLeafBrush < lastleafbrush; ndxLeafBrush++ ) { // get the current brush int ndxBrush = map_leafbrushes[ndxLeafBrush]; cbrush_t * RESTRICT pBrush = &map_brushes[ndxBrush]; // make sure we only add this brush once if ( !pTraceInfo->Visit( pBrush, ndxBrush, count, pCounters ) ) continue; traceData.m_brushList.AddToTail(ndxBrush); } } // add displacements to list if ( 1 ) { TraceCounter_t *pCounters = pTraceInfo->GetDispCounters(); TraceCounter_t count = pTraceInfo->GetCount(); // Collide (test) against displacement surfaces in this leaf. for( int i = 0; i < pLeaf->dispCount; i++ ) { int dispIndex = pTraceInfo->m_pBSPData->map_dispList[pLeaf->dispListStart + i]; alignedbbox_t * RESTRICT pDispBounds = &g_pDispBounds[dispIndex]; // make sure we only add this disp once if ( !pTraceInfo->Visit( pDispBounds->GetCounter(), count, pCounters ) ) continue; if ( !IsBoxIntersectingBox( pDispBounds->mins, pDispBounds->maxs, traceData.m_mins, traceData.m_maxs ) ) continue; traceData.m_dispList.AddToTail(dispIndex); } } } void CM_GetTraceDataForBSP( const Vector &mins, const Vector &maxs, CTraceListData &traceData ) { CCollisionBSPData *pBSPData = GetCollisionBSPData(); Vector center = (mins+maxs)*0.5f; Vector extents = maxs - center; int nodenum = 0; TraceInfo_t *pTraceInfo = BeginTrace(); const int NODELIST_MAX = 1024; int nodeList[NODELIST_MAX]; int nodeReadIndex = 0; int nodeWriteIndex = 0; cplane_t *plane; cnode_t *node; while (1) { if (nodenum < 0) { int leafIndex = -1 - nodenum; CM_GetTraceDataForLeaf( pTraceInfo, leafIndex, traceData ); if ( nodeReadIndex == nodeWriteIndex ) break; nodenum = nodeList[nodeReadIndex]; nodeReadIndex = (nodeReadIndex+1) & (NODELIST_MAX-1); } else { node = &pBSPData->map_rootnode[nodenum]; plane = node->plane; // s = BoxOnPlaneSide (leaf_mins, leaf_maxs, plane); // s = BOX_ON_PLANE_SIDE(*leaf_mins, *leaf_maxs, plane); float d0 = DotProduct( plane->normal, center ) - plane->dist; float d1 = DotProductAbs( plane->normal, extents ); if (d0 >= d1) nodenum = node->children[0]; else if (d0 < -d1) nodenum = node->children[1]; else { // go down both nodeList[nodeWriteIndex] = node->children[0]; nodeWriteIndex = (nodeWriteIndex+1) & (NODELIST_MAX-1); // check for overflow of the ring buffer Assert(nodeWriteIndex != nodeReadIndex); nodenum = node->children[1]; } } } EndTrace(pTraceInfo); } /* ================ CM_TestInLeaf ================ */ static void FASTCALL CM_TestInLeaf( TraceInfo_t *pTraceInfo, int ndxLeaf ) { // get the leaf cleaf_t *pLeaf = &pTraceInfo->m_pBSPData->map_leafs[ndxLeaf]; // // trace ray/box sweep against all brushes in this leaf // TraceCounter_t *pCounters = pTraceInfo->GetBrushCounters(); TraceCounter_t count = pTraceInfo->GetCount(); for( int ndxLeafBrush = 0; ndxLeafBrush < pLeaf->numleafbrushes; ndxLeafBrush++ ) { // get the current brush int ndxBrush = pTraceInfo->m_pBSPData->map_leafbrushes[pLeaf->firstleafbrush+ndxLeafBrush]; cbrush_t *pBrush = &pTraceInfo->m_pBSPData->map_brushes[ndxBrush]; // make sure we only check this brush once per trace/stab if ( !pTraceInfo->Visit( pBrush, ndxBrush, count, pCounters ) ) continue; // only collide with objects you are interested in if( !( pBrush->contents & pTraceInfo->m_contents ) ) continue; // // test to see if the point/box is inside of any solid // NOTE: pTraceInfo->m_trace.fraction == 0.0f only when trace starts inside of a brush! // CM_TestBoxInBrush( pTraceInfo, pBrush ); if( !pTraceInfo->m_trace.fraction ) return; } // TODO: this may be redundant if( pTraceInfo->m_trace.startsolid ) return; // if there are no displacement surfaces in this leaf -- we are done testing if( pLeaf->dispCount ) { // test to see if the point/box is inside of any of the displacement surface unsigned short *pDispList = &pTraceInfo->m_pBSPData->map_dispList[pLeaf->dispListStart]; CM_TestInDispTree( pTraceInfo, pDispList, pLeaf->dispCount, pTraceInfo->m_start, pTraceInfo->m_mins, pTraceInfo->m_maxs, pTraceInfo->m_contents, &pTraceInfo->m_trace ); } } //----------------------------------------------------------------------------- // Computes the ray endpoints given a trace. //----------------------------------------------------------------------------- static inline void CM_ComputeTraceEndpoints( const Ray_t& ray, trace_t& tr ) { // The ray start is the center of the extents; compute the actual start Vector start; VectorAdd( ray.m_Start, ray.m_StartOffset, start ); if (tr.fraction == 1) VectorAdd(start, ray.m_Delta, tr.endpos); else VectorMA( start, tr.fraction, ray.m_Delta, tr.endpos ); if (tr.fractionleftsolid == 0) { VectorCopy (start, tr.startpos); } else { if (tr.fractionleftsolid == 1.0f) { tr.startsolid = tr.allsolid = 1; tr.fraction = 0.0f; VectorCopy( start, tr.endpos ); } VectorMA( start, tr.fractionleftsolid, ray.m_Delta, tr.startpos ); } } //----------------------------------------------------------------------------- // Purpose: Get a list of leaves for a trace. //----------------------------------------------------------------------------- void CM_RayLeafnums_r( const Ray_t &ray, CCollisionBSPData *pBSPData, int iNode, float p1f, float p2f, const Vector &vecPoint1, const Vector &vecPoint2, int *pLeafList, int nMaxLeafCount, int &nLeafCount ) { cnode_t *pNode = NULL; cplane_t *pPlane = NULL; float flDist1 = 0.0f, flDist2 = 0.0f; float flOffset = 0.0f; float flDist; float flFrac1, flFrac2; int nSide; float flMid; Vector vecMid; // A quick check so we don't flood the message on overflow - or keep testing beyond our means! if ( nLeafCount >= nMaxLeafCount ) return; // Find the point distances to the separating plane and the offset for the size of the box. // NJS: Hoisted loop invariant comparison to pTraceInfo->m_ispoint if( ray.m_IsRay ) { while( iNode >= 0 ) { pNode = pBSPData->map_rootnode + iNode; pPlane = pNode->plane; if ( pPlane->type < 3 ) { flDist1 = vecPoint1[pPlane->type] - pPlane->dist; flDist2 = vecPoint2[pPlane->type] - pPlane->dist; flOffset = ray.m_Extents[pPlane->type]; } else { flDist1 = DotProduct( pPlane->normal, vecPoint1 ) - pPlane->dist; flDist2 = DotProduct( pPlane->normal, vecPoint2 ) - pPlane->dist; flOffset = 0.0f; } // See which sides we need to consider if ( flDist1 > flOffset && flDist2 > flOffset ) { iNode = pNode->children[0]; continue; } if ( flDist1 < -flOffset && flDist2 < -flOffset ) { iNode = pNode->children[1]; continue; } break; } } else { while( iNode >= 0 ) { pNode = pBSPData->map_rootnode + iNode; pPlane = pNode->plane; if ( pPlane->type < 3 ) { flDist1 = vecPoint1[pPlane->type] - pPlane->dist; flDist2 = vecPoint2[pPlane->type] - pPlane->dist; flOffset = ray.m_Extents[pPlane->type]; } else { flDist1 = DotProduct( pPlane->normal, vecPoint1 ) - pPlane->dist; flDist2 = DotProduct( pPlane->normal, vecPoint2 ) - pPlane->dist; flOffset = fabs( ray.m_Extents[0] * pPlane->normal[0] ) + fabs( ray.m_Extents[1] * pPlane->normal[1] ) + fabs( ray.m_Extents[2] * pPlane->normal[2] ); } // See which sides we need to consider if ( flDist1 > flOffset && flDist2 > flOffset ) { iNode = pNode->children[0]; continue; } if ( flDist1 < -flOffset && flDist2 < -flOffset ) { iNode = pNode->children[1]; continue; } break; } } // If < 0, we are in a leaf node. if ( iNode < 0 ) { if ( nLeafCount < nMaxLeafCount ) { pLeafList[nLeafCount] = -1 - iNode; nLeafCount++; } else { DevMsg( 1, "CM_RayLeafnums_r: Max leaf count along ray exceeded!\n" ); } return; } // Put the crosspoint DIST_EPSILON pixels on the near side. if ( flDist1 < flDist2 ) { flDist = 1.0 / ( flDist1 - flDist2 ); nSide = 1; flFrac2 = ( flDist1 + flOffset + DIST_EPSILON ) * flDist; flFrac1 = ( flDist1 - flOffset - DIST_EPSILON ) * flDist; } else if ( flDist1 > flDist2 ) { flDist = 1.0 / ( flDist1-flDist2 ); nSide = 0; flFrac2 = ( flDist1 - flOffset - DIST_EPSILON ) * flDist; flFrac1 = ( flDist1 + flOffset + DIST_EPSILON ) * flDist; } else { nSide = 0; flFrac1 = 1.0f; flFrac2 = 0.0f; } // Move up to the node flFrac1 = clamp( flFrac1, 0.0f, 1.0f ); flMid = p1f + ( p2f - p1f ) * flFrac1; VectorLerp( vecPoint1, vecPoint2, flFrac1, vecMid ); CM_RayLeafnums_r( ray, pBSPData, pNode->children[nSide], p1f, flMid, vecPoint1, vecMid, pLeafList, nMaxLeafCount, nLeafCount ); // Go past the node flFrac2 = clamp( flFrac2, 0.0f, 1.0f ); flMid = p1f + ( p2f - p1f ) * flFrac2; VectorLerp( vecPoint1, vecPoint2, flFrac2, vecMid ); CM_RayLeafnums_r( ray, pBSPData, pNode->children[nSide^1], flMid, p2f, vecMid, vecPoint2, pLeafList, nMaxLeafCount, nLeafCount ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CM_RayLeafnums( const Ray_t &ray, int *pLeafList, int nMaxLeafCount, int &nLeafCount ) { CCollisionBSPData *pBSPData = GetCollisionBSPData(); if ( !pBSPData->numnodes ) return; Vector vecEnd; VectorAdd( ray.m_Start, ray.m_Delta, vecEnd ); CM_RayLeafnums_r( ray, pBSPData, 0/*headnode*/, 0.0f, 1.0f, ray.m_Start, vecEnd, pLeafList, nMaxLeafCount, nLeafCount ); } bool FASTCALL CM_RecursiveOcclusionPass( COcclusionInfo &oi, int num, const float p1f, const float p2f, const Vector& p1, const Vector& p2 ) { cnode_t *node = NULL; cplane_t *plane; float t1 = 0, t2 = 0, offset = 0; float frac, frac2; float idist; Vector mid; int side; float midf; // find the point distances to the separating plane // and the offset for the size of the box while ( num >= 0 ) { node = oi.m_pBSPData->map_rootnode + num; plane = node->plane; byte type = plane->type; float dist = plane->dist; if ( type < 3 ) { t1 = p1[ type ] - dist; t2 = p2[ type ] - dist; offset = oi.m_extents[ type ]; } else { t1 = DotProduct( plane->normal, p1 ) - dist; t2 = DotProduct( plane->normal, p2 ) - dist; offset = fabsf( oi.m_extents[ 0 ] * plane->normal[ 0 ] ) + fabsf( oi.m_extents[ 1 ] * plane->normal[ 1 ] ) + fabsf( oi.m_extents[ 2 ] * plane->normal[ 2 ] ); } // see which sides we need to consider if ( t1 > offset && t2 > offset ) // if (t1 >= offset && t2 >= offset) { num = node->children[ 0 ]; continue; } if ( t1 < -offset && t2 < -offset ) { num = node->children[ 1 ]; continue; } break; } // if < 0, we are in a leaf node if ( num < 0 ) { return CM_LeafOcclusionPass( oi, -1 - num, p1f, p2f ); } // put the crosspoint DIST_EPSILON pixels on the near side if ( t1 < t2 ) { idist = 1.0 / ( t1 - t2 ); side = 1; frac2 = ( t1 + offset + DIST_EPSILON )*idist; frac = ( t1 - offset - DIST_EPSILON )*idist; } else if ( t1 > t2 ) { idist = 1.0 / ( t1 - t2 ); side = 0; frac2 = ( t1 - offset - DIST_EPSILON )*idist; frac = ( t1 + offset + DIST_EPSILON )*idist; } else { side = 0; frac = 1; frac2 = 0; } // move up to the node frac = clamp( frac, 0, 1 ); midf = p1f + ( p2f - p1f )*frac; VectorLerp( p1, p2, frac, mid ); if ( CM_RecursiveOcclusionPass( oi, node->children[ side ], p1f, midf, p1, mid ) ) { return true; // found full occlusion } // go past the node frac2 = clamp( frac2, 0, 1 ); midf = p1f + ( p2f - p1f )*frac2; VectorLerp( p1, p2, frac2, mid ); if ( CM_RecursiveOcclusionPass( oi, node->children[ side ^ 1 ], midf, p2f, mid, p2 ) ) { return true; // found full occlusion } return false;// didn't find full occlusion yet } /* ================== CM_RecursiveHullCheck ================== Attempt to do whatever is nessecary to get this function to unroll at least once */ template static void FASTCALL CM_RecursiveHullCheckImpl( TraceInfo_t *pTraceInfo, int num, const float p1f, const float p2f, const Vector& p1, const Vector& p2) { if (pTraceInfo->m_trace.fraction <= p1f) return; // already hit something nearer cnode_t *node = NULL; cplane_t *plane; float t1 = 0, t2 = 0, offset = 0; float frac, frac2; float idist; Vector mid; int side; float midf; // find the point distances to the separating plane // and the offset for the size of the box while( num >= 0 ) { node = pTraceInfo->m_pBSPData->map_rootnode + num; plane = node->plane; byte type = plane->type; float dist = plane->dist; if (type < 3) { t1 = p1[type] - dist; t2 = p2[type] - dist; offset = pTraceInfo->m_extents[type]; } else { t1 = DotProduct (plane->normal, p1) - dist; t2 = DotProduct (plane->normal, p2) - dist; if( IS_POINT ) { offset = 0; } else { offset = fabsf(pTraceInfo->m_extents[0]*plane->normal[0]) + fabsf(pTraceInfo->m_extents[1]*plane->normal[1]) + fabsf(pTraceInfo->m_extents[2]*plane->normal[2]); } } // see which sides we need to consider if (t1 > offset && t2 > offset ) // if (t1 >= offset && t2 >= offset) { num = node->children[0]; continue; } if (t1 < -offset && t2 < -offset) { num = node->children[1]; continue; } break; } // if < 0, we are in a leaf node if (num < 0) { CM_TraceToLeaf(pTraceInfo, -1-num, p1f, p2f); return; } // put the crosspoint DIST_EPSILON pixels on the near side if (t1 < t2) { idist = 1.0/(t1-t2); side = 1; frac2 = (t1 + offset + DIST_EPSILON)*idist; frac = (t1 - offset - DIST_EPSILON)*idist; } else if (t1 > t2) { idist = 1.0/(t1-t2); side = 0; frac2 = (t1 - offset - DIST_EPSILON)*idist; frac = (t1 + offset + DIST_EPSILON)*idist; } else { side = 0; frac = 1; frac2 = 0; } // move up to the node frac = clamp( frac, 0, 1 ); midf = p1f + (p2f - p1f)*frac; VectorLerp( p1, p2, frac, mid ); CM_RecursiveHullCheckImpl(pTraceInfo, node->children[side], p1f, midf, p1, mid); // go past the node frac2 = clamp( frac2, 0, 1 ); midf = p1f + (p2f - p1f)*frac2; VectorLerp( p1, p2, frac2, mid ); CM_RecursiveHullCheckImpl(pTraceInfo, node->children[side^1], midf, p2f, mid, p2); } void FASTCALL CM_RecursiveHullCheck ( TraceInfo_t *pTraceInfo, int num, const float p1f, const float p2f ) { const Vector& p1 = pTraceInfo->m_start; const Vector& p2 = pTraceInfo->m_end; if( pTraceInfo->m_ispoint ) { CM_RecursiveHullCheckImpl( pTraceInfo, num, p1f, p2f, p1, p2); } else { CM_RecursiveHullCheckImpl( pTraceInfo, num, p1f, p2f, p1, p2); } } void CM_ClearTrace( trace_t *trace ) { memset( trace, 0, sizeof(*trace)); trace->fraction = 1.f; trace->fractionleftsolid = 0; trace->surface = CCollisionBSPData::nullsurface; } //----------------------------------------------------------------------------- // // The following versions use ray... gradually I'm gonna remove other versions // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Test an unswept box //----------------------------------------------------------------------------- static inline void CM_UnsweptBoxTrace( TraceInfo_t *pTraceInfo, const Ray_t& ray, int headnode, int brushmask ) { int leafs[1024]; int i, numleafs; leafnums_t context; context.pLeafList = leafs; context.leafTopNode = -1; context.leafMaxCount = ARRAYSIZE(leafs); context.pBSPData = pTraceInfo->m_pBSPData; bool bFoundNonSolidLeaf = false; numleafs = CM_BoxLeafnums ( context, ray.m_Start, ray.m_Extents+Vector(1,1,1), headnode); for (i=0 ; im_pBSPData->map_leafs[leafs[i]].contents & CONTENTS_SOLID) == 0) { bFoundNonSolidLeaf = true; } CM_TestInLeaf ( pTraceInfo, leafs[i] ); if (pTraceInfo->m_trace.allsolid) break; } if (!bFoundNonSolidLeaf) { pTraceInfo->m_trace.allsolid = pTraceInfo->m_trace.startsolid = 1; pTraceInfo->m_trace.fraction = 0.0f; pTraceInfo->m_trace.fractionleftsolid = 1.0f; } } //----------------------------------------------------------------------------- // Purpose: Ray/Hull trace against the world without the RecursiveHullTrace //----------------------------------------------------------------------------- void CM_BoxTraceAgainstLeafList( const Ray_t &ray, const CTraceListData &traceData, int nBrushMask, trace_t &trace ) { VPROF("CM_BoxTraceAgainstLeafList"); TraceInfo_t *pTraceInfo = BeginTrace(); CM_ClearTrace(&pTraceInfo->m_trace); // Setup global trace data. (This is nasty! I hate this.) pTraceInfo->m_bDispHit = false; pTraceInfo->m_DispStabDir.Init(); pTraceInfo->m_contents = nBrushMask; VectorCopy( ray.m_Start, pTraceInfo->m_start ); VectorAdd( ray.m_Start, ray.m_Delta, pTraceInfo->m_end ); VectorMultiply( ray.m_Extents, -1.0f, pTraceInfo->m_mins ); VectorCopy( ray.m_Extents, pTraceInfo->m_maxs ); VectorCopy( ray.m_Extents, pTraceInfo->m_extents ); pTraceInfo->m_delta = ray.m_Delta; pTraceInfo->m_invDelta = ray.InvDelta(); pTraceInfo->m_ispoint = ray.m_IsRay; pTraceInfo->m_isswept = ray.m_IsSwept; if ( !ray.m_IsSwept ) { for ( int i = 0; i < traceData.m_brushList.Count(); i++ ) { int brushIndex = traceData.m_brushList[i]; cbrush_t *pBrush = &pTraceInfo->m_pBSPData->map_brushes[brushIndex]; // only collide with objects you are interested in if( !( pBrush->contents & pTraceInfo->m_contents ) ) continue; // // test to see if the point/box is inside of any solid // NOTE: pTraceInfo->m_trace.fraction == 0.0f only when trace starts inside of a brush! // CM_TestBoxInBrush( pTraceInfo, pBrush ); if ( pTraceInfo->m_trace.allsolid ) break; } if( !pTraceInfo->m_trace.startsolid ) { CM_TestInDispTree( pTraceInfo, traceData.m_dispList.Base(), traceData.m_dispList.Count(), pTraceInfo->m_start, pTraceInfo->m_mins, pTraceInfo->m_maxs, pTraceInfo->m_contents, &pTraceInfo->m_trace ); } if (!traceData.m_bFoundNonSolidLeaf) { pTraceInfo->m_trace.allsolid = pTraceInfo->m_trace.startsolid = 1; pTraceInfo->m_trace.fraction = 0.0f; pTraceInfo->m_trace.fractionleftsolid = 1.0f; } } else { if ( ray.m_IsRay ) { CM_TraceToBrushList( pTraceInfo, traceData.m_brushList.Base(), traceData.m_brushList.Count() ); } else { CM_TraceToBrushList( pTraceInfo, traceData.m_brushList.Base(), traceData.m_brushList.Count() ); } if ( pTraceInfo->m_trace.fraction > 0 && !pTraceInfo->m_trace.startsolid ) { if ( ray.m_IsRay ) { CM_TraceToDispList( pTraceInfo, traceData.m_dispList.Base(), traceData.m_dispList.Count(), 0, 1 ); } else { CM_TraceToDispList( pTraceInfo, traceData.m_dispList.Base(), traceData.m_dispList.Count(), 0, 1 ); } } } // Compute the trace start and end points. CM_ComputeTraceEndpoints( ray, pTraceInfo->m_trace ); // Copy off the results trace = pTraceInfo->m_trace; EndTrace( pTraceInfo ); Assert( !ray.m_IsRay || trace.allsolid || ((trace.fraction + kBoxCheckFloatEpsilon) >= trace.fractionleftsolid) ); } uint64 COcclusionInfo::s_nAssocArrayCollisions = 0; uint64 COcclusionInfo::s_nAssocArrayHits = 0; uint64 COcclusionInfo::s_nAssocArrayMisses = 0; //uint64 s_nOccluded = 0, s_nUnoccluded = 0; const int32 ALIGN16 g_SIMD_0101_signmask[ 4 ] ALIGN16_POST = { 0, (int32)0x80000000, 0, (int32)0x80000000 }; const int32 ALIGN16 g_SIMD_0011_signmask[ 4 ] ALIGN16_POST = { 0, 0, (int32)0x80000000, (int32)0x80000000 }; struct OcclusionTestRec_t { VectorAligned p0; VectorAligned vExtents0; VectorAligned p1; VectorAligned vExtents1; }; ConVar occlusion_test_rays( "occlusion_test_rays", "0", FCVAR_DEVELOPMENTONLY ); static CUtlVector< OcclusionTestRec_t > s_OcclusionTestRecords; static int s_nOcclusionTestsToCollect = 0, s_nOcclusionTestsCollectionsToRun = 0, s_nOcclusionTestCollectionLock = 0; CON_COMMAND_F( occlusion_test_record, "dump occlusion tests - useful on server only", FCVAR_CHEAT ) { if ( args.ArgC() < 2 ) { if ( s_nOcclusionTestsCollectionsToRun == 0 ) Msg( "Not recording occlusion tests now, %d in record array\n", s_OcclusionTestRecords.Count() ); else Msg( "Currently recording %d tests in %d passes, %d records collected in this pass so far\n", s_nOcclusionTestsToCollect, s_nOcclusionTestsCollectionsToRun, s_OcclusionTestRecords.Count() ); return; } int nCount = V_atoi( args.Arg( 1 ) ); if ( nCount > 0 ) { s_nOcclusionTestsToCollect = nCount; if ( args.ArgC() >= 3 ) { s_nOcclusionTestsCollectionsToRun = V_atoi( args.Arg( 2 ) ); if ( s_nOcclusionTestsCollectionsToRun < 1 ) s_nOcclusionTestsCollectionsToRun = 1; } else { s_nOcclusionTestsCollectionsToRun = 1; } /* if ( s_OcclusionTestRecords.Count() > 0 ) { Msg( "Abandoning %d tests already collected. ", s_OcclusionTestRecords.Count() ); s_OcclusionTestRecords.Purge(); } */ Msg( "Preparing to collect %d occlusion tests. %d collected so far.\n", nCount, s_OcclusionTestRecords.Count() ); s_OcclusionTestRecords.EnsureCapacity( nCount ); } else if ( nCount == 0 ) { Msg( "Stopping all occlusion tests: %dx%d, purging %d\n", s_nOcclusionTestsToCollect, s_nOcclusionTestsCollectionsToRun, s_OcclusionTestRecords.Count() ); s_nOcclusionTestsToCollect = 0; s_nOcclusionTestsCollectionsToRun = 0; s_OcclusionTestRecords.Purge(); } else { Msg( "Need to collect positive number of tests\n" ); } } // Exported in host.cpp // No idea why it isn't in the header. const char *GetMapName( void ); bool DebugCheckOcclusion( const Vector &p0, const Vector &vExtents0, const Vector &p1, const Vector &vExtents1, int nOcclusionRays ) { for ( int i = 0; i < nOcclusionRays; ++i ) { // we shouldn't find a single ray between the two boxes that is not occluded Vector rnd0( RandomFloat() * vExtents0.x, RandomFloat() * vExtents0.y, RandomFloat() * vExtents0.z ); Vector rnd1( RandomFloat() * vExtents1.x, RandomFloat() * vExtents1.y, RandomFloat() * vExtents1.z ); Ray_t ray; ray.Init( p0 + rnd0, p1 + rnd1 ); trace_t tr; V_memset( &tr, 0, sizeof( tr ) ); CM_BoxTrace( ray, 0, CONTENTS_SOLID | CONTENTS_MOVEABLE, false, tr ); if ( !tr.DidHit() ) { return false; } } return true; } bool CM_IsFullyOccluded( const VectorAligned &p0, const VectorAligned &vExtents0, const VectorAligned &p1, const VectorAligned &vExtents1, OcclusionTestResults_t *pResults ) { if ( s_nOcclusionTestsToCollect > 0 && s_nOcclusionTestCollectionLock == 0 ) { OcclusionTestRec_t &rec = s_OcclusionTestRecords[ s_OcclusionTestRecords.AddToTail() ]; rec.p0 = p0; rec.vExtents0 = vExtents0; rec.p1 = p1; rec.vExtents1 = vExtents1; if ( s_OcclusionTestRecords.Count() >= s_nOcclusionTestsToCollect ) { // record the file for ( int nAttempt = 0; nAttempt < 10000; ++nAttempt ) { const char *pMapName = GetMapName(); CFmtStr fileName( "occlusion_records.%s.%04d.ocr", pMapName, nAttempt ); if ( g_pFullFileSystem->FileExists( fileName.Get() ) ) continue; Msg( "Saving %d occlusion records to %s (#%d)\n", s_OcclusionTestRecords.Count(), fileName.Get(), nAttempt ); FileHandle_t hDump = g_pFullFileSystem->Open( fileName.Get(), "wb" ); g_pFullFileSystem->Write( s_OcclusionTestRecords.Base(), s_OcclusionTestRecords.Count() * sizeof( s_OcclusionTestRecords[ 0 ] ), hDump ); g_pFullFileSystem->Close( hDump ); s_OcclusionTestRecords.Purge(); break; } if ( --s_nOcclusionTestsCollectionsToRun <= 0 ) { s_nOcclusionTestsToCollect = 0; s_nOcclusionTestsCollectionsToRun = 0; } } } VPROF_BUDGET( "CM_IsFullyOccluded", VPROF_BUDGETGROUP_OTHER_UNACCOUNTED ); Vector vExtents; VectorMax( vExtents0, vExtents1, vExtents ); COcclusionInfo oi; oi.m_pBSPData = GetCollisionBSPData(); oi.m_pResults = pResults; // check if the map is not loaded if ( !oi.m_pBSPData->numnodes ) { return false; } // axis-aligned boxes are a very special case: we can just cast 6 rays and if we don't care about their order (i.e. if we aren't looking for partial occlusions), and have 2x4 or 8 lane SIMD, we can just cast each corner to a corresponding other box'es corner and we'll be just fine fltx4 f4ExtentsStart = LoadAlignedSIMD( &vExtents0 ), f4ExtentsEnd = LoadAlignedSIMD( &vExtents1 ), f4Start = LoadAlignedSIMD( &p0 ), f4End = LoadAlignedSIMD( &p1 ); fltx4 f4Delta = f4End - f4Start; fltx4 f4DeltaPos = fabs( f4Delta ); if ( !( TestSignSIMD( ( f4ExtentsStart + f4ExtentsEnd ) - f4DeltaPos ) & 7 ) ) { return false; // we can't get full occlusion if the two boxes intersect } StoreAligned3SIMD( &oi.m_delta, f4Delta ); StoreAligned3SIMD( &oi.m_deltaPos, f4DeltaPos ); fltx4 f40101signmask = *( fltx4* )g_SIMD_0101_signmask; oi.m_StartXnXYnY = ShuffleXXYY( f4Start ) + XorSIMD( f40101signmask, ShuffleXXYY( f4ExtentsStart ) ); oi.m_EndXnXYnY = ShuffleXXYY( f4End ) + XorSIMD( f40101signmask, ShuffleXXYY( f4ExtentsEnd ) ); oi.m_StartEndZnZ = _mm_shuffle_ps( f4Start, f4End, MM_SHUFFLE_REV( 2, 2, 2, 2 ) ) + XorSIMD( f40101signmask, _mm_shuffle_ps( f4ExtentsStart, f4ExtentsEnd, MM_SHUFFLE_REV( 2, 2, 2, 2 ) ) ); oi.m_start = p0; oi.m_end = p1; Vector vExtentsScaled = vExtents; oi.m_extents = vExtentsScaled; oi.m_extents.z = Max( 0.0f, oi.m_extents.z - 0.0625f /*MOVE_HEIGHT_EPSILON*/ ); oi.m_delta = p1 - p0; VectorAbs( oi.m_delta, oi.m_deltaPos ); oi.m_uvwExtents.Init( oi.m_deltaPos.y * oi.m_extents.z + oi.m_deltaPos.z * oi.m_extents.y, oi.m_deltaPos.z * oi.m_extents.x + oi.m_deltaPos.x * oi.m_extents.z, oi.m_deltaPos.x * oi.m_extents.y + oi.m_deltaPos.y * oi.m_extents.x ); // all extents and their abs projections should be positive or zero oi.m_uvwMins = -oi.m_uvwExtents; oi.m_uvwMaxs = oi.m_uvwExtents; oi.m_deltaSigns.Init( Sign( oi.m_delta.x ), Sign( oi.m_delta.y ), Sign( oi.m_delta.z ) ); // oi.m_minsPos.Init( oi.m_delta.x >= 0 ? p0.x : -p1.x, oi.m_delta.y >= 0 ? p0.y : -p1.y, oi.m_delta.z >= 0 ? p0.z : -p1.z ); // oi.m_maxsPos.Init( oi.m_delta.x >= 0 ? p1.x : -p0.x, oi.m_delta.y >= 0 ? p1.y : -p0.y, oi.m_delta.z >= 0 ? p1.z : -p0.z ); // AssertDbg( oi.m_minsPos.x <= oi.m_maxsPos.x && oi.m_minsPos.y <= oi.m_maxsPos.y && oi.m_minsPos.z <= oi.m_maxsPos.z ); oi.m_traceMins; VectorMin( p0, p1, oi.m_traceMins ); oi.m_traceMins.z -= vExtentsScaled.z; oi.m_traceMaxs; VectorMax( p0, p1, oi.m_traceMaxs ); oi.m_traceMaxs.z += vExtentsScaled.z; oi.m_contents = CONTENTS_SOLID | CONTENTS_MOVEABLE; // can solid or moveable be semitransparent? oi.m_pDebugLog = NULL; /* #ifdef _DEBUG static bool s_bDumpOcclusionPass = false; if ( s_bDumpOcclusionPass ) { oi.m_pDebugLog = new CBspDebugLog( "bsp_debug_log.obj"); oi.m_pDebugLog->AddBox( "start", "start", p0 - vExtents0, p0 + vExtents0 ); oi.m_pDebugLog->AddBox( "end", "end", p1 - vExtents1, p1 + vExtents1 ); oi.m_pDebugLog->ResetPrimCount(); } #endif */ return CM_RecursiveOcclusionPass( oi, 0, 0.0f, 1.0f, p0, p1 ); } bool CM_IsFullyOccluded( const AABB_t &aabb1, const AABB_t &aabb2 ) { VectorAligned vCenter1( aabb1.GetCenter() ); VectorAligned vCenter2( aabb2.GetCenter() ); VectorAligned vHullExtents1( aabb1.GetSize() * .5f ); VectorAligned vHullExtents2( aabb2.GetSize() * .5f ); // include both hulls extents bool bOccluded = CM_IsFullyOccluded( vCenter1, vHullExtents1, vCenter2, vHullExtents2 ); return bOccluded; } #if 0 CON_COMMAND_F( occlusion_test_run, "run occlusion test", FCVAR_CHEAT ) { if ( s_nOcclusionTestCollectionLock ) { Msg( "Occlusion test collection lock is on (%d). This is unexpected. Resetting.\n", s_nOcclusionTestCollectionLock ); s_nOcclusionTestCollectionLock = 0; } if ( args.ArgC() < 2 ) { Msg( "Usage: occlusion_test_run [-cold] [-check []]\n" ); return; } const char *pMapName = GetMapName(); CFmtStr fileName( "occlusion_records.%s.%04d.ocr", pMapName, V_atoi( args.Arg( 1 ) ) ); CUtlBuffer buf; if ( !g_pFullFileSystem->ReadFile( fileName.Get(), NULL, buf ) ) { Msg( "Cannot read %s\n", fileName.Get() ); return; } s_nOcclusionTestCollectionLock++; bool bColdCache = false; int nDoubleCheck = 0; const CPUInformation &cpuInfo = GetCPUInformation(); uint32 nCacheSizeKb = Max( Max( cpuInfo.m_nL1CacheSizeKb, cpuInfo.m_nL2CacheSizeKb ), cpuInfo.m_nL3CacheSizeKb ); for ( int i = 2; i < args.ArgC(); ++i ) { const char *pArg = args.Arg( i ); if ( !V_stricmp( pArg, "-cold" ) ) { bColdCache = true; Msg( "Will test with cold cache; flush array size %dkb; cache sizes: L1 %dKb, L2 %dKb, L3 %dKb\n", nCacheSizeKb, cpuInfo.m_nL1CacheSizeKb, cpuInfo.m_nL2CacheSizeKb , cpuInfo.m_nL3CacheSizeKb ); } else if ( !V_stricmp( pArg, "-check" ) ) { nDoubleCheck = 100; if ( i + 1 < args.ArgC() ) { nDoubleCheck = V_atoi( args[ i + 1 ] ); if ( nDoubleCheck > 0 ) { i++; } else { nDoubleCheck = 100; } } Msg( "Will double-check occlusion with %d rays\n", nDoubleCheck ); } } int nRecCount = buf.TellPut() / sizeof( OcclusionTestRec_t ); OcclusionTestRec_t *pRec = ( OcclusionTestRec_t * )buf.Base(); Msg( "Loaded %d occlusion records from %s\n", nRecCount, fileName.Get() ); int nRecOccluded = 0, nRecFalseOcclusion = 0, nRecFalseNonOcclusion = 0; int64 nCpuSpeed = cpuInfo.m_Speed; int64 nBestCase = nCpuSpeed, nWorstCase = 0, nTotalTime = 0; int nBestIndex = -1, nWorstIndex = -1; CUtlVector< int8 > flushCache; if ( bColdCache ) flushCache.SetCount( nCacheSizeKb * 1024 ); for ( int i = 0; i < nRecCount; ++i ) { if ( bColdCache ) { // flush the cache flushCache.FillWithValue( i ); } int64 nTimeStart = GetTimebaseRegister(); bool bOccluded = CM_IsFullyOccluded( pRec[ i ].p0, pRec[ i ].vExtents0, pRec[ i ].p1, pRec[ i ].vExtents1 ); int64 nTimeOcclusion = GetTimebaseRegister() - nTimeStart; if ( nWorstCase < nTimeOcclusion ) { nWorstCase = nTimeOcclusion; nWorstIndex = i; } if ( nBestCase > nTimeOcclusion ) { nBestCase = nTimeOcclusion ; nBestIndex = i; } nTotalTime += nTimeOcclusion; if ( bOccluded ) ++nRecOccluded; if ( nDoubleCheck ) { if ( bOccluded != DebugCheckOcclusion( pRec[ i ].p0, pRec[ i ].vExtents0, pRec[ i ].p1, pRec[ i ].vExtents1, nDoubleCheck ) ) { if ( bOccluded ) { ++nRecFalseOcclusion; Warning( "FALSE OCCLUSION FOUND: #%d\n", i ); } else ++nRecFalseNonOcclusion; } } } double flMicrosecondsPerTick = 1e6 / nCpuSpeed; Msg( "%d/%d (%.1f%%) occluded, occlusion tests run %.1f us ave ([%d]=%.1fus, [%d]=%.1fus) on a %.2fGHz CPU\n", nRecOccluded, nRecCount, ( nRecOccluded * 100.0f ) / nRecCount, ( nTotalTime * flMicrosecondsPerTick ) / nRecCount, nBestIndex, nBestCase * flMicrosecondsPerTick, nWorstIndex, nWorstCase * flMicrosecondsPerTick, nCpuSpeed * 1e-9 ); if ( nDoubleCheck ) { Msg( "%d false non-occlusions\n", nRecFalseNonOcclusion ); } s_nOcclusionTestCollectionLock--; } #endif void CM_BoxTrace( const Ray_t& ray, int headnode, int brushmask, bool computeEndpt, trace_t& tr ) { VPROF("BoxTrace"); // for multi-check avoidance TraceInfo_t *pTraceInfo = BeginTrace(); #ifdef COUNT_COLLISIONS // for statistics, may be zeroed g_CollisionCounts.m_Traces++; #endif // fill in a default trace CM_ClearTrace( &pTraceInfo->m_trace ); // check if the map is not loaded if (!pTraceInfo->m_pBSPData->numnodes) { tr = pTraceInfo->m_trace; EndTrace( pTraceInfo ); return; } pTraceInfo->m_bDispHit = false; pTraceInfo->m_DispStabDir.Init(); pTraceInfo->m_contents = brushmask; VectorCopy (ray.m_Start, pTraceInfo->m_start); VectorAdd (ray.m_Start, ray.m_Delta, pTraceInfo->m_end); VectorMultiply (ray.m_Extents, -1.0f, pTraceInfo->m_mins); VectorCopy (ray.m_Extents, pTraceInfo->m_maxs); VectorCopy (ray.m_Extents, pTraceInfo->m_extents); pTraceInfo->m_delta = ray.m_Delta; pTraceInfo->m_invDelta = ray.InvDelta(); pTraceInfo->m_ispoint = ray.m_IsRay; pTraceInfo->m_isswept = ray.m_IsSwept; if (!ray.m_IsSwept) { // check for position test special case CM_UnsweptBoxTrace( pTraceInfo, ray, headnode, brushmask ); } else { // general sweeping through world CM_RecursiveHullCheck( pTraceInfo, headnode, 0, 1 ); } // Compute the trace start + end points if (computeEndpt) { CM_ComputeTraceEndpoints( ray, pTraceInfo->m_trace ); } // Copy off the results tr = pTraceInfo->m_trace; EndTrace( pTraceInfo ); Assert( !ray.m_IsRay || tr.allsolid || ((tr.fraction + kBoxCheckFloatEpsilon) >= tr.fractionleftsolid) ); } void CM_TransformedBoxTrace( const Ray_t& ray, int headnode, int brushmask, const Vector& origin, QAngle const& angles, trace_t& tr ) { matrix3x4_t localToWorld; Ray_t ray_l; // subtract origin offset VectorCopy( ray.m_StartOffset, ray_l.m_StartOffset ); VectorCopy( ray.m_Extents, ray_l.m_Extents ); // Are we rotated? bool rotated = (angles[0] || angles[1] || angles[2]); // rotate start and end into the models frame of reference if (rotated) { // NOTE: In this case, the bbox is rotated into the space of the BSP as well // to insure consistency at all orientations, we must rotate the origin of the ray // and reapply the offset to the center of the box. That way all traces with the // same box centering will have the same transformation into local space Vector worldOrigin = ray.m_Start + ray.m_StartOffset; AngleMatrix( angles, origin, localToWorld ); VectorIRotate( ray.m_Delta, localToWorld, ray_l.m_Delta ); VectorITransform( worldOrigin, localToWorld, ray_l.m_Start ); ray_l.m_Start -= ray.m_StartOffset; } else { VectorSubtract( ray.m_Start, origin, ray_l.m_Start ); VectorCopy( ray.m_Delta, ray_l.m_Delta ); } ray_l.m_IsRay = ray.m_IsRay; ray_l.m_IsSwept = ray.m_IsSwept; // sweep the box through the model, don't compute endpoints CM_BoxTrace( ray_l, headnode, brushmask, false, tr ); // If we hit, gotta fix up the normal... if (( tr.fraction != 1 ) && rotated ) { // transform the normal from the local space of this entity to world space Vector temp; VectorCopy (tr.plane.normal, temp); VectorRotate( temp, localToWorld, tr.plane.normal ); } CM_ComputeTraceEndpoints( ray, tr ); } /* =============================================================================== PVS / PAS =============================================================================== */ //----------------------------------------------------------------------------- // Purpose: // Input : *pBSPData - // *out - //----------------------------------------------------------------------------- void CM_NullVis( CCollisionBSPData *pBSPData, byte *out ) { int numClusterBytes = (pBSPData->numclusters+7)>>3; byte *out_p = out; while (numClusterBytes) { *out_p++ = 0xff; numClusterBytes--; } } /* =================== CM_DecompressVis =================== */ void CM_DecompressVis( CCollisionBSPData *pBSPData, int cluster, int visType, byte *out ) { int c; byte *out_p; int numClusterBytes; if ( !pBSPData ) { Assert( false ); // Shouldn't ever happen. } if ( cluster > pBSPData->numclusters || cluster < 0 ) { // This can happen if this is called while the level is loading. See Map_VisCurrentCluster. CM_NullVis( pBSPData, out ); return; } // no vis info, so make all visible if ( !pBSPData->numvisibility || !pBSPData->map_vis ) { CM_NullVis( pBSPData, out ); return; } byte *in = ((byte *)pBSPData->map_vis) + pBSPData->map_vis->bitofs[cluster][visType]; numClusterBytes = (pBSPData->numclusters+7)>>3; out_p = out; // no vis info, so make all visible if ( !in ) { CM_NullVis( pBSPData, out ); return; } do { if (*in) { *out_p++ = *in++; continue; } c = in[1]; in += 2; if ((out_p - out) + c > numClusterBytes) { c = numClusterBytes - (out_p - out); ConMsg( "warning: Vis decompression overrun\n" ); } while (c) { *out_p++ = 0; c--; } } while (out_p - out < numClusterBytes); } //----------------------------------------------------------------------------- // Purpose: Decompress the RLE bitstring for PVS or PAS of one cluster // Input : *dest - buffer to store the decompressed data // cluster - index of cluster of interest // visType - DVIS_PAS or DVIS_PAS // Output : byte * - pointer to the filled buffer //----------------------------------------------------------------------------- const byte *CM_Vis( byte *dest, int destlen, int cluster, int visType ) { // get the current collision bsp -- there is only one! CCollisionBSPData *pBSPData = GetCollisionBSPData(); if ( !dest || visType > 2 || visType < 0 ) { Sys_Error( "CM_Vis: error"); return NULL; } if ( cluster == -1 ) { int len = (pBSPData->numclusters+7)>>3; if ( len > destlen ) { Sys_Error( "CM_Vis: buffer not big enough (%i but need %i)\n", destlen, len ); } memset( dest, 0, (pBSPData->numclusters+7)>>3 ); } else { CM_DecompressVis( pBSPData, cluster, visType, dest ); } return dest; } static byte pvsrow[MAX_MAP_LEAFS/8]; int CM_ClusterPVSSize() { return sizeof( pvsrow ); } const byte *CM_ClusterPVS (int cluster) { return CM_Vis( pvsrow, CM_ClusterPVSSize(), cluster, DVIS_PVS ); } /* =============================================================================== AREAPORTALS =============================================================================== */ void FloodArea_r (CCollisionBSPData *pBSPData, carea_t *area, int floodnum) { int i; dareaportal_t *p; if (area->floodvalid == pBSPData->floodvalid) { if (area->floodnum == floodnum) return; Sys_Error( "FloodArea_r: reflooded"); } area->floodnum = floodnum; area->floodvalid = pBSPData->floodvalid; p = &pBSPData->map_areaportals[area->firstareaportal]; for (i=0 ; inumareaportals ; i++, p++) { if (pBSPData->portalopen[p->m_PortalKey]) { FloodArea_r (pBSPData, &pBSPData->map_areas[p->otherarea], floodnum); } } } /* ==================== FloodAreaConnections ==================== */ void FloodAreaConnections ( CCollisionBSPData *pBSPData ) { int i; carea_t *area; int floodnum; // all current floods are now invalid pBSPData->floodvalid++; floodnum = 0; // area 0 is not used for (i=1 ; inumareas ; i++) { area = &pBSPData->map_areas[i]; if (area->floodvalid == pBSPData->floodvalid) continue; // already flooded into floodnum++; FloodArea_r (pBSPData, area, floodnum); } } void CM_SetAreaPortalState( int portalnum, int isOpen ) { // get the current collision bsp -- there is only one! CCollisionBSPData *pBSPData = GetCollisionBSPData(); // Portalnums in the BSP file are 1-based instead of 0-based if (portalnum > pBSPData->numareaportals) { Sys_Error( "portalnum > numareaportals"); } pBSPData->portalopen[portalnum] = (isOpen != 0); FloodAreaConnections (pBSPData); } void CM_SetAreaPortalStates( const int *portalnums, const int *isOpen, int nPortals ) { if ( nPortals == 0 ) return; CCollisionBSPData *pBSPData = GetCollisionBSPData(); // get the current collision bsp -- there is only one! for ( int i=0; i < nPortals; i++ ) { // Portalnums in the BSP file are 1-based instead of 0-based if (portalnums[i] > pBSPData->numareaportals) Sys_Error( "portalnum > numareaportals"); pBSPData->portalopen[portalnums[i]] = (isOpen[i] != 0); } FloodAreaConnections( pBSPData ); } bool CM_AreasConnected( int area1, int area2 ) { // get the current collision bsp -- there is only one! CCollisionBSPData *pBSPData = GetCollisionBSPData(); if (map_noareas.GetInt()) return true; if (area1 >= pBSPData->numareas || area2 >= pBSPData->numareas) { Sys_Error( "area(1==%i, 2==%i) >= numareas (%i): Check if engine->ResetPVS() was called from ClientSetupVisibility", area1, area2, pBSPData->numareas ); } return (pBSPData->map_areas[area1].floodnum == pBSPData->map_areas[area2].floodnum); } void CM_LeavesConnected( const Vector &vecOrigin, int nCount, const int *pLeaves, bool *pIsConnected ) { // get the current collision bsp -- there is only one! CCollisionBSPData *pBSPData = GetCollisionBSPData(); if ( map_noareas.GetInt() ) { memset( pIsConnected, 1, nCount * sizeof(bool) ); return; } int nArea = CM_LeafArea( CM_PointLeafnum( vecOrigin ) ); Assert( nArea < pBSPData->numareas && nArea >= 0 ); for ( int i = 0; i < nCount; ++i ) { int nLeafArea = pBSPData->map_leafs[ pLeaves[i] ].area; Assert( nLeafArea < pBSPData->numareas && nLeafArea >= 0 ); pIsConnected[i] = ( pBSPData->map_areas[nArea].floodnum == pBSPData->map_areas[ nLeafArea ].floodnum ); } } //----------------------------------------------------------------------------- // Purpose: CM_WriteAreaBits // Writes a bit vector of all the areas in the same flood as the area parameter // Input : *buffer - // buflen - // area - // Output : int - number of bytes of collision data (based on area count) //----------------------------------------------------------------------------- int CM_WriteAreaBits( byte *buffer, int buflen, int area ) { int i; int floodnum; int bytes; if ( buflen < 32 ) { Sys_Error( "CM_WriteAreaBits with buffer size %d < 32\n", buflen ); } // get the current collision bsp -- there is only one! CCollisionBSPData *pBSPData = GetCollisionBSPData(); Assert( pBSPData ); bytes = ( pBSPData->numareas + 7 ) >> 3; Assert( buflen >= bytes ); if ( map_noareas.GetInt() ) { // for debugging, send everything (or nothing if map_noareas == 2 ) byte fill = ( map_noareas.GetInt() == 2 ) ? 0x00 : 0xff; Q_memset( buffer, fill, buflen ); } else { Q_memset( buffer, 0x00, buflen ); floodnum = pBSPData->map_areas[area].floodnum; for ( i = 0 ; i < pBSPData->numareas; ++i ) { if ( pBSPData->map_areas[i].floodnum == floodnum || !area ) { buffer[ i >> 3 ] |= ( 1 << ( i & 7 ) ); } } } return bytes; } bool CM_GetAreaPortalPlane( const Vector &vViewOrigin, int portalKey, VPlane *pPlane ) { CCollisionBSPData *pBSPData = GetCollisionBSPData(); // First, find the leaf and area the viewer is in. int iLeaf = CM_PointLeafnum( vViewOrigin ); if( iLeaf < 0 || iLeaf >= pBSPData->numleafs ) return false; int iArea = pBSPData->map_leafs[iLeaf].area; if( iArea < 0 || iArea >= pBSPData->numareas ) return false; carea_t *pArea = &pBSPData->map_areas[iArea]; for( int i=0; i < pArea->numareaportals; i++ ) { dareaportal_t *pPortal = &pBSPData->map_areaportals[pArea->firstareaportal + i]; if( pPortal->m_PortalKey == portalKey ) { cplane_t *pMapPlane = &pBSPData->map_planes[pPortal->planenum]; pPlane->m_Normal = pMapPlane->normal; pPlane->m_Dist = pMapPlane->dist; return true; } } return false; } /* ============= CM_HeadnodeVisible Returns true if any leaf under headnode has a cluster that is potentially visible ============= */ bool CM_HeadnodeVisible (int nodenum, const byte *visbits, int vissize ) { int leafnum; int cluster; cnode_t *node; // get the current collision bsp -- there is only one! CCollisionBSPData *pBSPData = GetCollisionBSPData(); if (nodenum < 0) { leafnum = -1-nodenum; cluster = pBSPData->map_leafs[leafnum].cluster; if (cluster == -1) return false; if (visbits[cluster>>3] & (1<<(cluster&7))) return true; return false; } node = &pBSPData->map_rootnode[nodenum]; if (CM_HeadnodeVisible(node->children[0], visbits, vissize )) return true; return CM_HeadnodeVisible(node->children[1], visbits, vissize ); } //----------------------------------------------------------------------------- // Purpose: returns true if the box is in a cluster that is visible in the visbits // Input : mins - box extents // maxs - // *visbits - pvs or pas of some cluster // Output : true if visible, false if not //----------------------------------------------------------------------------- #define MAX_BOX_LEAVES 256 int CM_BoxVisible( const Vector& mins, const Vector& maxs, const byte *visbits, int vissize ) { int leafList[MAX_BOX_LEAVES]; int topnode; // FIXME: Could save a loop here by traversing the tree in this routine like the code above int count = CM_BoxLeafnums( mins, maxs, leafList, MAX_BOX_LEAVES, &topnode ); for ( int i = 0; i < count; i++ ) { int cluster = CM_LeafCluster( leafList[i] ); int offset = cluster>>3; if ( offset > vissize ) { Sys_Error( "CM_BoxVisible: cluster %i, offset %i out of bounds %i\n", cluster, offset, vissize ); } if (visbits[cluster>>3] & (1<<(cluster&7))) { return true; } } return false; } //----------------------------------------------------------------------------- // Returns the world-space center of an entity //----------------------------------------------------------------------------- void CM_WorldSpaceCenter( ICollideable *pCollideable, Vector *pCenter ) { Vector vecLocalCenter; VectorAdd( pCollideable->OBBMins(), pCollideable->OBBMaxs(), vecLocalCenter ); vecLocalCenter *= 0.5f; if ( ( pCollideable->GetCollisionAngles() == vec3_angle ) || ( vecLocalCenter == vec3_origin ) ) { VectorAdd( vecLocalCenter, pCollideable->GetCollisionOrigin(), *pCenter ); } else { VectorTransform( vecLocalCenter, pCollideable->CollisionToWorldTransform(), *pCenter ); } } //----------------------------------------------------------------------------- // Returns the world-align bounds of an entity //----------------------------------------------------------------------------- void CM_WorldAlignBounds( ICollideable *pCollideable, Vector *pMins, Vector *pMaxs ) { if ( pCollideable->GetCollisionAngles() == vec3_angle ) { *pMins = pCollideable->OBBMins(); *pMaxs = pCollideable->OBBMaxs(); } else { ITransformAABB( pCollideable->CollisionToWorldTransform(), pCollideable->OBBMins(), pCollideable->OBBMaxs(), *pMins, *pMaxs ); *pMins -= pCollideable->GetCollisionOrigin(); *pMaxs -= pCollideable->GetCollisionOrigin(); } } //----------------------------------------------------------------------------- // Returns the world-space bounds of an entity //----------------------------------------------------------------------------- void CM_WorldSpaceBounds( ICollideable *pCollideable, Vector *pMins, Vector *pMaxs ) { if ( pCollideable->GetCollisionAngles() == vec3_angle ) { VectorAdd( pCollideable->GetCollisionOrigin(), pCollideable->OBBMins(), *pMins ); VectorAdd( pCollideable->GetCollisionOrigin(), pCollideable->OBBMaxs(), *pMaxs ); } else { TransformAABB( pCollideable->CollisionToWorldTransform(), pCollideable->OBBMins(), pCollideable->OBBMaxs(), *pMins, *pMaxs ); } } void CM_SetupAreaFloodNums( byte areaFloodNums[MAX_MAP_AREAS], int *pNumAreas ) { CCollisionBSPData *pBSPData = GetCollisionBSPData(); *pNumAreas = pBSPData->numareas; if ( pBSPData->numareas > MAX_MAP_AREAS ) Error( "pBSPData->numareas > MAX_MAP_AREAS" ); for ( int i=0; i < pBSPData->numareas; i++ ) { Assert( pBSPData->map_areas[i].floodnum < MAX_MAP_AREAS ); areaFloodNums[i] = (byte)pBSPData->map_areas[i].floodnum; } } // ----------------------------------------------------------------------------- // CFastLeafAccessor implementation. // ----------------------------------------------------------------------------- CFastPointLeafNum::CFastPointLeafNum() { Reset(); } int CFastPointLeafNum::GetLeaf( const Vector &vPos ) { CCollisionBSPData *pBSPData = GetCollisionBSPData(); if ( m_iCachedLeaf < 0 || m_iCachedLeaf >= pBSPData->numleafs || vPos.DistToSqr( m_vCachedPos ) > m_flDistToExitLeafSqr ) { m_vCachedPos = vPos; m_flDistToExitLeafSqr = 1e24; m_iCachedLeaf = CM_PointLeafnumMinDistSqr_r( pBSPData, vPos, 0, m_flDistToExitLeafSqr ); } Assert( m_iCachedLeaf >= 0 ); Assert( m_iCachedLeaf < pBSPData->numleafs ); return m_iCachedLeaf; } void CFastPointLeafNum::Reset( void ) { m_iCachedLeaf = -1; m_flDistToExitLeafSqr = -1; m_vCachedPos.Init(); } bool FASTCALL IsBoxIntersectingRayNoLowest( fltx4 boxMin, fltx4 boxMax, const fltx4 & origin, const fltx4 & delta, const fltx4 & invDelta, // ray parameters const fltx4 & vTolerance ///< eg from ReplicateX4(flTolerance) ) { /* Assert( boxMin[0] <= boxMax[0] ); Assert( boxMin[1] <= boxMax[1] ); Assert( boxMin[2] <= boxMax[2] ); */ #if defined(_X360) && defined(DBGFLAG_ASSERT) unsigned int r; AssertMsg( (XMVectorGreaterOrEqualR(&r, SetWToZeroSIMD(boxMax),SetWToZeroSIMD(boxMin)), XMComparisonAllTrue(r)), "IsBoxIntersectingRay : boxmax < boxmin" ); #endif // test if delta is tiny along any dimension bi32x4 bvDeltaTinyComponents = CmpInBoundsSIMD( delta, Four_Epsilons ); // push box extents out by tolerance (safe to do because pass by copy, not ref) boxMin = SubSIMD(boxMin, vTolerance); boxMax = AddSIMD(boxMax, vTolerance); // for the very short components of the ray, check if the origin is inside the box; // if not, then it doesn't intersect. bi32x4 bvOriginOutsideBox = OrSIMD( CmpLtSIMD(origin,boxMin), CmpGtSIMD(origin,boxMax) ); bvDeltaTinyComponents = SetWToZeroSIMD(bvDeltaTinyComponents); // work out entry and exit points for the ray. This may produce strange results for // very short delta components, but those will be masked out by bvDeltaTinyComponents // anyway. We could early-out on bvOriginOutsideBox, but it won't be ready to branch // on for fourteen cycles. fltx4 vt1 = SubSIMD( boxMin, origin ); fltx4 vt2 = SubSIMD( boxMax, origin ); vt1 = MulSIMD( vt1, invDelta ); vt2 = MulSIMD( vt2, invDelta ); // ensure that vt1 #include "pixelwriter.h" struct inpix_t { float channels[4]; }; CON_COMMAND( ps3_testf16, "test float116" ) { volatile static bool trapper = true; const int nPix = atoi(args[1]); CUtlMemory memOld; memOld.EnsureCapacity( nPix * 4 ); CUtlMemory memNu; memNu.EnsureCapacity( nPix * 4 ); // set to be one huge row of pixels CPixelWriter vOldWay; vOldWay.SetPixelMemory( IMAGE_FORMAT_RGBA16161616F, memOld.Base(), nPix * 4 * sizeof(float16) ); vOldWay.bIgnorePS3NOCHECKIN = true; CPixelWriter vNewWay; vNewWay.bIgnorePS3NOCHECKIN = false; vNewWay.SetPixelMemory( IMAGE_FORMAT_RGBA16161616F, memOld.Base(), nPix * 4 * sizeof(float16) ); CUtlVector indata; indata.EnsureCount( nPix ); static const float mint = exp2f(-14); for ( int i = 0 ; i < nPix ; ++i ) { for ( int j = 0 ; j < 4 ; ++j ) { indata[i].channels[j] = RandomFloat( mint, maxfloat16bits * 2 ); } } for ( int i = 0 ; i < nPix ; ++i ) { vOldWay.WritePixelF( indata[i].channels[0], indata[i].channels[1], indata[i].channels[2], indata[i].channels[3] ); } vNewWay.WriteManyPixelTo16BitF( indata[0].channels, nPix ); const float16 *pOldPixels = (const float16 *)vOldWay.GetPixelMemory(); const float16 *pNewPixels = (const float16 *)vNewWay.GetPixelMemory(); for ( int i = 0 ; i < nPix * 4 ; ++i ) { if ( pOldPixels[i] != pNewPixels[i] ) { Msg( "%f -> %f %f\t%x %x\n", indata[i], pOldPixels[i], pNewPixels[i], pOldPixels[i].GetBits(), pNewPixels[i].GetBits() ); Assert(false); } } #if 0 static float fins[65536]; // build an array of every float that can be represented // in a float16 for ( uint i = 0 ; i <= 65535 ; ++i ) { short s = i; float16 foo; memcpy( &foo, &s, sizeof(short) ); fins[i] = foo.GetFloat(); } int bottom; int top; { float16 fbot; fbot.SetFloat( exp2f(-14) ); float16 ftop; ftop.SetFloat( maxfloat16bits ); bottom = fbot.GetBits(); top = ftop.GetBits(); } // now test conversion back for ( uint i = bottom ; i <= top ; i+=4 ) { float16 oldway[4]; float16 neway[4]; oldway[0].SetFloat( fins[i+0] ); oldway[1].SetFloat( fins[i+1] ); oldway[2].SetFloat( fins[i+2] ); oldway[3].SetFloat( fins[i+3] ); float16::ConvertFourFloatsTo16BitsAtOnce( neway, fins+i+0, fins+i+1, fins+i+2, fins+i+3 ); for ( int q = 0 ; q < 4 ; ++q ) { if ( oldway[q] != neway[q] && neway[q].GetBits() > 0 ) { if ( trapper ) DebuggerBreak(); Msg( "%f -> %f %f\t%x %x\n", fins[i+q], oldway[q].GetFloat(), neway[q].GetFloat(), oldway[q].GetBits(), neway[q].GetBits() ); } /* if ( neway[q].GetFloat() > 0 ) { if ( trapper ) DebuggerBreak(); Msg( "%f -> %f %f\t%x %x\n", fins[i+q], oldway[q].GetFloat(), neway[q].GetFloat(), oldway[q].GetBits(), neway[q].GetBits() ); } */ } } #endif #if 0 float16 a,b; a.SetFloat(1.0f); b = 1.0f; short c = float16::ConvertFloatTo16bits(1.0f); short d = float16::ConvertFloatTo16bitsNonDefault(1.0f); Msg("%f %f %x %x\n", a,b ,c ,d ); float foo[128]; float16 bar[128]; short quux[128]; for ( int i = 0 ; i < 128 ; ++i ) { foo[i] = RandomFloat( -65535, 65535 ); bar[i] = foo[i]; quux[i] = float16::ConvertFloatTo16bitsNonDefault(foo[i]); } for ( int i = 0 ; i < 128 ; ++i ) { if ( bar[i].GetBits() != quux[i] ) { Msg( "%f -> %f %x %x \n", foo[i], bar[i].GetFloat(), bar[i].GetBits(), quux[i] ); } } #endif } #endif