Treads of Glory

Treads of Glory is a shared-screen multiplayer tank game built using C++ and OpenGL. Players compete in arena-style combat using bouncing bullets and mines.

I worked on this project as a programmer. My responsibilities included the game physics and collision.

The game was developed by a team of three programmers over three weeks. Artwork courtesy of Alfonso Callejas

View Code

#pragma once

#include "EVector2.h"
#include "EUtil.h"

#define NUM_COLLISION_SHAPES 2

namespace Equinox
{

	enum CollisionShape
	{
		SQUARE, CIRCLE
	};

	class CollisionObject
	{
	public:
		CollisionObject( const EVector2& position) : position( position ) { }

		virtual CollisionShape getShape() = 0;
		void setPosition( const EVector2& position ) { this->position = position; }

		const EVector2& getPosition() { return position; }
		virtual const EVector2 getMaxBound() = 0;
	protected:
		EVector2 position;
	};

	class CollisionCircle : public CollisionObject
	{
	public:
		CollisionCircle( const EVector2& position, float radius ) :
			CollisionObject( position ), 
			radius( radius ) { }

		float getRadius() const { return radius; };
		void setRadius( float radius ) { this->radius = radius; }

		CollisionShape getShape() { return CIRCLE; }

		const EVector2 getMaxBound() { return EVector2( radius, radius); }

	private:
		float radius;
	};


	class CollisionSquare : public CollisionObject
	{
	public:
		CollisionSquare( const EVector2& position, float halfWidth) :
			CollisionObject( position ), 
			halfWidth( halfWidth ) { }

		float getHalfWidth() const { return halfWidth; }

		CollisionShape getShape() { return SQUARE; }

		const EVector2 getMaxBound() { return EVector2( halfWidth, halfWidth); }

	private:
		float halfWidth;
	};

	struct CollisionInfoBundle
	{
		float depth;
		EVector2 normal;
	};

	class CollisionManager
	{
	public:
		CollisionManager();

		// Tests whether (first) and (second) collide. If they do, depth and normal
		// will be saved in (collisionInfo) and returns true. Otherwise returns false.
		bool DoesCollide( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo );

	private:
		typedef bool (*CollisionFunc)( CollisionObject*, CollisionObject*, CollisionInfoBundle& );
		CollisionFunc m_TestFunc[ NUM_COLLISION_SHAPES ][ NUM_COLLISION_SHAPES ];

		static bool UndefinedCollision( CollisionObject*, CollisionObject *, CollisionInfoBundle& collisionInfo );

		static bool DoesCollideSquareSquare( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo  );
		static bool DoesCollideSquareCircle( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo  );
		static bool DoesCollideCircleCircle( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo  );
	};


}
#include "CollisionManager.h"

#include <cassert>
#include <cmath>

namespace Equinox
{


	//================================================================================
	//================================================================================
	CollisionManager::CollisionManager()
	{
		for ( unsigned int i = 0; i < NUM_COLLISION_SHAPES; ++i )
			for ( unsigned int j = 0; j < NUM_COLLISION_SHAPES; ++j )
				m_TestFunc[i][j] = UndefinedCollision;

		m_TestFunc[0][0] = DoesCollideSquareSquare;
		m_TestFunc[0][1] = DoesCollideSquareCircle;
		m_TestFunc[1][1] = DoesCollideCircleCircle;
	}

	//================================================================================
	//================================================================================
	bool CollisionManager::DoesCollide( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo  )
	{
		if ( first == nullptr || second == nullptr ) return false;

		float reverser = 1.0f;

		if ( second->getShape() < first->getShape() )
		{
			swap( first, second );
			reverser = -1.0f;
		}

		bool result = m_TestFunc[first->getShape()][second->getShape()]( first, second, collisionInfo );
		collisionInfo.normal *= reverser;

		return result;
	}


	//================================================================================
	//================================================================================
	bool CollisionManager::UndefinedCollision( CollisionObject*, CollisionObject *, CollisionInfoBundle& collisionInfo )
	{
		assert( false );

		return false;
	}

	//================================================================================
	//================================================================================
	bool CollisionManager::DoesCollideSquareSquare( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo  )
	{
		CollisionSquare* squareA = static_cast< CollisionSquare* >( first );
		CollisionSquare* squareB = static_cast< CollisionSquare* >( second );

		return false;
	}

	//================================================================================
	//================================================================================
	bool CollisionManager::DoesCollideSquareCircle( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo )
	{
		CollisionSquare* square = static_cast< CollisionSquare* >( first );
		CollisionCircle* circle = static_cast< CollisionCircle* >( second );

		float dist[2];

		for ( int i = 0; i < 2; ++i )
			dist[i] = abs( square->getPosition().GetAxis( i ) - circle->getPosition().GetAxis( i ) );

		unsigned int axis = ( dist[0] > dist[1] ) ? 0 : 1;
		unsigned int auxAxis = !axis;

		if ( ( dist[axis] < square->getHalfWidth() + circle->getRadius() ) && ( dist[ auxAxis ] < square->getHalfWidth() ) )
		{
			// Collision occurred with "line" portion of square
			collisionInfo.normal.SetAxis( axis, -1.0f );
			collisionInfo.normal.SetAxis( auxAxis, 0.0f );

			if ( square->getPosition().GetAxis(	axis ) < circle->getPosition().GetAxis( axis ) )
				collisionInfo.normal *= -1;

			collisionInfo.depth = square->getHalfWidth() + circle->getRadius() - dist[ axis ];

			return true;
		}

		float offset = square->getPosition().GetAxis( axis ) > circle->getPosition().GetAxis( axis ) ? -square->getHalfWidth() : square->getHalfWidth();
		float auxOffset = square->getPosition().GetAxis( auxAxis ) > circle->getPosition().GetAxis( auxAxis ) ? -square->getHalfWidth() : square->getHalfWidth();

		float d1 = ( square->getPosition().GetAxis( axis ) + offset ) - circle->getPosition().GetAxis( axis );
		float d2 = ( square->getPosition().GetAxis( auxAxis ) + auxOffset ) - circle->getPosition().GetAxis( auxAxis );

		if ( d1 * d1 + d2 * d2 < circle->getRadius() * circle->getRadius() )
		{
			// Collided with point
			collisionInfo.normal.SetAxis( axis, d1 );
			collisionInfo.normal.SetAxis( auxAxis, d2 );
			collisionInfo.normal.Normalize();

			collisionInfo.depth = sqrt( d1 * d1 + d2 * d2) - circle->getRadius();

			return true;
		}

		return false;
	}

	//================================================================================
	//================================================================================
	bool CollisionManager::DoesCollideCircleCircle( CollisionObject* first, CollisionObject* second, CollisionInfoBundle& collisionInfo  )
	{
		CollisionCircle* circleA = static_cast< CollisionCircle* >( first );
		CollisionCircle* circleB = static_cast< CollisionCircle* >( second );

		const EVector2& posA = circleA->getPosition();
		const EVector2& posB = circleB->getPosition();

		float distX = posB.X - posA.X;
		float distY = posB.Y - posA.Y;
		float distSq = distX * distX + distY * distY;

		float combinedRadius = circleA->getRadius() + circleB->getRadius();

		if ( distSq < combinedRadius * combinedRadius )
		{
			collisionInfo.depth = combinedRadius - sqrt(distSq);

			collisionInfo.normal.X = distX;
			collisionInfo.normal.Y = distY;
			if ( distX == 0 && distY == 0 )
			{
				collisionInfo.normal.Y = -1;
			}
			else
			{
				collisionInfo.normal.Normalize();
			}

			return true;
		}
		else
		{
			return false;
		}
	}


}