multi_phys
This sample entity creates two IPhysicsObject
s. The two objects are not constrained to each other, so can end up quite far apart.
Bug:Pushing lighter objects will sometimes lead to the player taking 5 crush damage. This is probably due to problems with the calculation of QPhysics mass when there are two dissimilar models (by default a big wooden wardrobe and an empty plastic wheelie bin) in the entity. In practice, avoid simulating completely separate physics objects with one entity. [todo tested in?]
Server
#include "cbase.h"
class CMultiPhys : public CBaseAnimating
{
public:
DECLARE_CLASS(CMultiPhys, CBaseAnimating);
DECLARE_SERVERCLASS();
void Spawn();
void SpawnTestObjects();
void UpdateOnRemove();
int VPhysicsGetObjectList( IPhysicsObject **pList, int listMax );
void VPhysicsUpdate( IPhysicsObject *pPhysics );
void ComputeWorldSpaceSurroundingBox( Vector *pMins, Vector *pMaxs );
bool TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace );
void PhysicsImpact( CBaseEntity *other, trace_t &trace );
void Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity );
CUtlVector<IPhysicsObject*> m_physList;
// Have to choose a limit, 2 in this case. Remember to update the client if you raise this.
CNetworkArray(Vector,m_physPos,2);
CNetworkArray(QAngle,m_physAng,2);
};
IMPLEMENT_SERVERCLASS_ST(CMultiPhys, DTMultiPhys)
SendPropArray(SendPropVector(SENDINFO_ARRAY(m_physPos)),m_physPos),
SendPropArray(SendPropQAngles(SENDINFO_ARRAY(m_physAng)),m_physAng),
END_SEND_TABLE()
LINK_ENTITY_TO_CLASS( multi_phys, CMultiPhys );
#define MODEL "models/props_junk/TrashBin01a.mdl"
#define MODEL2 "models/props_c17/FurnitureDresser001a.mdl"
void CMultiPhys::Spawn()
{
BaseClass::Spawn();
PrecacheModel(MODEL);
PrecacheModel(MODEL2);
SetModel(MODEL);
SpawnTestObjects();
VPhysicsSetObject(m_physList[0]);
SetMoveType( MOVETYPE_VPHYSICS );
SetSolid( SOLID_VPHYSICS );
AddSolidFlags(FSOLID_CUSTOMBOXTEST|FSOLID_CUSTOMRAYTEST);
CollisionProp()->SetSurroundingBoundsType(USE_GAME_CODE);
Teleport(&GetAbsOrigin(),&GetAbsAngles(),&GetAbsVelocity());
}
int CMultiPhys::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax )
{
int count = 0;
for (int i=0; i < m_physList.Count() && i < listMax; i++)
{
pList[i] = m_physList[i];
count++;
}
return count;
}
void CMultiPhys::VPhysicsUpdate( IPhysicsObject *pPhysics )
{
for (int i=0;i < m_physList.Count(); i++)
{
Vector pos;
QAngle ang;
m_physList[i]->GetPosition(&pos,&ang);
m_physPos.Set(i,pos);
m_physAng.Set(i,ang);
if (i==0)
{
SetAbsOrigin(pos);
SetAbsAngles(ang);
}
}
PhysicsTouchTriggers();
SetSimulationTime( gpGlobals->curtime ); // otherwise various game systems think the entity is inactive
}
// Called whenever bounding boxes intersect; *not* a VPhysics call
void CMultiPhys::PhysicsImpact( CBaseEntity *other, trace_t &trace )
{
for ( int i = 0; i < m_physList.Count(); i++ )
{
trace_t curTrace;
Vector position;
QAngle angles;
m_physList[i]->GetPosition( &position, &angles );
physcollision->TraceBox( trace.startpos, trace.endpos, Vector(-1), Vector(1) ,m_physList[i]->GetCollide(), position, angles, &curTrace );
if ( curTrace.fraction < trace.fraction )
BaseClass::PhysicsImpact(other,trace);
}
}
void CMultiPhys::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity )
{
BaseClass::Teleport(newPosition,newAngles,newVelocity);
for ( int i=1; i < m_physList.Count(); i++ ) // skip the first object, that's handled by CBaseEntity
{
Vector curPos;
QAngle curAng;
m_physList[i]->GetPosition(&curPos,&curAng);
// Redirect to current value if there isn't a new one
if (!newPosition)
newPosition = &curPos;
if (!newAngles)
newAngles = &curAng;
// In a real entity there would be something better than this
Vector OffsetPos = *newPosition + Vector(20*i, 0, 0);
newPosition = &OffsetPos;
// Move the physics object
m_physList[i]->SetPosition( *newPosition, *newAngles, true );
// Network the new values
m_physPos.Set(i,*newPosition);
m_physAng.Set(i,*newAngles);
// This doesn't need to be networked
if (newVelocity)
{
AngularImpulse angImp;
m_physList[i]->SetVelocity(newVelocity,&angImp);
}
}
}
#include "cbase.h"
#ifdef GAME_DLL
#include "multi_phys.h"
#include "physics_saverestore.h"
#else
#include "c_multi_phys.h"
#define CMultiPhys C_MultiPhys
#endif
#define MODEL "models/props_junk/TrashBin01a.mdl"
#define MODEL2 "models/props_c17/FurnitureDresser001a.mdl"
void CMultiPhys::SpawnTestObjects()
{
m_physList.AddToTail( PhysModelCreate( this, modelinfo->GetModelIndex(MODEL), vec3_origin, vec3_angle ) );
m_physList[0]->SetGameData(this);
m_physList[0]->SetGameIndex(0);
m_physList[0]->SetGameFlags(FVPHYSICS_MULTIOBJECT_ENTITY);
m_physList.AddToTail( PhysModelCreate( this, modelinfo->GetModelIndex(MODEL2), vec3_origin, vec3_angle ) );
m_physList[1]->SetGameData(this);
m_physList[1]->SetGameIndex(1);
m_physList[1]->SetGameFlags(FVPHYSICS_MULTIOBJECT_ENTITY);
}
// Generates the bounding box
void CMultiPhys::ComputeWorldSpaceSurroundingBox( Vector *pMins, Vector *pMaxs )
{
Vector Mins(GetAbsOrigin()),Maxs(Mins);
for (int i=0;i < m_physList.Count(); i++)
{
Vector curMin,curMax;
Vector curPos;
QAngle curAng;
m_physList[i]->GetPosition(&curPos,&curAng);
physcollision->CollideGetAABB(&curMin,&curMax,m_physList[i]->GetCollide(),curPos,curAng);
for (int j=0;j<3;j++)
{
Mins[j] = min(Mins[j],curMin[j]);
Maxs[j] = max(Maxs[j],curMax[j]);
}
}
*pMins = Mins;
*pMaxs = Maxs;
}
// Should a VPhysics collision happen?
bool CMultiPhys::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace )
{
for ( int i = 0; i < m_physList.Count(); i++ )
{
Vector position;
QAngle angles;
m_physList[i]->GetPosition( &position, &angles );
trace_t curTrace;
physcollision->TraceBox( ray, m_physList[i]->GetCollide(), position, angles, &curTrace );
if ( curTrace.fraction < trace.fraction )
{
curTrace.surface.surfaceProps = m_physList[i]->GetMaterialIndex();
trace = curTrace;
}
}
return trace.fraction < 1;
}
void CMultiPhys::UpdateOnRemove()
{
for ( int i=1; i < m_physList.Count(); i++ )
{
#ifdef GAME_DLL
g_pPhysSaveRestoreManager->ForgetModel( m_physList[i] );
#endif
physenv->DestroyObject( m_physList[i] );
}
BaseClass::UpdateOnRemove();
}
Client
#include "cbase.h"
class C_MultiPhys : public C_BaseAnimating
{
public:
DECLARE_CLASS(C_MultiPhys, C_BaseAnimating);
DECLARE_CLIENTCLASS();
C_MultiPhys();
void Spawn();
void SpawnTestObjects();
void ClientThink();
void UpdateOnRemove();
void GetRenderBounds( Vector& theMins, Vector& theMaxs );
void ComputeWorldSpaceSurroundingBox( Vector *pMins, Vector *pMaxs );
bool TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace );
int InternalDrawModel( int flags );
CUtlVector<IPhysicsObject*> m_physList;
Vector m_physPos[2];
QAngle m_physAng[2];
CInterpolatedVarArray<Vector,2> m_iv_physPos;
CInterpolatedVarArray<QAngle,2> m_iv_physAng;
private:
bool SpawnedPhys;
};
IMPLEMENT_CLIENTCLASS_DT(C_MultiPhys,DTMultiPhys,CMultiPhys)
RecvPropArray(RecvPropVector(RECVINFO(m_physPos[0])), m_physPos),
RecvPropArray(RecvPropQAngles(RECVINFO(m_physAng[0])), m_physAng),
END_RECV_TABLE()
LINK_ENTITY_TO_CLASS( multi_phys, C_MultiPhys );
C_MultiPhys::C_MultiPhys() :
m_iv_physPos("C_MultiPhys::m_iv_physPos"),
m_iv_physAng("C_MultiPhys::m_iv_physAng")
{
AddVar( m_physPos, &m_iv_physPos, LATCH_SIMULATION_VAR );
AddVar( m_physAng, &m_iv_physAng, LATCH_SIMULATION_VAR );
}
void C_MultiPhys::Spawn()
{
BaseClass::Spawn();
SetNextClientThink(CLIENT_THINK_ALWAYS);
}
void C_MultiPhys::ClientThink()
{
if (!SpawnedPhys && physenv) // The client physics environment might not exist when Spawn() is called!
{
SpawnTestObjects();
VPhysicsSetObject(m_physList[0]);
SpawnedPhys = true;
}
// Pos/Ang are interpolated every frame, so keep refreshing them
for (int i=0;i < m_physList.Count(); i++)
m_physList[i]->SetPosition(m_physPos[i],m_physAng[i],false);
SetNextClientThink(CLIENT_THINK_ALWAYS);
}
// FIXME: a little too large, despite WSSB being a tight fit
void C_MultiPhys::GetRenderBounds( Vector& theMins, Vector& theMaxs )
{
ComputeWorldSpaceSurroundingBox(&theMins,&theMaxs);
theMins -= GetAbsOrigin();
theMaxs -= GetAbsOrigin();
IRotateAABB( EntityToWorldTransform(), theMins, theMaxs, theMins, theMaxs );
}
// Quick visualisation of where the other models are. Actually rendering them is a whole new can of worms...
int C_MultiPhys::InternalDrawModel( int flags )
{
int ret = BaseClass::InternalDrawModel( flags );
IMaterial *pWireframe = materials->FindMaterial("shadertest/wireframevertexcolor", TEXTURE_GROUP_OTHER);
matrix3x4_t matrix;
static color32 debugColor = {0,255,255,0};
for (int i=1;i < m_physList.Count(); i++) // skip first object
{
m_physList[i]->GetPositionMatrix(&matrix);
engine->DebugDrawPhysCollide( m_physList[i]->GetCollide(), pWireframe, matrix, debugColor );
}
return ret;
}