FORCEINLINE void Check( T *pEntity ) { // Hmmm.. everything in this list should be a trigger.... ICollideable *pTriggerCollideable = pEntity->GetCollideable(); if ( !m_pCollide->ShouldTouchTrigger(pTriggerCollideable->GetSolidFlags()) ) return; if ( pTriggerCollideable->GetSolidFlags() & FSOLID_USE_TRIGGER_BOUNDS ) { Vector vecTriggerMins, vecTriggerMaxs; pTriggerCollideable->WorldSpaceTriggerBounds( &vecTriggerMins, &vecTriggerMaxs ); if ( !IsBoxIntersectingRay( vecTriggerMins, vecTriggerMaxs, m_Ray ) ) { return; } } else { trace_t tr; enginetrace->ClipRayToCollideable( m_Ray, MASK_SOLID, pTriggerCollideable, &tr ); if ( !(tr.contents & MASK_SOLID) ) return; } trace_t tr; UTIL_ClearTrace( tr ); tr.endpos = (m_pEnt->GetAbsOrigin() + pEntity->GetAbsOrigin()) * 0.5; m_pEnt->PhysicsMarkEntitiesAsTouching( pEntity, tr ); }
IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ) { // Static props should never be in the trigger list Assert( !StaticPropMgr()->IsStaticProp( pHandleEntity ) ); IServerNetworkable *pNetworkable = static_cast<IServerNetworkable*>( pHandleEntity ); Assert( pNetworkable ); // Convert the IHandleEntity to an edict_t*... // Context is the thing we're testing everything against edict_t* pTouch = pNetworkable->GetEdict(); // Can't bump against itself if ( pTouch == m_pEnt ) return ITERATION_CONTINUE; IServerEntity *serverEntity = pTouch->GetIServerEntity(); if ( !serverEntity ) return ITERATION_CONTINUE; // Hmmm.. everything in this list should be a trigger.... ICollideable *pCollideable = serverEntity->GetCollideable(); Assert(pCollideable->GetSolidFlags() & FSOLID_TRIGGER ); if ( (pCollideable->GetSolidFlags() & FSOLID_TRIGGER) == 0 ) return ITERATION_CONTINUE; model_t* pModel = sv.GetModel( pCollideable->GetCollisionModelIndex() ); if ( pModel && pModel->type == mod_brush ) { int headnode = SV_HullForEntity( pTouch ); int contents; if (!m_Ray.m_IsSwept) { contents = CM_TransformedBoxContents( m_Ray.m_Start, m_mins, m_maxs, headnode, serverEntity->GetAbsOrigin(), serverEntity->GetAbsAngles() ); } else { trace_t trace; CM_TransformedBoxTrace( m_Ray, headnode, MASK_ALL, serverEntity->GetAbsOrigin(), serverEntity->GetAbsAngles(), trace ); contents = trace.contents; } if ( !(contents & MASK_SOLID) ) return ITERATION_CONTINUE; } m_TouchedEntities.AddToTail( pTouch ); return ITERATION_CONTINUE; }
IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ) { ICollideable *pCollide; const char *pDbgName; m_pEngineTrace->HandleEntityToCollideable( pHandleEntity, &pCollide, &pDbgName ); if (!pCollide) return ITERATION_CONTINUE; // Deal with static props // NOTE: I could have added static props to a different list and // enumerated them separately, but that would have been less efficient if ( StaticPropMgr()->IsStaticProp( pHandleEntity ) ) { Ray_t ray; trace_t trace; ray.Init( m_Pos, m_Pos ); m_pEngineTrace->ClipRayToCollideable( ray, MASK_ALL, pCollide, &trace ); if (trace.startsolid) { // We're in a static prop; that's solid baby // Pretend we hit the world m_Contents = CONTENTS_SOLID; m_pCollide = m_pEngineTrace->GetWorldCollideable(); return ITERATION_STOP; } return ITERATION_CONTINUE; } // We only care about solid volumes if ((pCollide->GetSolidFlags() & FSOLID_VOLUME_CONTENTS) == 0) return ITERATION_CONTINUE; model_t* pModel = (model_t*)pCollide->GetCollisionModel(); if ( pModel && pModel->type == mod_brush ) { Assert( pCollide->GetCollisionModelIndex() < MAX_MODELS && pCollide->GetCollisionModelIndex() >= 0 ); int nHeadNode = GetModelHeadNode( pCollide ); int contents = CM_TransformedPointContents( m_Pos, nHeadNode, pCollide->GetCollisionOrigin(), pCollide->GetCollisionAngles() ); if (contents != CONTENTS_EMPTY) { // Return the contents of the first thing we hit m_Contents = contents; m_pCollide = pCollide; return ITERATION_STOP; } } return ITERATION_CONTINUE; }
/* =============== SV_LinkEdict =============== */ void SV_LinkEdict( edict_t *ent, qboolean touch_triggers, const Vector* pPrevAbsOrigin ) { IServerEntity *pServerEntity = ent->GetIServerEntity(); if ( !pServerEntity ) return; // Remove it from whatever lists it may be in at the moment // We'll re-add it below if we need to. if (ent->partition != PARTITION_INVALID_HANDLE) { SpatialPartition()->Remove( ent->partition ); } if (ent->free) return; if (ent == sv.edicts) return; // don't add the world pServerEntity->CalcAbsolutePosition(); // set the abs box pServerEntity->SetObjectCollisionBox(); // link to PVS leafs SV_BuildEntityClusterList( ent ); // Update KD tree information ICollideable *pCollide = pServerEntity->GetCollideable(); if (ent->partition == PARTITION_INVALID_HANDLE) { // Here, we haven't added the entity to the partition before // So we have to make a new partition handle. ent->partition = SpatialPartition()->CreateHandle( pCollide->GetEntityHandle() ); } // Here, we indicate the entity has moved. Note that this call does // some fast early-outing to prevent unnecessary work SpatialPartition()->ElementMoved( ent->partition, pServerEntity->GetAbsMins(), pServerEntity->GetAbsMaxs() ); // Make sure it's in the list of all entities SpatialPartition()->Insert( PARTITION_ENGINE_NON_STATIC_EDICTS, ent->partition ); SolidType_t iSolid = pCollide->GetSolid(); int nSolidFlags = pCollide->GetSolidFlags(); bool bIsSolid = IsSolid( iSolid, nSolidFlags ) || ((nSolidFlags & FSOLID_TRIGGER) != 0); if ( !bIsSolid ) { // If this ent's touch list isn't empty, it's transitioning to not solid if ( pServerEntity->IsCurrentlyTouching() ) { // mark ent so that at the end of frame it will check to see if it's no longer touch ents pServerEntity->SetCheckUntouch( true ); } return; } // Insert it into the appropriate lists. // We have to continually reinsert it because its solid type may have changed SpatialPartitionListMask_t mask = 0; if (( nSolidFlags & FSOLID_NOT_SOLID ) == 0) { mask |= PARTITION_ENGINE_SOLID_EDICTS; } if (( nSolidFlags & FSOLID_TRIGGER ) != 0 ) { mask |= PARTITION_ENGINE_TRIGGER_EDICTS; } Assert( mask != 0 ); SpatialPartition()->Insert( mask, ent->partition ); if ( iSolid == SOLID_BSP ) { model_t *model = sv.GetModel( pServerEntity->GetModelIndex() ); if ( !model && strlen( STRING( pServerEntity->GetModelName() ) ) == 0 ) { Con_DPrintf( "Inserted %s with no model\n", STRING( ent->classname ) ); return; } } // if touch_triggers, touch all entities at this node and descend for more if (touch_triggers) { // mark ent so that at the end of frame it will check to see if it's no longer touch ents pServerEntity->SetCheckUntouch( true ); // If this is a trigger that's moved, then query the solid list instead if ( mask == PARTITION_ENGINE_TRIGGER_EDICTS ) { CTriggerMoved triggerEnum( ent ); SpatialPartition()->EnumerateElementsInBox( PARTITION_ENGINE_SOLID_EDICTS, pServerEntity->GetAbsMins(), pServerEntity->GetAbsMaxs(), false, &triggerEnum ); triggerEnum.HandleTouchedEntities( ); } else { if (!pPrevAbsOrigin) { CTouchLinks touchEnumerator(ent, NULL); SpatialPartition()->EnumerateElementsInBox( PARTITION_ENGINE_TRIGGER_EDICTS, pServerEntity->GetAbsMins(), pServerEntity->GetAbsMaxs(), false, &touchEnumerator ); touchEnumerator.HandleTouchedEntities( ); } else { CTouchLinks touchEnumerator(ent, pPrevAbsOrigin); // A version that checks against an extruded ray indicating the motion SpatialPartition()->EnumerateElementsAlongRay( PARTITION_ENGINE_TRIGGER_EDICTS, touchEnumerator.m_Ray, false, &touchEnumerator ); touchEnumerator.HandleTouchedEntities( ); } } } }
//----------------------------------------------------------------------------- // A version that simply accepts a ray (can work as a traceline or tracehull) //----------------------------------------------------------------------------- void CEngineTrace::TraceRay( const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace ) { CTraceFilterHitAll traceFilter; if ( !pTraceFilter ) { pTraceFilter = &traceFilter; } // Gather statistics. g_EngineStats.IncrementCountedStat( ENGINE_STATS_NUM_TRACE_LINES, 1 ); MEASURE_TIMED_STAT( ENGINE_STATS_TRACE_LINE_TIME ); CM_ClearTrace( pTrace ); // Collide with the world. if ( pTraceFilter->GetTraceType() != TRACE_ENTITIES_ONLY ) { ICollideable *pCollide = GetWorldCollideable(); // Make sure the world entity is unrotated // FIXME: BAH! The !pCollide test here is because of // CStaticProp::PrecacheLighting.. it's occurring too early // need to fix that later Assert(!pCollide || pCollide->GetCollisionOrigin() == vec3_origin ); Assert(!pCollide || pCollide->GetCollisionAngles() == vec3_angle ); CM_BoxTrace( ray, 0, fMask, true, *pTrace ); SetTraceEntity( pCollide, pTrace ); // Blocked by the world. if ( pTrace->fraction == 0 ) return; // Early out if we only trace against the world if ( pTraceFilter->GetTraceType() == TRACE_WORLD_ONLY ) return; } // Save the world collision fraction. float flWorldFraction = pTrace->fraction; // Create a ray that extends only until we hit the world // and adjust the trace accordingly Ray_t entityRay = ray; entityRay.m_Delta *= pTrace->fraction; // We know this is safe because if pTrace->fraction == 0 // we would have exited above pTrace->fractionleftsolid /= pTrace->fraction; pTrace->fraction = 1.0; // Collide with entities along the ray // FIXME: Hitbox code causes this to be re-entrant for the IK stuff. // If we could eliminate that, this could be static and therefore // not have to reallocate memory all the time CEntitiesAlongRay enumerator; enumerator.Reset(); SpatialPartition()->EnumerateElementsAlongRay( SpatialPartitionMask(), entityRay, false, &enumerator ); bool bNoStaticProps = pTraceFilter->GetTraceType() == TRACE_ENTITIES_ONLY; bool bFilterStaticProps = pTraceFilter->GetTraceType() == TRACE_EVERYTHING_FILTER_PROPS; trace_t tr; ICollideable *pCollideable; const char *pDebugName; int nCount = enumerator.m_EntityHandles.Count(); for ( int i = 0; i < nCount; ++i ) { // Generate a collideable IHandleEntity *pHandleEntity = enumerator.m_EntityHandles[i]; HandleEntityToCollideable( pHandleEntity, &pCollideable, &pDebugName ); // Check for error condition if ( !IsSolid( pCollideable->GetSolid(), pCollideable->GetSolidFlags() ) ) { char temp[1024]; Q_snprintf(temp, sizeof( temp ), "%s in solid list (not solid)\n", pDebugName ); Sys_Error (temp); } if ( !StaticPropMgr()->IsStaticProp( pHandleEntity ) ) { if ( !pTraceFilter->ShouldHitEntity( pHandleEntity, fMask ) ) continue; } else { // FIXME: Could remove this check here by // using a different spatial partition mask. Look into it // if we want more speedups here. if ( bNoStaticProps ) continue; if ( bFilterStaticProps ) { if ( !pTraceFilter->ShouldHitEntity( pHandleEntity, fMask ) ) continue; } } ClipRayToCollideable( entityRay, fMask, pCollideable, &tr ); // Make sure the ray is always shorter than it currently is ClipTraceToTrace( tr, pTrace ); // Stop if we're in allsolid if (pTrace->allsolid) break; } // Fix up the fractions so they are appropriate given the original // unclipped-to-world ray pTrace->fraction *= flWorldFraction; pTrace->fractionleftsolid *= flWorldFraction; #ifdef _DEBUG Vector vecOffset, vecEndTest; VectorAdd( ray.m_Start, ray.m_StartOffset, vecOffset ); VectorMA( vecOffset, pTrace->fractionleftsolid, ray.m_Delta, vecEndTest ); Assert( VectorsAreEqual( vecEndTest, pTrace->startpos, 0.1f ) ); VectorMA( vecOffset, pTrace->fraction, ray.m_Delta, vecEndTest ); Assert( VectorsAreEqual( vecEndTest, pTrace->endpos, 0.1f ) ); // Assert( !ray.m_IsRay || pTrace->allsolid || pTrace->fraction >= pTrace->fractionleftsolid ); #endif if ( !ray.m_IsRay ) { // Make sure no fractionleftsolid can be used with box sweeps VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos ); pTrace->fractionleftsolid = 0; #ifdef _DEBUG pTrace->fractionleftsolid = VEC_T_NAN; #endif } }