//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: Local fast collision system for particles // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "particle_collision.h" #include "engine/ivdebugoverlay.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define __DEBUG_PARTICLE_COLLISION_RETEST 1 #define __DEBUG_PARTICLE_COLLISION_OVERLAY 0 #define __DEBUG_PARTICLE_COLLISION_OVERLAY_LIFETIME 0.1f #define NUM_DISCREET_STEPS 8.0f #define NUM_SIMULATION_SECONDS 2.0f #define COLLISION_EPSILON 0.01f //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseSimpleCollision::CBaseSimpleCollision( void ) { ClearActivePlanes(); } //----------------------------------------------------------------------------- // Purpose: // Input : &origin - // radius - //----------------------------------------------------------------------------- void CBaseSimpleCollision::Setup( const Vector &origin, float speed, float gravity ) { TestForPlane( origin, Vector( 1, 0, 0 ), speed, gravity ); TestForPlane( origin, Vector( -1, 0, 0 ), speed, gravity ); TestForPlane( origin, Vector( 0, 1, 0 ), speed, gravity ); TestForPlane( origin, Vector( 0, -1, 0 ), speed, gravity ); TestForPlane( origin, Vector( 0, 0, 1 ), speed, gravity ); TestForPlane( origin, Vector( 0, 0, -1 ), speed, gravity ); } //----------------------------------------------------------------------------- // Purpose: Trace line for super-simplified traces // Input : &start - start position // &end - end position // *pTrace - trace structure to fill // coarse - tests again with a real trace unless coarse is set //----------------------------------------------------------------------------- void CBaseSimpleCollision::TraceLine( const Vector &start, const Vector &end, trace_t *pTrace, bool coarse ) { //Iterate over all active planes for ( int i = 0; i < m_nActivePlanes; i++ ) { //Must be a valid plane if ( m_collisionPlanes[i].m_Dist == -1.0f ) continue; //Get our information about the relation to this plane float dot1 = m_collisionPlanes[i].DistTo(start); float dot2 = m_collisionPlanes[i].DistTo(end); //Don't consider particles on the backside of planes if ( dot1 < -COLLISION_EPSILON ) continue; //Must be crossing the plane's boundary if ( ( dot1 > COLLISION_EPSILON ) == ( dot2 > COLLISION_EPSILON ) ) continue; //Find the intersection point float t = dot1 / (dot1 - dot2); Vector vIntersection = start + (end - start) * t; //Fake the collision info pTrace->endpos = vIntersection; pTrace->fraction = t - COLLISION_EPSILON; pTrace->plane.normal = m_collisionPlanes[i].m_Normal; pTrace->plane.dist = m_collisionPlanes[i].m_Dist; //If we need an exact trace, test again on a successful hit if ( ( coarse == false ) && ( pTrace->fraction < 1.0f ) ) { UTIL_TraceLine( start, end, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, pTrace ); } #if __DEBUG_PARTICLE_COLLISION_OVERLAY debugoverlay->AddBoxOverlay( vIntersection, Vector(-1,-1,-1), Vector(1,1,1), QAngle(0,0,0), 0, 255, 0, 16, __DEBUG_PARTICLE_COLLISION_OVERLAY_LIFETIME ); #endif //__DEBUG_PARTICLE_COLLISION_OVERLAY //Done return; } //Fell through, so clear all the fields pTrace->plane.normal[0] = 0.0f; pTrace->plane.normal[1] = 0.0f; pTrace->plane.normal[2] = 0.0f; pTrace->plane.dist = 0.0f; pTrace->fraction = 1.0f; pTrace->allsolid = false; pTrace->startsolid = false; pTrace->m_pEnt = NULL; } //----------------------------------------------------------------------------- // Purpose: Tests the planes against all others for validity // Input : *plane - plane to test //----------------------------------------------------------------------------- void CBaseSimpleCollision::ConsiderPlane( cplane_t *plane ) { //Test against all other active planes for ( int i = 0; i < m_nActivePlanes; i++ ) { if ( m_collisionPlanes[i].m_Dist != -1.0f ) { //Test for coplanar if ( ( m_collisionPlanes[i].m_Normal == plane->normal ) && ( m_collisionPlanes[i].m_Dist == plane->dist ) ) return; } } //Don't overrun if ( m_nActivePlanes >= MAX_COLLISION_PLANES ) return; //Take it m_collisionPlanes[m_nActivePlanes].m_Dist = plane->dist; m_collisionPlanes[m_nActivePlanes].m_Normal = plane->normal; m_nActivePlanes++; } //----------------------------------------------------------------------------- // Purpose: Runs a simulation of the average particle's movement, looking for collisions along the way // Input : &start - start of the simulation // &dir - direction of travel // speed - speed of the particle // gravity - gravity being used //----------------------------------------------------------------------------- void CBaseSimpleCollision::TestForPlane( const Vector &start, const Vector &dir, float speed, float gravity ) { trace_t tr; Vector testStart, testEnd; testStart = start; //Setup our step increments float dStepTime = (NUM_SIMULATION_SECONDS/NUM_DISCREET_STEPS); Vector vStepIncr = dir * ( speed * dStepTime ); float flGravIncr = gravity*dStepTime; //Simulate collsions in discreet steps for ( int i = 1; i <= NUM_DISCREET_STEPS; i++ ) { testEnd = testStart + vStepIncr; testEnd[2] -= flGravIncr * (0.5f*(dStepTime*i)*(dStepTime*i) ); //Trace the line UTIL_TraceLine( testStart, testEnd, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); //See if we found one if ( tr.fraction != 1.0f ) { #if __DEBUG_PARTICLE_COLLISION_OVERLAY debugoverlay->AddLineOverlay( testStart, tr.endpos, 255, 0, 0, true, __DEBUG_PARTICLE_COLLISION_OVERLAY_LIFETIME ); QAngle angles; VectorAngles( tr.plane.normal,angles ); angles[PITCH] += 90; debugoverlay->AddBoxOverlay( tr.endpos, Vector(-64,-64,0), Vector(64,64,0), angles, 255, 0, 0, 16, __DEBUG_PARTICLE_COLLISION_OVERLAY_LIFETIME ); #endif //__DEBUG_PARTICLE_COLLISION_OVERLAY //Test the plane against a set of criteria ConsiderPlane( &tr.plane ); return; } //We missed #if __DEBUG_PARTICLE_COLLISION_OVERLAY debugoverlay->AddLineOverlay( testStart, tr.endpos, 0, 128.0f+(128.0f*((float)i/(float)NUM_DISCREET_STEPS)), 0, true, __DEBUG_PARTICLE_COLLISION_OVERLAY_LIFETIME ); #endif //__DEBUG_PARTICLE_COLLISION_OVERLAY //Save that position for the next round testStart = testEnd; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseSimpleCollision::ClearActivePlanes( void ) { for ( int i = 0; i < MAX_COLLISION_PLANES; i++ ) { m_collisionPlanes[i].m_Dist = -1.0f; m_collisionPlanes[i].m_Normal.Init(); } m_nActivePlanes = 0; } // // CParticleCollision // //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CParticleCollision::CParticleCollision( void ) { m_flGravity = 800.0f; m_flCollisionDampen = 0.5f; m_flAngularCollisionDampen = 0.25f; ClearActivePlanes(); } //----------------------------------------------------------------------------- // Purpose: Test for surrounding collision surfaces for quick collision testing for the particle system // Input : &origin - starting position // *dir - direction of movement (if NULL, will do a point emission test in four directions) // angularSpread - looseness of the spread // minSpeed - minimum speed // maxSpeed - maximum speed // gravity - particle gravity for the sytem // dampen - dampening amount on collisions //----------------------------------------------------------------------------- void CParticleCollision::Setup( const Vector &origin, const Vector *dir, float angularSpread, float minSpeed, float maxSpeed, float gravity, float dampen ) { //Take the information for this simulation m_flGravity = gravity; m_flCollisionDampen = dampen; m_nActivePlanes = 0; //We take a rough estimation of the spray float speedAvg = (minSpeed+maxSpeed)*0.5f; //Point or directed? if ( dir == NULL ) { //Test all around TestForPlane( origin, Vector( 1, 0, 0 ), speedAvg, gravity ); TestForPlane( origin, Vector( -1, 0, 0 ), speedAvg, gravity ); TestForPlane( origin, Vector( 0, 1, 0 ), speedAvg, gravity ); TestForPlane( origin, Vector( 0, -1, 0 ), speedAvg, gravity ); TestForPlane( origin, Vector( 0, 0, 1 ), speedAvg, gravity ); TestForPlane( origin, Vector( 0, 0, -1 ), speedAvg, gravity ); } else { Vector vSkewDir, vRight; QAngle vAngles; //FIXME: Quicker conversion? //FIXME: We need to factor in the angular spread instead VectorAngles( *dir, vAngles ); AngleVectors( vAngles, NULL, &vRight, NULL ); //Test straight TestForPlane( origin, *dir, speedAvg, gravity ); vSkewDir = vRight; //Test right TestForPlane( origin, vSkewDir, speedAvg, gravity ); vSkewDir *= -1.0f; //Test left TestForPlane( origin, vSkewDir, speedAvg, gravity ); #if __DEBUG_PARTICLE_COLLISION_OVERLAY DevMsg( 1, "CParticleCollision: Found %d active plane(s)\n", m_nActivePlanes ); #endif //__DEBUG_PARTICLE_COLLISION_OVERLAY } } //----------------------------------------------------------------------------- // Purpose: Simulate movement, with collision // Input : &origin - position of the particle // &velocity - velocity of the particle // &rollDelta - roll delta of the particle // timeDelta - time step //----------------------------------------------------------------------------- bool CParticleCollision::MoveParticle( Vector &origin, Vector &velocity, float *rollDelta, float timeDelta, trace_t *pTrace ) { //Don't bother with non-moving particles if ( velocity == vec3_origin ) return false; //Factor in gravity velocity[2] -= m_flGravity * timeDelta; //Move Vector testPosition = ( origin + ( velocity * timeDelta ) ); //Only collide if we have active planes if ( m_nActivePlanes > 0 ) { //Collide TraceLine( origin, testPosition, pTrace ); //See if we hit something if ( pTrace->fraction != 1.0f ) { #if __DEBUG_PARTICLE_COLLISION_RETEST //Retest the collision with a true trace line to avoid errant collisions UTIL_TraceLine( origin, testPosition, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, pTrace ); #endif //__DEBUG_RETEST_COLLISION //Did we hit anything? if ( pTrace->fraction != 1.0f ) { //See if we've settled if ( ( pTrace->plane.normal[2] >= 0.5f ) && ( fabs( velocity[2] ) <= 48.0f ) ) { //Leave the particle at the collision point origin += velocity * ( (pTrace->fraction-COLLISION_EPSILON) * timeDelta ); //Stop the particle velocity = vec3_origin; if ( rollDelta != NULL ) { *rollDelta = 0.0f; } return false; } else { //Move the particle to the collision point origin += velocity * ( (pTrace->fraction-COLLISION_EPSILON) * timeDelta ); //Find the reflection vector float proj = velocity.Dot( pTrace->plane.normal ); velocity += pTrace->plane.normal * (-proj*2.0f); //Apply dampening velocity *= random->RandomFloat( (m_flCollisionDampen-0.1f), (m_flCollisionDampen+0.1f) ); //Dampen the roll of the particles if ( rollDelta != NULL ) { (*rollDelta) *= -0.25f; } return true; } } else { #if __DEBUG_PARTICLE_COLLISION_OVERLAY //Display a false hit debugoverlay->AddBoxOverlay( pTrace->endpos, Vector(-1,-1,-1), Vector(1,1,1), QAngle(0,0,0), 255, 0, 0, 16, __DEBUG_PARTICLE_COLLISION_OVERLAY_LIFETIME ); #endif //__DEBUG_PARTICLE_COLLISION_OVERLAY } } } //Simple move, no collision origin = testPosition; return false; }