multi_phys

From Valve Developer Community
Jump to: navigation, search

This sample entity creates two IPhysicsObjects. The two objects are not constrained to each other, so can end up quite far apart.

Icon-Bug.pngBug: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);
		}
	}
}

Shared

#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;
}