//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "portal_util_shared.h" #include "prop_portal_shared.h" #include "portal_shareddefs.h" #include "portal_collideable_enumerator.h" #include "beam_shared.h" #include "collisionutils.h" #include "util_shared.h" #ifndef CLIENT_DLL #include "util.h" #include "ndebugoverlay.h" #include "env_debughistory.h" #else #include "c_portal_player.h" #endif #include "PortalSimulation.h" bool g_bAllowForcePortalTrace = false; bool g_bForcePortalTrace = false; bool g_bBulletPortalTrace = false; ConVar sv_portal_trace_vs_world ("sv_portal_trace_vs_world", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Use traces against portal environment world geometry" ); ConVar sv_portal_trace_vs_displacements ("sv_portal_trace_vs_displacements", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Use traces against portal environment displacement geometry" ); ConVar sv_portal_trace_vs_holywall ("sv_portal_trace_vs_holywall", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Use traces against portal environment carved wall" ); ConVar sv_portal_trace_vs_staticprops ("sv_portal_trace_vs_staticprops", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Use traces against portal environment static prop geometry" ); ConVar sv_use_find_closest_passable_space ("sv_use_find_closest_passable_space", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Enables heavy-handed player teleporting stuck fix code." ); ConVar sv_use_transformed_collideables("sv_use_transformed_collideables", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Disables traces against remote portal moving entities using transforms to bring them into local space." ); class CTransformedCollideable : public ICollideable //wraps an existing collideable, but transforms everything that pertains to world space by another transform { public: VMatrix m_matTransform; //the transformation we apply to the wrapped collideable VMatrix m_matInvTransform; //cached inverse of m_matTransform ICollideable *m_pWrappedCollideable; //the collideable we're transforming without it knowing struct CTC_ReferenceVars_t { Vector m_vCollisionOrigin; QAngle m_qCollisionAngles; matrix3x4_t m_matCollisionToWorldTransform; matrix3x4_t m_matRootParentToWorldTransform; }; mutable CTC_ReferenceVars_t m_ReferencedVars; //when returning a const reference, it needs to point to something, so here we go //abstract functions which require no transforms, just pass them along to the wrapped collideable virtual IHandleEntity *GetEntityHandle() { return m_pWrappedCollideable->GetEntityHandle(); } virtual const Vector& OBBMinsPreScaled() const { return m_pWrappedCollideable->OBBMinsPreScaled(); } virtual const Vector& OBBMaxsPreScaled() const { return m_pWrappedCollideable->OBBMaxsPreScaled(); } virtual const Vector& OBBMins() const { return m_pWrappedCollideable->OBBMins(); } virtual const Vector& OBBMaxs() const { return m_pWrappedCollideable->OBBMaxs(); } virtual int GetCollisionModelIndex() { return m_pWrappedCollideable->GetCollisionModelIndex(); } virtual const model_t* GetCollisionModel() { return m_pWrappedCollideable->GetCollisionModel(); } virtual SolidType_t GetSolid() const { return m_pWrappedCollideable->GetSolid(); } virtual int GetSolidFlags() const { return m_pWrappedCollideable->GetSolidFlags(); } virtual IClientUnknown* GetIClientUnknown() { return m_pWrappedCollideable->GetIClientUnknown(); } virtual int GetCollisionGroup() const { return m_pWrappedCollideable->GetCollisionGroup(); } virtual bool ShouldTouchTrigger( int triggerSolidFlags ) const { return m_pWrappedCollideable->ShouldTouchTrigger(triggerSolidFlags); } //slightly trickier functions virtual void WorldSpaceTriggerBounds( Vector *pVecWorldMins, Vector *pVecWorldMaxs ) const; virtual bool TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ); virtual bool TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ); virtual const Vector& GetCollisionOrigin() const; virtual const QAngle& GetCollisionAngles() const; virtual const matrix3x4_t& CollisionToWorldTransform() const; virtual void WorldSpaceSurroundingBounds( Vector *pVecMins, Vector *pVecMaxs ); virtual const matrix3x4_t *GetRootParentToWorldTransform() const; }; void CTransformedCollideable::WorldSpaceTriggerBounds( Vector *pVecWorldMins, Vector *pVecWorldMaxs ) const { m_pWrappedCollideable->WorldSpaceTriggerBounds( pVecWorldMins, pVecWorldMaxs ); if( pVecWorldMins ) *pVecWorldMins = m_matTransform * (*pVecWorldMins); if( pVecWorldMaxs ) *pVecWorldMaxs = m_matTransform * (*pVecWorldMaxs); } bool CTransformedCollideable::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ) { //TODO: Transform the ray by inverse matTransform and transform the trace results by matTransform? AABB Errors arise by transforming the ray. return m_pWrappedCollideable->TestCollision( ray, fContentsMask, tr ); } bool CTransformedCollideable::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ) { //TODO: Transform the ray by inverse matTransform and transform the trace results by matTransform? AABB Errors arise by transforming the ray. return m_pWrappedCollideable->TestHitboxes( ray, fContentsMask, tr ); } const Vector& CTransformedCollideable::GetCollisionOrigin() const { m_ReferencedVars.m_vCollisionOrigin = m_matTransform * m_pWrappedCollideable->GetCollisionOrigin(); return m_ReferencedVars.m_vCollisionOrigin; } const QAngle& CTransformedCollideable::GetCollisionAngles() const { m_ReferencedVars.m_qCollisionAngles = TransformAnglesToWorldSpace( m_pWrappedCollideable->GetCollisionAngles(), m_matTransform.As3x4() ); return m_ReferencedVars.m_qCollisionAngles; } const matrix3x4_t& CTransformedCollideable::CollisionToWorldTransform() const { //1-2 order correct? ConcatTransforms( m_matTransform.As3x4(), m_pWrappedCollideable->CollisionToWorldTransform(), m_ReferencedVars.m_matCollisionToWorldTransform ); return m_ReferencedVars.m_matCollisionToWorldTransform; } void CTransformedCollideable::WorldSpaceSurroundingBounds( Vector *pVecMins, Vector *pVecMaxs ) { if( (pVecMins == NULL) && (pVecMaxs == NULL) ) return; Vector vMins, vMaxs; m_pWrappedCollideable->WorldSpaceSurroundingBounds( &vMins, &vMaxs ); TransformAABB( m_matTransform.As3x4(), vMins, vMaxs, vMins, vMaxs ); if( pVecMins ) *pVecMins = vMins; if( pVecMaxs ) *pVecMaxs = vMaxs; } const matrix3x4_t* CTransformedCollideable::GetRootParentToWorldTransform() const { const matrix3x4_t *pWrappedVersion = m_pWrappedCollideable->GetRootParentToWorldTransform(); if( pWrappedVersion == NULL ) return NULL; ConcatTransforms( m_matTransform.As3x4(), *pWrappedVersion, m_ReferencedVars.m_matRootParentToWorldTransform ); return &m_ReferencedVars.m_matRootParentToWorldTransform; } Color UTIL_Portal_Color( int iPortal ) { switch ( iPortal ) { case 0: // GRAVITY BEAM return Color( 242, 202, 167, 255 ); case 1: // PORTAL 1 return Color( 64, 160, 255, 255 ); case 2: // PORTAL 2 return Color( 255, 160, 32, 255 ); } return Color( 255, 255, 255, 255 ); } void UTIL_Portal_Trace_Filter( CTraceFilterSimpleClassnameList *traceFilterPortalShot ) { traceFilterPortalShot->AddClassnameToIgnore( "prop_physics" ); traceFilterPortalShot->AddClassnameToIgnore( "func_physbox" ); traceFilterPortalShot->AddClassnameToIgnore( "npc_portal_turret_floor" ); traceFilterPortalShot->AddClassnameToIgnore( "prop_energy_ball" ); traceFilterPortalShot->AddClassnameToIgnore( "npc_security_camera" ); traceFilterPortalShot->AddClassnameToIgnore( "player" ); traceFilterPortalShot->AddClassnameToIgnore( "simple_physics_prop" ); traceFilterPortalShot->AddClassnameToIgnore( "simple_physics_brush" ); traceFilterPortalShot->AddClassnameToIgnore( "prop_ragdoll" ); traceFilterPortalShot->AddClassnameToIgnore( "prop_glados_core" ); traceFilterPortalShot->AddClassnameToIgnore( "updateitem2" ); } CProp_Portal* UTIL_Portal_FirstAlongRay( const Ray_t &ray, float &fMustBeCloserThan ) { CProp_Portal *pIntersectedPortal = NULL; int iPortalCount = CProp_Portal_Shared::AllPortals.Count(); if( iPortalCount != 0 ) { CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base(); for( int i = 0; i != iPortalCount; ++i ) { CProp_Portal *pTempPortal = pPortals[i]; if( pTempPortal->IsActivedAndLinked() ) { float fIntersection = UTIL_IntersectRayWithPortal( ray, pTempPortal ); if( fIntersection >= 0.0f && fIntersection < fMustBeCloserThan ) { //within range, now check directionality if( pTempPortal->m_plane_Origin.normal.Dot( ray.m_Delta ) < 0.0f ) { //qualifies for consideration, now it just has to compete for closest pIntersectedPortal = pTempPortal; fMustBeCloserThan = fIntersection; } } } } } return pIntersectedPortal; } bool UTIL_Portal_TraceRay_Bullets( const CProp_Portal *pPortal, const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace, bool bTraceHolyWall ) { if( !pPortal || !pPortal->IsActivedAndLinked() ) { //not in a portal environment, use regular traces enginetrace->TraceRay( ray, fMask, pTraceFilter, pTrace ); return false; } trace_t trReal; enginetrace->TraceRay( ray, fMask, pTraceFilter, &trReal ); Vector vRayNormal = ray.m_Delta; VectorNormalize( vRayNormal ); Vector vPortalForward; pPortal->GetVectors( &vPortalForward, 0, 0 ); // If the ray isn't going into the front of the portal, just use the real trace if ( vPortalForward.Dot( vRayNormal ) > 0.0f ) { *pTrace = trReal; return false; } // If the real trace collides before the portal plane, just use the real trace float fPortalFraction = UTIL_IntersectRayWithPortal( ray, pPortal ); if ( fPortalFraction == -1.0f || trReal.fraction + 0.0001f < fPortalFraction ) { // Didn't intersect or the real trace intersected closer *pTrace = trReal; return false; } Ray_t rayPostPortal; rayPostPortal = ray; rayPostPortal.m_Start = ray.m_Start + ray.m_Delta * fPortalFraction; rayPostPortal.m_Delta = ray.m_Delta * ( 1.0f - fPortalFraction ); VMatrix matThisToLinked = pPortal->MatrixThisToLinked(); Ray_t rayTransformed; UTIL_Portal_RayTransform( matThisToLinked, rayPostPortal, rayTransformed ); // After a bullet traces through a portal it can hit the player that fired it CTraceFilterSimple *pSimpleFilter = dynamic_cast(pTraceFilter); const IHandleEntity *pPassEntity = NULL; if ( pSimpleFilter ) { pPassEntity = pSimpleFilter->GetPassEntity(); pSimpleFilter->SetPassEntity( 0 ); } trace_t trPostPortal; enginetrace->TraceRay( rayTransformed, fMask, pTraceFilter, &trPostPortal ); if ( pSimpleFilter ) { pSimpleFilter->SetPassEntity( pPassEntity ); } //trPostPortal.startpos = ray.m_Start; UTIL_Portal_PointTransform( matThisToLinked, ray.m_Start, trPostPortal.startpos ); trPostPortal.fraction = trPostPortal.fraction * ( 1.0f - fPortalFraction ) + fPortalFraction; *pTrace = trPostPortal; return true; } CProp_Portal* UTIL_Portal_TraceRay_Beam( const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, float *pfFraction ) { // Do a regular trace trace_t tr; UTIL_TraceLine( ray.m_Start, ray.m_Start + ray.m_Delta, fMask, pTraceFilter, &tr ); float fMustBeCloserThan = tr.fraction + 0.0001f; CProp_Portal *pIntersectedPortal = UTIL_Portal_FirstAlongRay( ray, fMustBeCloserThan ); *pfFraction = fMustBeCloserThan; //will be real trace distance if it didn't hit a portal return pIntersectedPortal; } bool UTIL_Portal_Trace_Beam( const CBeam *pBeam, Vector &vecStart, Vector &vecEnd, Vector &vecIntersectionStart, Vector &vecIntersectionEnd, ITraceFilter *pTraceFilter ) { vecStart = pBeam->GetAbsStartPos(); vecEnd = pBeam->GetAbsEndPos(); // Trace to see if we've intersected a portal float fEndFraction; Ray_t rayBeam; bool bIsReversed = ( pBeam->GetBeamFlags() & FBEAM_REVERSED ) != 0x0; if ( !bIsReversed ) rayBeam.Init( vecStart, vecEnd ); else rayBeam.Init( vecEnd, vecStart ); CProp_Portal *pPortal = UTIL_Portal_TraceRay_Beam( rayBeam, MASK_SHOT, pTraceFilter, &fEndFraction ); // If we intersected a portal we need to modify the start and end points to match the actual trace through portal drawing extents if ( !pPortal ) return false; // Modify the start and end points to match the actual trace through portal drawing extents vecStart = rayBeam.m_Start; Vector vecIntersection = rayBeam.m_Start + rayBeam.m_Delta * fEndFraction; int iNumLoops = 0; // Loop through the portals (at most 16 times) while ( pPortal && iNumLoops < 16 ) { // Get the point that we hit a portal or wall vecIntersectionStart = vecIntersection; VMatrix matThisToLinked = pPortal->MatrixThisToLinked(); // Get the transformed positions of the sub beam in the other portal's space UTIL_Portal_PointTransform( matThisToLinked, vecIntersectionStart, vecIntersectionEnd ); UTIL_Portal_PointTransform( matThisToLinked, rayBeam.m_Start + rayBeam.m_Delta, vecEnd ); CTraceFilterSkipClassname traceFilter( pPortal->m_hLinkedPortal, "prop_energy_ball", COLLISION_GROUP_NONE ); rayBeam.Init( vecIntersectionEnd, vecEnd ); pPortal = UTIL_Portal_TraceRay_Beam( rayBeam, MASK_SHOT, &traceFilter, &fEndFraction ); vecIntersection = rayBeam.m_Start + rayBeam.m_Delta * fEndFraction; ++iNumLoops; } vecEnd = vecIntersection; return true; } void UTIL_Portal_TraceRay_With( const CProp_Portal *pPortal, const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace, bool bTraceHolyWall ) { //check to see if the player is theoretically in a portal environment if( !pPortal || !pPortal->m_PortalSimulator.IsReadyToSimulate() ) { //not in a portal environment, use regular traces enginetrace->TraceRay( ray, fMask, pTraceFilter, pTrace ); } else { trace_t RealTrace; enginetrace->TraceRay( ray, fMask, pTraceFilter, &RealTrace ); trace_t PortalTrace; UTIL_Portal_TraceRay( pPortal, ray, fMask, pTraceFilter, &PortalTrace, bTraceHolyWall ); if( !g_bForcePortalTrace && !RealTrace.startsolid && PortalTrace.fraction <= RealTrace.fraction ) { *pTrace = RealTrace; return; } if ( g_bAllowForcePortalTrace ) { g_bForcePortalTrace = true; } *pTrace = PortalTrace; // If this ray has a delta, make sure its towards the portal before we try to trace across portals Vector vDirection = ray.m_Delta; VectorNormalize( vDirection ); Vector vPortalForward; pPortal->GetVectors( &vPortalForward, 0, 0 ); float flDot = -1.0f; if ( ray.m_IsSwept ) { flDot = vDirection.Dot( vPortalForward ); } // TODO: Translate extents of rays properly, tracing extruded box rays across portals causes collision bugs // Until this is fixed, we'll only test true rays across portals if ( flDot < 0.0f && /*PortalTrace.fraction == 1.0f &&*/ ray.m_IsRay) { // Check if we're hitting stuff on the other side of the portal trace_t PortalLinkedTrace; UTIL_PortalLinked_TraceRay( pPortal, ray, fMask, pTraceFilter, &PortalLinkedTrace, bTraceHolyWall ); if ( PortalLinkedTrace.fraction < pTrace->fraction ) { // Only collide with the cross-portal objects if this trace crossed a portal if ( UTIL_DidTraceTouchPortals( ray, PortalLinkedTrace ) ) { *pTrace = PortalLinkedTrace; } } } if( pTrace->fraction < 1.0f ) { pTrace->contents = RealTrace.contents; pTrace->surface = RealTrace.surface; } } } //----------------------------------------------------------------------------- // Purpose: Tests if a ray touches the surface of any portals // Input : ray - the ray to be tested against portal surfaces // trace - a filled-in trace corresponding to the parameter ray // Output : bool - false if the 'ray' parameter failed to hit any portal surface // pOutLocal - the portal touched (if any) // pOutRemote - the portal linked to the portal touched //----------------------------------------------------------------------------- bool UTIL_DidTraceTouchPortals( const Ray_t& ray, const trace_t& trace, CProp_Portal** pOutLocal, CProp_Portal** pOutRemote ) { int iPortalCount = CProp_Portal_Shared::AllPortals.Count(); if( iPortalCount == 0 ) { if( pOutLocal ) *pOutLocal = NULL; if( pOutRemote ) *pOutRemote = NULL; return false; } CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base(); CProp_Portal *pIntersectedPortal = NULL; if( ray.m_IsSwept ) { float fMustBeCloserThan = trace.fraction + 0.0001f; pIntersectedPortal = UTIL_Portal_FirstAlongRay( ray, fMustBeCloserThan ); } if( (pIntersectedPortal == NULL) && !ray.m_IsRay ) { //haven't hit anything yet, try again with box tests Vector ptRayEndPoint = trace.endpos - ray.m_StartOffset; // The trace added the start offset to the end position, so remove it for the box test CProp_Portal **pBoxIntersectsPortals = (CProp_Portal **)stackalloc( sizeof(CProp_Portal *) * iPortalCount ); int iBoxIntersectsPortalsCount = 0; for( int i = 0; i != iPortalCount; ++i ) { CProp_Portal *pTempPortal = pPortals[i]; if( (pTempPortal->m_bActivated) && (pTempPortal->m_hLinkedPortal.Get() != NULL) ) { if( UTIL_IsBoxIntersectingPortal( ptRayEndPoint, ray.m_Extents, pTempPortal, 0.00f ) ) { pBoxIntersectsPortals[iBoxIntersectsPortalsCount] = pTempPortal; ++iBoxIntersectsPortalsCount; } } } if( iBoxIntersectsPortalsCount > 0 ) { pIntersectedPortal = pBoxIntersectsPortals[0]; if( iBoxIntersectsPortalsCount > 1 ) { //hit more than one, use the closest float fDistToBeat = (ptRayEndPoint - pIntersectedPortal->GetAbsOrigin()).LengthSqr(); for( int i = 1; i != iBoxIntersectsPortalsCount; ++i ) { float fDist = (ptRayEndPoint - pBoxIntersectsPortals[i]->GetAbsOrigin()).LengthSqr(); if( fDist < fDistToBeat ) { pIntersectedPortal = pBoxIntersectsPortals[i]; fDistToBeat = fDist; } } } } } if( pIntersectedPortal == NULL ) { if( pOutLocal ) *pOutLocal = NULL; if( pOutRemote ) *pOutRemote = NULL; return false; } else { // Record the touched portals and return if( pOutLocal ) *pOutLocal = pIntersectedPortal; if( pOutRemote ) *pOutRemote = pIntersectedPortal->m_hLinkedPortal.Get(); return true; } } //----------------------------------------------------------------------------- // Purpose: Redirects the trace to either a trace that uses portal environments, or if a // global boolean is set, trace with a special bullets trace. // NOTE: UTIL_Portal_TraceRay_With will use the default world trace if it gets a NULL portal pointer // Input : &ray - the ray to use to trace // fMask - collision mask // *pTraceFilter - customizable filter on the trace // *pTrace - trace struct to fill with output info //----------------------------------------------------------------------------- CProp_Portal* UTIL_Portal_TraceRay( const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace, bool bTraceHolyWall ) { float fMustBeCloserThan = 2.0f; CProp_Portal *pIntersectedPortal = UTIL_Portal_FirstAlongRay( ray, fMustBeCloserThan ); if ( g_bBulletPortalTrace ) { if ( UTIL_Portal_TraceRay_Bullets( pIntersectedPortal, ray, fMask, pTraceFilter, pTrace, bTraceHolyWall ) ) return pIntersectedPortal; // Bullet didn't actually go through portal return NULL; } else { UTIL_Portal_TraceRay_With( pIntersectedPortal, ray, fMask, pTraceFilter, pTrace, bTraceHolyWall ); return pIntersectedPortal; } } CProp_Portal* UTIL_Portal_TraceRay( const Ray_t &ray, unsigned int fMask, const IHandleEntity *ignore, int collisionGroup, trace_t *pTrace, bool bTraceHolyWall ) { CTraceFilterSimple traceFilter( ignore, collisionGroup ); return UTIL_Portal_TraceRay( ray, fMask, &traceFilter, pTrace, bTraceHolyWall ); } //----------------------------------------------------------------------------- // Purpose: This version of traceray only traces against the portal environment of the specified portal. // Input : *pPortal - the portal whose physics we will trace against // &ray - the ray to trace with // fMask - collision mask // *pTraceFilter - customizable filter to determine what it hits // *pTrace - the trace struct to fill in with results // bTraceHolyWall - if this trace is to test against the 'holy wall' geometry //----------------------------------------------------------------------------- void UTIL_Portal_TraceRay( const CProp_Portal *pPortal, const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace, bool bTraceHolyWall ) { #ifdef CLIENT_DLL Assert( (GameRules() == NULL) || GameRules()->IsMultiplayer() ); #endif Assert( pPortal->m_PortalSimulator.IsReadyToSimulate() ); //a trace shouldn't make it down this far if the portal is incapable of changing the results of the trace CTraceFilterHitAll traceFilterHitAll; if ( !pTraceFilter ) { pTraceFilter = &traceFilterHitAll; } pTrace->fraction = 2.0f; pTrace->startsolid = true; pTrace->allsolid = true; trace_t TempTrace; int counter; const CPortalSimulator &portalSimulator = pPortal->m_PortalSimulator; CPortalSimulator *pLinkedPortalSimulator = portalSimulator.GetLinkedPortalSimulator(); //bool bTraceDisplacements = sv_portal_trace_vs_displacements.GetBool(); bool bTraceStaticProps = sv_portal_trace_vs_staticprops.GetBool(); if( sv_portal_trace_vs_holywall.GetBool() == false ) bTraceHolyWall = false; bool bTraceTransformedGeometry = ( (pLinkedPortalSimulator != NULL) && bTraceHolyWall && portalSimulator.RayIsInPortalHole( ray ) ); bool bCopyBackBrushTraceData = false; // Traces vs world if( pTraceFilter->GetTraceType() != TRACE_ENTITIES_ONLY ) { //trace_t RealTrace; //enginetrace->TraceRay( ray, fMask, pTraceFilter, &RealTrace ); if( portalSimulator.m_DataAccess.Simulation.Static.World.Brushes.pCollideable && sv_portal_trace_vs_world.GetBool() ) { physcollision->TraceBox( ray, portalSimulator.m_DataAccess.Simulation.Static.World.Brushes.pCollideable, vec3_origin, vec3_angle, pTrace ); bCopyBackBrushTraceData = true; } if( bTraceHolyWall ) { if( portalSimulator.m_DataAccess.Simulation.Static.Wall.Local.Tube.pCollideable ) { physcollision->TraceBox( ray, portalSimulator.m_DataAccess.Simulation.Static.Wall.Local.Tube.pCollideable, vec3_origin, vec3_angle, &TempTrace ); if( (TempTrace.startsolid == false) && (TempTrace.fraction < pTrace->fraction) ) //never allow something to be stuck in the tube, it's more of a last-resort guide than a real collideable { *pTrace = TempTrace; bCopyBackBrushTraceData = true; } } if( portalSimulator.m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable ) { physcollision->TraceBox( ray, portalSimulator.m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable, vec3_origin, vec3_angle, &TempTrace ); if( (TempTrace.fraction < pTrace->fraction) ) { *pTrace = TempTrace; bCopyBackBrushTraceData = true; } } //if( portalSimulator.m_DataAccess.Simulation.Static.Wall.RemoteTransformedToLocal.Brushes.pCollideable && sv_portal_trace_vs_world.GetBool() ) if( bTraceTransformedGeometry && pLinkedPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable ) { physcollision->TraceBox( ray, pLinkedPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable, portalSimulator.m_DataAccess.Placement.ptaap_LinkedToThis.ptOriginTransform, portalSimulator.m_DataAccess.Placement.ptaap_LinkedToThis.qAngleTransform, &TempTrace ); if( (TempTrace.fraction < pTrace->fraction) ) { *pTrace = TempTrace; bCopyBackBrushTraceData = true; } } } if( bCopyBackBrushTraceData ) { pTrace->surface = portalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.surface; pTrace->contents = portalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.contents; pTrace->m_pEnt = portalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.pEntity; bCopyBackBrushTraceData = false; } } // Traces vs entities if( pTraceFilter->GetTraceType() != TRACE_WORLD_ONLY ) { bool bFilterStaticProps = (pTraceFilter->GetTraceType() == TRACE_EVERYTHING_FILTER_PROPS); //solid entities CPortalCollideableEnumerator enumerator( pPortal ); partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS | PARTITION_ENGINE_STATIC_PROPS, ray, false, &enumerator ); for( counter = 0; counter != enumerator.m_iHandleCount; ++counter ) { if( staticpropmgr->IsStaticProp( enumerator.m_pHandles[counter] ) ) { //if( bFilterStaticProps && !pTraceFilter->ShouldHitEntity( enumerator.m_pHandles[counter], fMask ) ) continue; //static props are handled separately, with clipped versions } else if ( !pTraceFilter->ShouldHitEntity( enumerator.m_pHandles[counter], fMask ) ) { continue; } enginetrace->ClipRayToEntity( ray, fMask, enumerator.m_pHandles[counter], &TempTrace ); if( (TempTrace.fraction < pTrace->fraction) ) *pTrace = TempTrace; } if( bTraceStaticProps ) { //local clipped static props { int iLocalStaticCount = portalSimulator.m_DataAccess.Simulation.Static.World.StaticProps.ClippedRepresentations.Count(); if( iLocalStaticCount != 0 && portalSimulator.m_DataAccess.Simulation.Static.World.StaticProps.bCollisionExists ) { const PS_SD_Static_World_StaticProps_ClippedProp_t *pCurrentProp = portalSimulator.m_DataAccess.Simulation.Static.World.StaticProps.ClippedRepresentations.Base(); const PS_SD_Static_World_StaticProps_ClippedProp_t *pStop = pCurrentProp + iLocalStaticCount; Vector vTransform = vec3_origin; QAngle qTransform = vec3_angle; do { if( (!bFilterStaticProps) || pTraceFilter->ShouldHitEntity( pCurrentProp->pSourceProp, fMask ) ) { physcollision->TraceBox( ray, pCurrentProp->pCollide, vTransform, qTransform, &TempTrace ); if( (TempTrace.fraction < pTrace->fraction) ) { *pTrace = TempTrace; pTrace->surface.flags = pCurrentProp->iTraceSurfaceFlags; pTrace->surface.surfaceProps = pCurrentProp->iTraceSurfaceProps; pTrace->surface.name = pCurrentProp->szTraceSurfaceName; pTrace->contents = pCurrentProp->iTraceContents; pTrace->m_pEnt = pCurrentProp->pTraceEntity; } } ++pCurrentProp; } while( pCurrentProp != pStop ); } } if( bTraceHolyWall ) { //remote clipped static props transformed into our wall space if( bTraceTransformedGeometry && (pTraceFilter->GetTraceType() != TRACE_WORLD_ONLY) && sv_portal_trace_vs_staticprops.GetBool() ) { int iLocalStaticCount = pLinkedPortalSimulator->m_DataAccess.Simulation.Static.World.StaticProps.ClippedRepresentations.Count(); if( iLocalStaticCount != 0 ) { const PS_SD_Static_World_StaticProps_ClippedProp_t *pCurrentProp = pLinkedPortalSimulator->m_DataAccess.Simulation.Static.World.StaticProps.ClippedRepresentations.Base(); const PS_SD_Static_World_StaticProps_ClippedProp_t *pStop = pCurrentProp + iLocalStaticCount; Vector vTransform = portalSimulator.m_DataAccess.Placement.ptaap_LinkedToThis.ptOriginTransform; QAngle qTransform = portalSimulator.m_DataAccess.Placement.ptaap_LinkedToThis.qAngleTransform; do { if( (!bFilterStaticProps) || pTraceFilter->ShouldHitEntity( pCurrentProp->pSourceProp, fMask ) ) { physcollision->TraceBox( ray, pCurrentProp->pCollide, vTransform, qTransform, &TempTrace ); if( (TempTrace.fraction < pTrace->fraction) ) { *pTrace = TempTrace; pTrace->surface.flags = pCurrentProp->iTraceSurfaceFlags; pTrace->surface.surfaceProps = pCurrentProp->iTraceSurfaceProps; pTrace->surface.name = pCurrentProp->szTraceSurfaceName; pTrace->contents = pCurrentProp->iTraceContents; pTrace->m_pEnt = pCurrentProp->pTraceEntity; } } ++pCurrentProp; } while( pCurrentProp != pStop ); } } } } } if( pTrace->fraction > 1.0f ) //this should only happen if there was absolutely nothing to trace against { //AssertMsg( 0, "Nothing to trace against" ); memset( pTrace, 0, sizeof( trace_t ) ); pTrace->fraction = 1.0f; pTrace->startpos = ray.m_Start - ray.m_StartOffset; pTrace->endpos = pTrace->startpos + ray.m_Delta; } else if ( pTrace->fraction < 0 ) { // For all brush traces, use the 'portal backbrush' surface surface contents // BUGBUG: Doing this is a great solution because brushes near a portal // will have their contents and surface properties homogenized to the brush the portal ray hit. pTrace->contents = portalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.contents; pTrace->surface = portalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.surface; pTrace->m_pEnt = portalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.pEntity; } } void UTIL_Portal_TraceRay( const CProp_Portal *pPortal, const Ray_t &ray, unsigned int fMask, const IHandleEntity *ignore, int collisionGroup, trace_t *pTrace, bool bTraceHolyWall ) { CTraceFilterSimple traceFilter( ignore, collisionGroup ); UTIL_Portal_TraceRay( pPortal, ray, fMask, &traceFilter, pTrace, bTraceHolyWall ); } //----------------------------------------------------------------------------- // Purpose: Trace a ray 'past' a portal's surface, hitting objects in the linked portal's collision environment // Input : *pPortal - The portal being traced 'through' // &ray - The ray being traced // fMask - trace mask to cull results // *pTraceFilter - trace filter to cull results // *pTrace - Empty trace to return the result (value will be overwritten) //----------------------------------------------------------------------------- void UTIL_PortalLinked_TraceRay( const CProp_Portal *pPortal, const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace, bool bTraceHolyWall ) { #ifdef CLIENT_DLL Assert( (GameRules() == NULL) || GameRules()->IsMultiplayer() ); #endif // Transform the specified ray to the remote portal's space Ray_t rayTransformed; UTIL_Portal_RayTransform( pPortal->MatrixThisToLinked(), ray, rayTransformed ); AssertMsg ( ray.m_IsRay, "Ray with extents across portal tracing not implemented!" ); const CPortalSimulator &portalSimulator = pPortal->m_PortalSimulator; CProp_Portal *pLinkedPortal = (CProp_Portal*)(pPortal->m_hLinkedPortal.Get()); if( (pLinkedPortal == NULL) || (portalSimulator.RayIsInPortalHole( ray ) == false) ) { memset( pTrace, 0, sizeof(trace_t)); pTrace->fraction = 1.0f; pTrace->fractionleftsolid = 0; pTrace->contents = pPortal->m_PortalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.contents; pTrace->surface = pPortal->m_PortalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.surface; pTrace->m_pEnt = pPortal->m_PortalSimulator.m_DataAccess.Simulation.Static.SurfaceProperties.pEntity; return; } UTIL_Portal_TraceRay( pLinkedPortal, rayTransformed, fMask, pTraceFilter, pTrace, bTraceHolyWall ); // Transform the ray's start, end and plane back into this portal's space, // because we react to the collision as it is displayed, and the image is displayed with this local portal's orientation. VMatrix matLinkedToThis = pLinkedPortal->MatrixThisToLinked(); UTIL_Portal_PointTransform( matLinkedToThis, pTrace->startpos, pTrace->startpos ); UTIL_Portal_PointTransform( matLinkedToThis, pTrace->endpos, pTrace->endpos ); UTIL_Portal_PlaneTransform( matLinkedToThis, pTrace->plane, pTrace->plane ); } void UTIL_PortalLinked_TraceRay( const CProp_Portal *pPortal, const Ray_t &ray, unsigned int fMask, const IHandleEntity *ignore, int collisionGroup, trace_t *pTrace, bool bTraceHolyWall ) { CTraceFilterSimple traceFilter( ignore, collisionGroup ); UTIL_PortalLinked_TraceRay( pPortal, ray, fMask, &traceFilter, pTrace, bTraceHolyWall ); } //----------------------------------------------------------------------------- // Purpose: A version of trace entity which detects portals and translates the trace through portals //----------------------------------------------------------------------------- void UTIL_Portal_TraceEntity( CBaseEntity *pEntity, const Vector &vecAbsStart, const Vector &vecAbsEnd, unsigned int mask, ITraceFilter *pFilter, trace_t *pTrace ) { #ifdef CLIENT_DLL Assert( (GameRules() == NULL) || GameRules()->IsMultiplayer() ); Assert( pEntity->IsPlayer() ); CPortalSimulator *pPortalSimulator = NULL; if( pEntity->IsPlayer() ) { C_Prop_Portal *pPortal = ((C_Portal_Player *)pEntity)->m_hPortalEnvironment.Get(); if( pPortal ) pPortalSimulator = &pPortal->m_PortalSimulator; } #else CPortalSimulator *pPortalSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pEntity ); #endif memset( pTrace, 0, sizeof(trace_t)); pTrace->fraction = 1.0f; pTrace->fractionleftsolid = 0; ICollideable* pCollision = enginetrace->GetCollideable( pEntity ); // If main is simulating this object, trace as UTIL_TraceEntity would trace_t realTrace; QAngle qCollisionAngles = pCollision->GetCollisionAngles(); enginetrace->SweepCollideable( pCollision, vecAbsStart, vecAbsEnd, qCollisionAngles, mask, pFilter, &realTrace ); // For the below box test, we need to add the tolerance onto the extents, because the underlying // box on plane side test doesn't use the parameter tolerance. float flTolerance = 0.1f; Vector vEntExtents = pEntity->WorldAlignSize() * 0.5 + Vector ( flTolerance, flTolerance, flTolerance ); Vector vColCenter = realTrace.endpos + ( pEntity->WorldAlignMaxs() + pEntity->WorldAlignMins() ) * 0.5f; // If this entity is not simulated in a portal environment, trace as normal if( pPortalSimulator == NULL ) { // If main is simulating this object, trace as UTIL_TraceEntity would *pTrace = realTrace; } else { CPortalSimulator *pLinkedPortalSimulator = pPortalSimulator->GetLinkedPortalSimulator(); Ray_t entRay; entRay.Init( vecAbsStart, vecAbsEnd, pCollision->OBBMins(), pCollision->OBBMaxs() ); #if 0 // this trace for brush ents made sense at one time, but it's 'overcolliding' during portal transitions (bugzilla#25) if( realTrace.m_pEnt && (realTrace.m_pEnt->GetMoveType() != MOVETYPE_NONE) ) //started by hitting something moving which wouldn't be detected in the following traces { float fFirstPortalFraction = 2.0f; CProp_Portal *pFirstPortal = UTIL_Portal_FirstAlongRay( entRay, fFirstPortalFraction ); if ( !pFirstPortal ) *pTrace = realTrace; else { Vector vFirstPortalForward; pFirstPortal->GetVectors( &vFirstPortalForward, NULL, NULL ); if ( vFirstPortalForward.Dot( realTrace.endpos - pFirstPortal->GetAbsOrigin() ) > 0.0f ) *pTrace = realTrace; } } #endif // We require both environments to be active in order to trace against them Assert ( pCollision ); if ( !pCollision ) { return; } // World, displacements and holy wall are stored in separate collideables // Traces against each and keep the closest intersection (if any) trace_t tempTrace; // Hit the world if ( pFilter->GetTraceType() != TRACE_ENTITIES_ONLY ) { if( pPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable && sv_portal_trace_vs_world.GetBool() ) { //physcollision->TraceCollide( vecAbsStart, vecAbsEnd, pCollision, qCollisionAngles, // pPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable, vec3_origin, vec3_angle, &tempTrace ); physcollision->TraceBox( entRay, MASK_ALL, NULL, pPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable, vec3_origin, vec3_angle, &tempTrace ); if ( tempTrace.startsolid || (tempTrace.fraction < pTrace->fraction) ) { *pTrace = tempTrace; } } //if( pPortalSimulator->m_DataAccess.Simulation.Static.Wall.RemoteTransformedToLocal.Brushes.pCollideable && if( pLinkedPortalSimulator && pLinkedPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable && sv_portal_trace_vs_world.GetBool() && sv_portal_trace_vs_holywall.GetBool() ) { //physcollision->TraceCollide( vecAbsStart, vecAbsEnd, pCollision, qCollisionAngles, // pLinkedPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable, pPortalSimulator->m_DataAccess.Placement.ptaap_LinkedToThis.ptOriginTransform, pPortalSimulator->m_DataAccess.Placement.ptaap_LinkedToThis.qAngleTransform, &tempTrace ); physcollision->TraceBox( entRay, MASK_ALL, NULL, pLinkedPortalSimulator->m_DataAccess.Simulation.Static.World.Brushes.pCollideable, pPortalSimulator->m_DataAccess.Placement.ptaap_LinkedToThis.ptOriginTransform, pPortalSimulator->m_DataAccess.Placement.ptaap_LinkedToThis.qAngleTransform, &tempTrace ); if ( tempTrace.startsolid || (tempTrace.fraction < pTrace->fraction) ) { *pTrace = tempTrace; } } if ( pPortalSimulator->m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable && sv_portal_trace_vs_holywall.GetBool() ) { //physcollision->TraceCollide( vecAbsStart, vecAbsEnd, pCollision, qCollisionAngles, // pPortalSimulator->m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable, vec3_origin, vec3_angle, &tempTrace ); physcollision->TraceBox( entRay, MASK_ALL, NULL, pPortalSimulator->m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable, vec3_origin, vec3_angle, &tempTrace ); if ( tempTrace.startsolid || (tempTrace.fraction < pTrace->fraction) ) { if( tempTrace.fraction == 0.0f ) tempTrace.startsolid = true; if( tempTrace.fractionleftsolid == 1.0f ) tempTrace.allsolid = true; *pTrace = tempTrace; } } if ( pPortalSimulator->m_DataAccess.Simulation.Static.Wall.Local.Tube.pCollideable && sv_portal_trace_vs_holywall.GetBool() ) { //physcollision->TraceCollide( vecAbsStart, vecAbsEnd, pCollision, qCollisionAngles, // pPortalSimulator->m_DataAccess.Simulation.Static.Wall.Local.Tube.pCollideable, vec3_origin, vec3_angle, &tempTrace ); physcollision->TraceBox( entRay, MASK_ALL, NULL, pPortalSimulator->m_DataAccess.Simulation.Static.Wall.Local.Tube.pCollideable, vec3_origin, vec3_angle, &tempTrace ); if( (tempTrace.startsolid == false) && (tempTrace.fraction < pTrace->fraction) ) //never allow something to be stuck in the tube, it's more of a last-resort guide than a real collideable { *pTrace = tempTrace; } } // For all brush traces, use the 'portal backbrush' surface surface contents // BUGBUG: Doing this is a great solution because brushes near a portal // will have their contents and surface properties homogenized to the brush the portal ray hit. if ( pTrace->startsolid || (pTrace->fraction < 1.0f) ) { pTrace->surface = pPortalSimulator->m_DataAccess.Simulation.Static.SurfaceProperties.surface; pTrace->contents = pPortalSimulator->m_DataAccess.Simulation.Static.SurfaceProperties.contents; pTrace->m_pEnt = pPortalSimulator->m_DataAccess.Simulation.Static.SurfaceProperties.pEntity; } } // Trace vs entities if ( pFilter->GetTraceType() != TRACE_WORLD_ONLY ) { if( sv_portal_trace_vs_staticprops.GetBool() && (pFilter->GetTraceType() != TRACE_ENTITIES_ONLY) ) { bool bFilterStaticProps = (pFilter->GetTraceType() == TRACE_EVERYTHING_FILTER_PROPS); //local clipped static props { int iLocalStaticCount = pPortalSimulator->m_DataAccess.Simulation.Static.World.StaticProps.ClippedRepresentations.Count(); if( iLocalStaticCount != 0 && pPortalSimulator->m_DataAccess.Simulation.Static.World.StaticProps.bCollisionExists ) { const PS_SD_Static_World_StaticProps_ClippedProp_t *pCurrentProp = pPortalSimulator->m_DataAccess.Simulation.Static.World.StaticProps.ClippedRepresentations.Base(); const PS_SD_Static_World_StaticProps_ClippedProp_t *pStop = pCurrentProp + iLocalStaticCount; Vector vTransform = vec3_origin; QAngle qTransform = vec3_angle; do { if( (!bFilterStaticProps) || pFilter->ShouldHitEntity( pCurrentProp->pSourceProp, mask ) ) { //physcollision->TraceCollide( vecAbsStart, vecAbsEnd, pCollision, qCollisionAngles, // pCurrentProp->pCollide, vTransform, qTransform, &tempTrace ); physcollision->TraceBox( entRay, MASK_ALL, NULL, pCurrentProp->pCollide, vTransform, qTransform, &tempTrace ); if( tempTrace.startsolid || (tempTrace.fraction < pTrace->fraction) ) { *pTrace = tempTrace; pTrace->surface.flags = pCurrentProp->iTraceSurfaceFlags; pTrace->surface.surfaceProps = pCurrentProp->iTraceSurfaceProps; pTrace->surface.name = pCurrentProp->szTraceSurfaceName; pTrace->contents = pCurrentProp->iTraceContents; pTrace->m_pEnt = pCurrentProp->pTraceEntity; } } ++pCurrentProp; } while( pCurrentProp != pStop ); } } if( pLinkedPortalSimulator && pPortalSimulator->EntityIsInPortalHole( pEntity ) ) { #ifndef CLIENT_DLL if( sv_use_transformed_collideables.GetBool() ) //if this never gets turned off, it should be removed before release { //moving entities near the remote portal CBaseEntity *pEnts[1024]; int iEntCount = pLinkedPortalSimulator->GetMoveableOwnedEntities( pEnts, 1024 ); CTransformedCollideable transformedCollideable; transformedCollideable.m_matTransform = pLinkedPortalSimulator->m_DataAccess.Placement.matThisToLinked; transformedCollideable.m_matInvTransform = pLinkedPortalSimulator->m_DataAccess.Placement.matLinkedToThis; for( int i = 0; i != iEntCount; ++i ) { CBaseEntity *pRemoteEntity = pEnts[i]; if( pRemoteEntity->GetSolid() == SOLID_NONE ) continue; transformedCollideable.m_pWrappedCollideable = pRemoteEntity->GetCollideable(); Assert( transformedCollideable.m_pWrappedCollideable != NULL ); //enginetrace->ClipRayToCollideable( entRay, mask, &transformedCollideable, pTrace ); enginetrace->ClipRayToCollideable( entRay, mask, &transformedCollideable, &tempTrace ); if( tempTrace.startsolid || (tempTrace.fraction < pTrace->fraction) ) { *pTrace = tempTrace; } } } #endif //#ifndef CLIENT_DLL } } } if( pTrace->fraction == 1.0f ) { memset( pTrace, 0, sizeof( trace_t ) ); pTrace->fraction = 1.0f; pTrace->startpos = vecAbsStart; pTrace->endpos = vecAbsEnd; } //#endif } } void UTIL_Portal_PointTransform( const VMatrix matThisToLinked, const Vector &ptSource, Vector &ptTransformed ) { ptTransformed = matThisToLinked * ptSource; } void UTIL_Portal_VectorTransform( const VMatrix matThisToLinked, const Vector &vSource, Vector &vTransformed ) { vTransformed = matThisToLinked.ApplyRotation( vSource ); } void UTIL_Portal_AngleTransform( const VMatrix matThisToLinked, const QAngle &qSource, QAngle &qTransformed ) { qTransformed = TransformAnglesToWorldSpace( qSource, matThisToLinked.As3x4() ); } void UTIL_Portal_RayTransform( const VMatrix matThisToLinked, const Ray_t &raySource, Ray_t &rayTransformed ) { rayTransformed = raySource; UTIL_Portal_PointTransform( matThisToLinked, raySource.m_Start, rayTransformed.m_Start ); UTIL_Portal_VectorTransform( matThisToLinked, raySource.m_StartOffset, rayTransformed.m_StartOffset ); UTIL_Portal_VectorTransform( matThisToLinked, raySource.m_Delta, rayTransformed.m_Delta ); //BUGBUG: Extents are axis aligned, so rotating it won't necessarily give us what we're expecting UTIL_Portal_VectorTransform( matThisToLinked, raySource.m_Extents, rayTransformed.m_Extents ); //HACKHACK: Negative extents hang in traces, make each positive because we rotated it above if ( rayTransformed.m_Extents.x < 0.0f ) { rayTransformed.m_Extents.x = -rayTransformed.m_Extents.x; } if ( rayTransformed.m_Extents.y < 0.0f ) { rayTransformed.m_Extents.y = -rayTransformed.m_Extents.y; } if ( rayTransformed.m_Extents.z < 0.0f ) { rayTransformed.m_Extents.z = -rayTransformed.m_Extents.z; } } void UTIL_Portal_PlaneTransform( const VMatrix matThisToLinked, const cplane_t &planeSource, cplane_t &planeTransformed ) { planeTransformed = planeSource; Vector vTrans; UTIL_Portal_VectorTransform( matThisToLinked, planeSource.normal, planeTransformed.normal ); planeTransformed.dist = planeSource.dist * DotProduct( planeTransformed.normal, planeTransformed.normal ); planeTransformed.dist += DotProduct( planeTransformed.normal, matThisToLinked.GetTranslation( vTrans ) ); } void UTIL_Portal_PlaneTransform( const VMatrix matThisToLinked, const VPlane &planeSource, VPlane &planeTransformed ) { Vector vTranformedNormal; float fTransformedDist; Vector vTrans; UTIL_Portal_VectorTransform( matThisToLinked, planeSource.m_Normal, vTranformedNormal ); fTransformedDist = planeSource.m_Dist * DotProduct( vTranformedNormal, vTranformedNormal ); fTransformedDist += DotProduct( vTranformedNormal, matThisToLinked.GetTranslation( vTrans ) ); planeTransformed.Init( vTranformedNormal, fTransformedDist ); } void UTIL_Portal_Triangles( const Vector &ptPortalCenter, const QAngle &qPortalAngles, Vector pvTri1[ 3 ], Vector pvTri2[ 3 ] ) { // Get points to make triangles Vector vRight, vUp; AngleVectors( qPortalAngles, NULL, &vRight, &vUp ); Vector vTopEdge = vUp * PORTAL_HALF_HEIGHT; Vector vBottomEdge = -vTopEdge; Vector vRightEdge = vRight * PORTAL_HALF_WIDTH; Vector vLeftEdge = -vRightEdge; Vector vTopLeft = ptPortalCenter + vTopEdge + vLeftEdge; Vector vTopRight = ptPortalCenter + vTopEdge + vRightEdge; Vector vBottomLeft = ptPortalCenter + vBottomEdge + vLeftEdge; Vector vBottomRight = ptPortalCenter + vBottomEdge + vRightEdge; // Make triangles pvTri1[ 0 ] = vTopRight; pvTri1[ 1 ] = vTopLeft; pvTri1[ 2 ] = vBottomLeft; pvTri2[ 0 ] = vTopRight; pvTri2[ 1 ] = vBottomLeft; pvTri2[ 2 ] = vBottomRight; } void UTIL_Portal_Triangles( const CProp_Portal *pPortal, Vector pvTri1[ 3 ], Vector pvTri2[ 3 ] ) { UTIL_Portal_Triangles( pPortal->GetAbsOrigin(), pPortal->GetAbsAngles(), pvTri1, pvTri2 ); } float UTIL_Portal_DistanceThroughPortal( const CProp_Portal *pPortal, const Vector &vPoint1, const Vector &vPoint2 ) { return FastSqrt( UTIL_Portal_DistanceThroughPortalSqr( pPortal, vPoint1, vPoint2 ) ); } float UTIL_Portal_DistanceThroughPortalSqr( const CProp_Portal *pPortal, const Vector &vPoint1, const Vector &vPoint2 ) { if ( !pPortal || !pPortal->m_bActivated ) return -1.0f; CProp_Portal *pPortalLinked = pPortal->m_hLinkedPortal; if ( !pPortalLinked || !pPortalLinked->m_bActivated ) return -1.0f; return vPoint1.DistToSqr( pPortal->GetAbsOrigin() ) + pPortalLinked->GetAbsOrigin().DistToSqr( vPoint2 ); } float UTIL_Portal_ShortestDistance( const Vector &vPoint1, const Vector &vPoint2, CProp_Portal **pShortestDistPortal_Out /*= NULL*/, bool bRequireStraightLine /*= false*/ ) { return FastSqrt( UTIL_Portal_ShortestDistanceSqr( vPoint1, vPoint2, pShortestDistPortal_Out, bRequireStraightLine ) ); } float UTIL_Portal_ShortestDistanceSqr( const Vector &vPoint1, const Vector &vPoint2, CProp_Portal **pShortestDistPortal_Out /*= NULL*/, bool bRequireStraightLine /*= false*/ ) { float fMinDist = vPoint1.DistToSqr( vPoint2 ); int iPortalCount = CProp_Portal_Shared::AllPortals.Count(); if( iPortalCount == 0 ) { if( pShortestDistPortal_Out ) *pShortestDistPortal_Out = NULL; return fMinDist; } CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base(); CProp_Portal *pShortestDistPortal = NULL; for( int i = 0; i != iPortalCount; ++i ) { CProp_Portal *pTempPortal = pPortals[i]; if( pTempPortal->m_bActivated ) { CProp_Portal *pLinkedPortal = pTempPortal->m_hLinkedPortal.Get(); if( pLinkedPortal != NULL ) { Vector vPoint1Transformed = pTempPortal->MatrixThisToLinked() * vPoint1; float fDirectDist = vPoint1Transformed.DistToSqr( vPoint2 ); if( fDirectDist < fMinDist ) { //worth investigating further //find out if it's a straight line through the portal, or if we have to wrap around a corner float fPoint1TransformedDist = pLinkedPortal->m_plane_Origin.normal.Dot( vPoint1Transformed ) - pLinkedPortal->m_plane_Origin.dist; float fPoint2Dist = pLinkedPortal->m_plane_Origin.normal.Dot( vPoint2 ) - pLinkedPortal->m_plane_Origin.dist; bool bStraightLine = true; if( (fPoint1TransformedDist > 0.0f) || (fPoint2Dist < 0.0f) ) //straight line through portal impossible, part of the line has to backtrack to get to the portal surface bStraightLine = false; if( bStraightLine ) //if we're not already doing some crazy wrapping, find an intersection point { float fTotalDist = fPoint2Dist - fPoint1TransformedDist; //fPoint1TransformedDist is known to be negative Vector ptPlaneIntersection; if( fTotalDist != 0.0f ) { float fInvTotalDist = 1.0f / fTotalDist; ptPlaneIntersection = (vPoint1Transformed * (fPoint2Dist * fInvTotalDist)) + (vPoint2 * ((-fPoint1TransformedDist) * fInvTotalDist)); } else { ptPlaneIntersection = vPoint1Transformed; } Vector vRight, vUp; pLinkedPortal->GetVectors( NULL, &vRight, &vUp ); Vector ptLinkedCenter = pLinkedPortal->GetAbsOrigin(); Vector vCenterToIntersection = ptPlaneIntersection - ptLinkedCenter; float fRight = vRight.Dot( vCenterToIntersection ); float fUp = vUp.Dot( vCenterToIntersection ); float fAbsRight = fabs( fRight ); float fAbsUp = fabs( fUp ); if( (fAbsRight > PORTAL_HALF_WIDTH) || (fAbsUp > PORTAL_HALF_HEIGHT) ) bStraightLine = false; if( bStraightLine == false ) { if( bRequireStraightLine ) continue; //find the offending extent and shorten both extents to bring it into the portal quad float fNormalizer; if( fAbsRight > PORTAL_HALF_WIDTH ) { fNormalizer = fAbsRight/PORTAL_HALF_WIDTH; if( fAbsUp > PORTAL_HALF_HEIGHT ) { float fUpNormalizer = fAbsUp/PORTAL_HALF_HEIGHT; if( fUpNormalizer > fNormalizer ) fNormalizer = fUpNormalizer; } } else { fNormalizer = fAbsUp/PORTAL_HALF_HEIGHT; } vCenterToIntersection *= (1.0f/fNormalizer); ptPlaneIntersection = ptLinkedCenter + vCenterToIntersection; float fWrapDist = vPoint1Transformed.DistToSqr( ptPlaneIntersection ) + vPoint2.DistToSqr( ptPlaneIntersection ); if( fWrapDist < fMinDist ) { fMinDist = fWrapDist; pShortestDistPortal = pTempPortal; } } else { //it's a straight shot from point 1 to 2 through the portal fMinDist = fDirectDist; pShortestDistPortal = pTempPortal; } } else { if( bRequireStraightLine ) continue; //do some crazy wrapped line intersection algorithm //for now, just do the cheap and easy solution float fWrapDist = vPoint1.DistToSqr( pTempPortal->GetAbsOrigin() ) + pLinkedPortal->GetAbsOrigin().DistToSqr( vPoint2 ); if( fWrapDist < fMinDist ) { fMinDist = fWrapDist; pShortestDistPortal = pTempPortal; } } } } } } return fMinDist; } void UTIL_Portal_AABB( const CProp_Portal *pPortal, Vector &vMin, Vector &vMax ) { Vector vOrigin = pPortal->GetAbsOrigin(); QAngle qAngles = pPortal->GetAbsAngles(); Vector vOBBForward; Vector vOBBRight; Vector vOBBUp; AngleVectors( qAngles, &vOBBForward, &vOBBRight, &vOBBUp ); //scale the extents to usable sizes vOBBForward *= PORTAL_HALF_DEPTH; vOBBRight *= PORTAL_HALF_WIDTH; vOBBUp *= PORTAL_HALF_HEIGHT; vOrigin -= vOBBForward + vOBBRight + vOBBUp; vOBBForward *= 2.0f; vOBBRight *= 2.0f; vOBBUp *= 2.0f; vMin = vMax = vOrigin; for( int i = 1; i != 8; ++i ) { Vector ptTest = vOrigin; if( i & (1 << 0) ) ptTest += vOBBForward; if( i & (1 << 1) ) ptTest += vOBBRight; if( i & (1 << 2) ) ptTest += vOBBUp; if( ptTest.x < vMin.x ) vMin.x = ptTest.x; if( ptTest.y < vMin.y ) vMin.y = ptTest.y; if( ptTest.z < vMin.z ) vMin.z = ptTest.z; if( ptTest.x > vMax.x ) vMax.x = ptTest.x; if( ptTest.y > vMax.y ) vMax.y = ptTest.y; if( ptTest.z > vMax.z ) vMax.z = ptTest.z; } } float UTIL_IntersectRayWithPortal( const Ray_t &ray, const CProp_Portal *pPortal ) { if ( !pPortal || !pPortal->m_bActivated ) { return -1.0f; } Vector vForward; pPortal->GetVectors( &vForward, NULL, NULL ); // Discount rays not coming from the front of the portal float fDot = DotProduct( vForward, ray.m_Delta ); if ( fDot > 0.0f ) { return -1.0f; } Vector pvTri1[ 3 ], pvTri2[ 3 ]; UTIL_Portal_Triangles( pPortal, pvTri1, pvTri2 ); float fT; // Test triangle 1 fT = IntersectRayWithTriangle( ray, pvTri1[ 0 ], pvTri1[ 1 ], pvTri1[ 2 ], false ); // If there was an intersection return the T if ( fT >= 0.0f ) return fT; // Return the result of collision with the other face triangle return IntersectRayWithTriangle( ray, pvTri2[ 0 ], pvTri2[ 1 ], pvTri2[ 2 ], false ); } bool UTIL_IntersectRayWithPortalOBB( const CProp_Portal *pPortal, const Ray_t &ray, trace_t *pTrace ) { return IntersectRayWithOBB( ray, pPortal->GetAbsOrigin(), pPortal->GetAbsAngles(), CProp_Portal_Shared::vLocalMins, CProp_Portal_Shared::vLocalMaxs, 0.0f, pTrace ); } bool UTIL_IntersectRayWithPortalOBBAsAABB( const CProp_Portal *pPortal, const Ray_t &ray, trace_t *pTrace ) { Vector vAABBMins, vAABBMaxs; UTIL_Portal_AABB( pPortal, vAABBMins, vAABBMaxs ); return IntersectRayWithBox( ray, vAABBMins, vAABBMaxs, 0.0f, pTrace ); } bool UTIL_IsBoxIntersectingPortal( const Vector &vecBoxCenter, const Vector &vecBoxExtents, const Vector &ptPortalCenter, const QAngle &qPortalAngles, float flTolerance ) { Vector pvTri1[ 3 ], pvTri2[ 3 ]; UTIL_Portal_Triangles( ptPortalCenter, qPortalAngles, pvTri1, pvTri2 ); cplane_t plane; ComputeTrianglePlane( pvTri1[ 0 ], pvTri1[ 1 ], pvTri1[ 2 ], plane.normal, plane.dist ); plane.type = PLANE_ANYZ; plane.signbits = SignbitsForPlane( &plane ); if ( IsBoxIntersectingTriangle( vecBoxCenter, vecBoxExtents, pvTri1[ 0 ], pvTri1[ 1 ], pvTri1[ 2 ], plane, flTolerance ) ) { return true; } ComputeTrianglePlane( pvTri2[ 0 ], pvTri2[ 1 ], pvTri2[ 2 ], plane.normal, plane.dist ); plane.type = PLANE_ANYZ; plane.signbits = SignbitsForPlane( &plane ); return IsBoxIntersectingTriangle( vecBoxCenter, vecBoxExtents, pvTri2[ 0 ], pvTri2[ 1 ], pvTri2[ 2 ], plane, flTolerance ); } bool UTIL_IsBoxIntersectingPortal( const Vector &vecBoxCenter, const Vector &vecBoxExtents, const CProp_Portal *pPortal, float flTolerance ) { if( pPortal == NULL ) return false; return UTIL_IsBoxIntersectingPortal( vecBoxCenter, vecBoxExtents, pPortal->GetAbsOrigin(), pPortal->GetAbsAngles(), flTolerance ); } CProp_Portal *UTIL_IntersectEntityExtentsWithPortal( const CBaseEntity *pEntity ) { int iPortalCount = CProp_Portal_Shared::AllPortals.Count(); if( iPortalCount == 0 ) return NULL; Vector vMin, vMax; pEntity->CollisionProp()->WorldSpaceAABB( &vMin, &vMax ); Vector ptCenter = ( vMin + vMax ) * 0.5f; Vector vExtents = ( vMax - vMin ) * 0.5f; CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base(); for( int i = 0; i != iPortalCount; ++i ) { CProp_Portal *pTempPortal = pPortals[i]; if( pTempPortal->m_bActivated && (pTempPortal->m_hLinkedPortal.Get() != NULL) && UTIL_IsBoxIntersectingPortal( ptCenter, vExtents, pTempPortal ) ) { return pPortals[i]; } } return NULL; } void UTIL_Portal_NDebugOverlay( const Vector &ptPortalCenter, const QAngle &qPortalAngles, int r, int g, int b, int a, bool noDepthTest, float duration ) { #ifndef CLIENT_DLL Vector pvTri1[ 3 ], pvTri2[ 3 ]; UTIL_Portal_Triangles( ptPortalCenter, qPortalAngles, pvTri1, pvTri2 ); NDebugOverlay::Triangle( pvTri1[ 0 ], pvTri1[ 1 ], pvTri1[ 2 ], r, g, b, a, noDepthTest, duration ); NDebugOverlay::Triangle( pvTri2[ 0 ], pvTri2[ 1 ], pvTri2[ 2 ], r, g, b, a, noDepthTest, duration ); #endif //#ifndef CLIENT_DLL } void UTIL_Portal_NDebugOverlay( const CProp_Portal *pPortal, int r, int g, int b, int a, bool noDepthTest, float duration ) { #ifndef CLIENT_DLL UTIL_Portal_NDebugOverlay( pPortal->GetAbsOrigin(), pPortal->GetAbsAngles(), r, g, b, a, noDepthTest, duration ); #endif //#ifndef CLIENT_DLL } bool FindClosestPassableSpace( CBaseEntity *pEntity, const Vector &vIndecisivePush, unsigned int fMask ) //assumes the object is already in a mostly passable space { if ( sv_use_find_closest_passable_space.GetBool() == false ) return true; // Don't ever do this to entities with a move parent if ( pEntity->GetMoveParent() ) return true; #ifndef CLIENT_DLL ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "RUNNING FIND CLOSEST PASSABLE SPACE on %s..\n", pEntity->GetDebugName() ) ); #endif Vector ptExtents[8]; //ordering is going to be like 3 bits, where 0 is a min on the related axis, and 1 is a max on the same axis, axis order x y z float fExtentsValidation[8]; //some points are more valid than others, and this is our measure Vector vEntityMaxs;// = pEntity->WorldAlignMaxs(); Vector vEntityMins;// = pEntity->WorldAlignMins(); CCollisionProperty *pEntityCollision = pEntity->CollisionProp(); pEntityCollision->WorldSpaceAABB( &vEntityMins, &vEntityMaxs ); Vector ptEntityCenter = ((vEntityMins + vEntityMaxs) / 2.0f); vEntityMins -= ptEntityCenter; vEntityMaxs -= ptEntityCenter; Vector ptEntityOriginalCenter = ptEntityCenter; ptEntityCenter.z += 0.001f; //to satisfy m_IsSwept on first pass int iEntityCollisionGroup = pEntity->GetCollisionGroup(); trace_t traces[2]; Ray_t entRay; //entRay.Init( ptEntityCenter, ptEntityCenter, vEntityMins, vEntityMaxs ); entRay.m_Extents = vEntityMaxs; entRay.m_IsRay = false; entRay.m_IsSwept = true; entRay.m_StartOffset = vec3_origin; Vector vOriginalExtents = vEntityMaxs; Vector vGrowSize = vEntityMaxs / 101.0f; vEntityMaxs -= vGrowSize; vEntityMins += vGrowSize; Ray_t testRay; testRay.m_Extents = vGrowSize; testRay.m_IsRay = false; testRay.m_IsSwept = true; testRay.m_StartOffset = vec3_origin; unsigned int iFailCount; for( iFailCount = 0; iFailCount != 100; ++iFailCount ) { entRay.m_Start = ptEntityCenter; entRay.m_Delta = ptEntityOriginalCenter - ptEntityCenter; UTIL_TraceRay( entRay, fMask, pEntity, iEntityCollisionGroup, &traces[0] ); if( traces[0].startsolid == false ) { Vector vNewPos = traces[0].endpos + (pEntity->GetAbsOrigin() - ptEntityOriginalCenter); #ifdef CLIENT_DLL pEntity->SetAbsOrigin( vNewPos ); #else pEntity->Teleport( &vNewPos, NULL, NULL ); #endif return true; //current placement worked } bool bExtentInvalid[8]; for( int i = 0; i != 8; ++i ) { fExtentsValidation[i] = 0.0f; ptExtents[i] = ptEntityCenter; ptExtents[i].x += ((i & (1<<0)) ? vEntityMaxs.x : vEntityMins.x); ptExtents[i].y += ((i & (1<<1)) ? vEntityMaxs.y : vEntityMins.y); ptExtents[i].z += ((i & (1<<2)) ? vEntityMaxs.z : vEntityMins.z); bExtentInvalid[i] = enginetrace->PointOutsideWorld( ptExtents[i] ); } unsigned int counter, counter2; for( counter = 0; counter != 7; ++counter ) { for( counter2 = counter + 1; counter2 != 8; ++counter2 ) { testRay.m_Delta = ptExtents[counter2] - ptExtents[counter]; if( bExtentInvalid[counter] ) traces[0].startsolid = true; else { testRay.m_Start = ptExtents[counter]; UTIL_TraceRay( testRay, fMask, pEntity, iEntityCollisionGroup, &traces[0] ); } if( bExtentInvalid[counter2] ) traces[1].startsolid = true; else { testRay.m_Start = ptExtents[counter2]; testRay.m_Delta = -testRay.m_Delta; UTIL_TraceRay( testRay, fMask, pEntity, iEntityCollisionGroup, &traces[1] ); } float fDistance = testRay.m_Delta.Length(); for( int i = 0; i != 2; ++i ) { int iExtent = (i==0)?(counter):(counter2); if( traces[i].startsolid ) { fExtentsValidation[iExtent] -= 100.0f; } else { fExtentsValidation[iExtent] += traces[i].fraction * fDistance; } } } } Vector vNewOriginDirection( 0.0f, 0.0f, 0.0f ); float fTotalValidation = 0.0f; for( counter = 0; counter != 8; ++counter ) { if( fExtentsValidation[counter] > 0.0f ) { vNewOriginDirection += (ptExtents[counter] - ptEntityCenter) * fExtentsValidation[counter]; fTotalValidation += fExtentsValidation[counter]; } } if( fTotalValidation != 0.0f ) { ptEntityCenter += (vNewOriginDirection / fTotalValidation); //increase sizing testRay.m_Extents += vGrowSize; vEntityMaxs -= vGrowSize; vEntityMins = -vEntityMaxs; } else { //no point was valid, apply the indecisive vector ptEntityCenter += vIndecisivePush; //reset sizing testRay.m_Extents = vGrowSize; vEntityMaxs = vOriginalExtents; vEntityMins = -vEntityMaxs; } } // X360TBD: Hits in portal devtest AssertMsg( IsX360() || iFailCount != 100, "FindClosestPassableSpace() failure." ); return false; } bool UTIL_Portal_EntityIsInPortalHole( const CProp_Portal *pPortal, CBaseEntity *pEntity ) { CCollisionProperty *pCollisionProp = pEntity->CollisionProp(); Vector vMins = pCollisionProp->OBBMins(); Vector vMaxs = pCollisionProp->OBBMaxs(); Vector vForward, vUp, vRight; AngleVectors( pCollisionProp->GetCollisionAngles(), &vForward, &vRight, &vUp ); Vector ptOrigin = pEntity->GetAbsOrigin(); Vector ptOBBCenter = pEntity->GetAbsOrigin() + (vMins + vMaxs * 0.5f); Vector vExtents = (vMaxs - vMins) * 0.5f; vForward *= vExtents.x; vRight *= vExtents.y; vUp *= vExtents.z; Vector vPortalForward, vPortalRight, vPortalUp; pPortal->GetVectors( &vPortalForward, &vPortalRight, &vPortalUp ); Vector ptPortalCenter = pPortal->GetAbsOrigin(); return OBBHasFullyContainedIntersectionWithQuad( vForward, vRight, vUp, ptOBBCenter, vPortalForward, vPortalForward.Dot( ptPortalCenter ), ptPortalCenter, vPortalRight, PORTAL_HALF_WIDTH + 1.0f, vPortalUp, PORTAL_HALF_HEIGHT + 1.0f ); } #ifdef CLIENT_DLL void UTIL_TransformInterpolatedAngle( CInterpolatedVar< QAngle > &qInterped, matrix3x4_t matTransform, bool bSkipNewest ) { int iHead = qInterped.GetHead(); if( !qInterped.IsValidIndex( iHead ) ) return; #ifdef DBGFLAG_ASSERT float fHeadTime; qInterped.GetHistoryValue( iHead, fHeadTime ); #endif float fTime; QAngle *pCurrent; int iCurrent; if( bSkipNewest ) iCurrent = qInterped.GetNext( iHead ); else iCurrent = iHead; while( (pCurrent = qInterped.GetHistoryValue( iCurrent, fTime )) != NULL ) { Assert( (fTime <= fHeadTime) || (iCurrent == iHead) ); //asserting that head is always newest if( fTime < gpGlobals->curtime ) *pCurrent = TransformAnglesToWorldSpace( *pCurrent, matTransform ); iCurrent = qInterped.GetNext( iCurrent ); if( iCurrent == iHead ) break; } qInterped.Interpolate( gpGlobals->curtime ); } void UTIL_TransformInterpolatedPosition( CInterpolatedVar< Vector > &vInterped, VMatrix matTransform, bool bSkipNewest ) { int iHead = vInterped.GetHead(); if( !vInterped.IsValidIndex( iHead ) ) return; #ifdef DBGFLAG_ASSERT float fHeadTime; vInterped.GetHistoryValue( iHead, fHeadTime ); #endif float fTime; Vector *pCurrent; int iCurrent; if( bSkipNewest ) iCurrent = vInterped.GetNext( iHead ); else iCurrent = iHead; while( (pCurrent = vInterped.GetHistoryValue( iCurrent, fTime )) != NULL ) { Assert( (fTime <= fHeadTime) || (iCurrent == iHead) ); if( fTime < gpGlobals->curtime ) *pCurrent = matTransform * (*pCurrent); iCurrent = vInterped.GetNext( iCurrent ); if( iCurrent == iHead ) break; } vInterped.Interpolate( gpGlobals->curtime ); } #endif #ifndef CLIENT_DLL void CC_Debug_FixMyPosition( void ) { CBaseEntity *pPlayer = UTIL_GetCommandClient(); FindClosestPassableSpace( pPlayer, vec3_origin ); } static ConCommand debug_fixmyposition("debug_fixmyposition", CC_Debug_FixMyPosition, "Runs FindsClosestPassableSpace() on player.", FCVAR_CHEAT ); #endif