multi_phys

From Valve Developer Community
Jump to navigation Jump to 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;
}