//========= Copyright Valve Corporation, All rights reserved. ============//
// traceperf.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "gametrace.h"
#include "fmtstr.h"
#include "appframework/appframework.h"
#include "filesystem.h"
#include "filesystem_init.h"
#include "tier1/tier1.h"
#include "tier2/tier2.h"
#include "tier3/tier3.h"

IPhysicsCollision *physcollision = NULL;

#define NUM_COLLISION_TESTS 2500

void ReadPHYFile(const char *name, vcollide_t &collide )
{
	FileHandle_t fp = g_pFullFileSystem->Open(name, "rb");
	if (!fp)
		Error ("Couldn't open %s", name);

	phyheader_t header;

	g_pFullFileSystem->Read( &header, sizeof(header), fp );
	if ( header.size != sizeof(header) || header.solidCount <= 0 )
		return;

	int fileSize = g_pFullFileSystem->Size(fp);

	char *buf = (char *)_alloca( fileSize );
	g_pFullFileSystem->Read( buf, fileSize, fp );
	g_pFullFileSystem->Close( fp );

	physcollision->VCollideLoad( &collide, header.solidCount, (const char *)buf, fileSize );
}


struct testlist_t
{
	Vector start;
	Vector end;
	Vector normal;
	bool hit;
};

struct benchresults_t
{
	int		collisionTests;
	int		collisionHits;
	float	totalTime;
	float	rayTime;
	float	boxTime;
};

testlist_t g_Traces[NUM_COLLISION_TESTS];
void Benchmark_PHY( const CPhysCollide *pCollide, benchresults_t *pOut )
{
	int i;
	Vector start = vec3_origin;
	static Vector *targets = NULL;
	static bool first = true;
	static float test[2] = {1,1};
	if ( first )
	{
		float radius = 0;
		float theta = 0;
		float phi = 0;
		for ( int i = 0; i < NUM_COLLISION_TESTS; i++ )
		{
			radius += NUM_COLLISION_TESTS * 123.123f;
			radius = fabs(fmod(radius, 128));
			theta += NUM_COLLISION_TESTS * 0.76f;
			theta = fabs(fmod(theta, DEG2RAD(360)));
			phi += NUM_COLLISION_TESTS * 0.16666666f;
			phi = fabs(fmod(phi, DEG2RAD(180)));

			float st, ct, sp, cp;
			SinCos( theta, &st, &ct );
			SinCos( phi, &sp, &cp );
			st = sin(theta);
			ct = cos(theta);
			sp = sin(phi);
			cp = cos(phi);

			g_Traces[i].start.x = radius * ct * sp;
			g_Traces[i].start.y = radius * st * sp;
			g_Traces[i].start.z = radius * cp;
		}
		first = false;
	}

	float duration = 0;
	Vector size[2];
	size[0].Init(0,0,0);
	size[1].Init(16,16,16);

#if VPROF_LEVEL > 0 
	g_VProfCurrentProfile.Reset();
	g_VProfCurrentProfile.ResetPeaks();
	g_VProfCurrentProfile.Start();
#endif

#if TEST_BBOX
	Vector mins, maxs;
	physcollision->CollideGetAABB( &mins, &maxs, pCollide, Vector(-500, 200, -100), vec3_angle );
	Vector extents = maxs - mins;
	Vector center = 0.5f * (maxs+mins);
	Msg("bbox: %.2f,%.2f, %.2f @ %.2f, %.2f, %.2f\n", extents.x, extents.y, extents.z, center.x, center.y, center.z );
#endif
	unsigned int hitCount = 0;
	double startTime = Plat_FloatTime();
	trace_t tr;
	for ( i = 0; i < NUM_COLLISION_TESTS; i++ )
	{
		physcollision->TraceBox( g_Traces[i].start, start, -size[0], size[0], pCollide, vec3_origin, vec3_angle, &tr );
		if ( tr.DidHit() )
		{
			g_Traces[i].end = tr.endpos;
			g_Traces[i].normal = tr.plane.normal;
			g_Traces[i].hit = true;
			hitCount++;
		}
		else
		{
			g_Traces[i].hit = false;
		}
	}
	for ( i = 0; i < NUM_COLLISION_TESTS; i++ )
	{
		physcollision->TraceBox( g_Traces[i].start, start, -size[1], size[1], pCollide, vec3_origin, vec3_angle, &tr );
	}
	duration = Plat_FloatTime() - startTime;

#if VPROF_LEVEL > 0 
	g_VProfCurrentProfile.MarkFrame();
	g_VProfCurrentProfile.Stop();
	g_VProfCurrentProfile.Reset();
	g_VProfCurrentProfile.ResetPeaks();
	g_VProfCurrentProfile.Start();
#endif
	hitCount = 0;
	startTime = Plat_FloatTime();
	for ( i = 0; i < NUM_COLLISION_TESTS; i++ )
	{
		physcollision->TraceBox( g_Traces[i].start, start, -size[0], size[0], pCollide, vec3_origin, vec3_angle, &tr );
		if ( tr.DidHit() )
		{
			g_Traces[i].end = tr.endpos;
			g_Traces[i].normal = tr.plane.normal;
			g_Traces[i].hit = true;
			hitCount++;
		}
		else
		{
			g_Traces[i].hit = false;
		}
#if VPROF_LEVEL > 0 
		g_VProfCurrentProfile.MarkFrame();
#endif
	}
	double midTime = Plat_FloatTime();
	for ( i = 0; i < NUM_COLLISION_TESTS; i++ )
	{
		physcollision->TraceBox( g_Traces[i].start, start, -size[1], size[1], pCollide, vec3_origin, vec3_angle, &tr );
#if VPROF_LEVEL > 0 
		g_VProfCurrentProfile.MarkFrame();
#endif
	}
	double endTime = Plat_FloatTime();
	duration = endTime - startTime;
	pOut->collisionTests = NUM_COLLISION_TESTS;
	pOut->collisionHits = hitCount;
	pOut->totalTime = duration * 1000.0f;
	pOut->rayTime = (midTime - startTime) * 1000.0f;
	pOut->boxTime = (endTime - midTime)*1000.0f;

#if VPROF_LEVEL > 0 
	g_VProfCurrentProfile.Stop();
	g_VProfCurrentProfile.OutputReport( VPRT_FULL & ~VPRT_HIERARCHY, NULL );
#endif
}

//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose: 
//
// $NoKeywords: $
//
//===========================================================================//


//-----------------------------------------------------------------------------
// The application object
//-----------------------------------------------------------------------------
class CBenchmarkApp : public CDefaultAppSystemGroup< CSteamAppSystemGroup >
{
	typedef CDefaultAppSystemGroup< CSteamAppSystemGroup > BaseClass;

public:
	// Methods of IApplication
	virtual bool Create();
	virtual bool PreInit( );
	virtual int Main();
	virtual void PostShutdown();
	bool SetupSearchPaths();

private:
	bool ParseArguments();
};

DEFINE_CONSOLE_STEAM_APPLICATION_OBJECT( CBenchmarkApp );


//-----------------------------------------------------------------------------
// The application object
//-----------------------------------------------------------------------------
bool CBenchmarkApp::Create()
{
	MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false );

	// Add in the cvar factory
	//AppModule_t cvarModule = LoadModule( VStdLib_GetICVarFactory() );
	//AddSystem( cvarModule, VENGINE_CVAR_INTERFACE_VERSION );

	AppSystemInfo_t appSystems[] = 
	{
		{ "vphysics.dll",			VPHYSICS_INTERFACE_VERSION },
		{ "", "" }	// Required to terminate the list
	};

	bool bRet = AddSystems( appSystems );
	if ( bRet )
	{
		physcollision = (IPhysicsCollision*)FindSystem( VPHYSICS_COLLISION_INTERFACE_VERSION );
		if ( !physcollision )
			return false;
	}
	return bRet;
}

bool CBenchmarkApp::SetupSearchPaths()
{
	CFSSteamSetupInfo steamInfo;
	steamInfo.m_pDirectoryName = NULL;
	steamInfo.m_bOnlyUseDirectoryName = false;
	steamInfo.m_bToolsMode = true;
	steamInfo.m_bSetSteamDLLPath = true;
	steamInfo.m_bSteam = g_pFullFileSystem->IsSteam();

	if ( FileSystem_SetupSteamEnvironment( steamInfo ) != FS_OK )
		return false;

	CFSMountContentInfo fsInfo;
	fsInfo.m_pFileSystem = g_pFullFileSystem;
	fsInfo.m_bToolsMode = true;
	fsInfo.m_pDirectoryName = steamInfo.m_GameInfoPath;

	if ( FileSystem_MountContent( fsInfo ) != FS_OK )
		return false;

	// Finally, load the search paths for the "GAME" path.
	CFSSearchPathsInit searchPathsInit;
	searchPathsInit.m_pDirectoryName = steamInfo.m_GameInfoPath;
	searchPathsInit.m_pFileSystem = g_pFullFileSystem;
	if ( FileSystem_LoadSearchPaths( searchPathsInit ) != FS_OK )
		return false;

	g_pFullFileSystem->AddSearchPath( steamInfo.m_GameInfoPath, "SKIN", PATH_ADD_TO_HEAD );

	FileSystem_AddSearchPath_Platform( g_pFullFileSystem, steamInfo.m_GameInfoPath );

	return true;
}

bool CBenchmarkApp::PreInit( )
{
	CreateInterfaceFn factory = GetFactory();
	ConnectTier1Libraries( &factory, 1 );
	ConnectTier2Libraries( &factory, 1 );
	ConnectTier3Libraries( &factory, 1 );

	if ( !g_pFullFileSystem || !physcollision )
	{
		Warning( "benchmark is missing a required interface!\n" );
		return false;
	}

	return SetupSearchPaths();//( NULL, false, true );
}


void CBenchmarkApp::PostShutdown()
{
	DisconnectTier3Libraries();
	DisconnectTier2Libraries();
	DisconnectTier1Libraries();
}

struct baseline_t
{
	float	total;
	float	ray;
	float	box;
};

// current baseline measured on Core2DuoE6600
baseline_t g_Baselines[] =
{
	{ 40.56f,	10.64f,		29.92f},		// bench01a.phy
	{ 38.13f,	10.76f,		27.37f },		// bicycle01a.phy
	{ 25.46f,	8.34f,		17.13f },		// furnituretable001a.phy
	{ 12.65f,	6.02f,		6.62f },		// gravestone003a.phy
	{ 40.58f,	16.49f,		24.10f },		// combineinnerwall001a.phy
};

const float g_TotalBaseline = 157.38f;

/*
Benchmark models\props_c17\bench01a.phy!

33.90 ms [1.20 X] 2500/2500 hits
8.95 ms rays    [1.19 X]        24.95 ms boxes [1.20 X]
Benchmark models\props_junk\bicycle01a.phy!

30.55 ms [1.25 X] 803/2500 hits
8.96 ms rays    [1.20 X]        21.59 ms boxes [1.27 X]
Benchmark models\props_c17\furnituretable001a.phy!

20.61 ms [1.24 X] 755/2500 hits
6.88 ms rays    [1.21 X]        13.73 ms boxes [1.25 X]
Benchmark models\props_c17\gravestone003a.phy!

9.13 ms [1.39 X] 2500/2500 hits
4.34 ms rays    [1.39 X]        4.79 ms boxes [1.38 X]
Benchmark models\props_combine\combineinnerwall001a.phy!

33.04 ms [1.23 X] 985/2500 hits
13.37 ms rays   [1.23 X]        19.67 ms boxes [1.23 X]

127.22s total   [1.24 X]!
*/

#define IMPROVEMENT_FACTOR(x,baseline) (baseline/(x))
#define IMPROVEMENT_PERCENT(x,baseline) (((baseline-(x)) / baseline) * 100.0f)
#include <windows.h>
//-----------------------------------------------------------------------------
// The application object
//-----------------------------------------------------------------------------
int CBenchmarkApp::Main()
{
	const char *pFileNames[] = 
	{
		"models\\props_c17\\bench01a.phy",
		"models\\props_junk\\bicycle01a.phy",
		"models\\props_c17\\furnituretable001a.phy",
		"models\\props_c17\\gravestone003a.phy",
		"models\\props_combine\\combineinnerwall001a.phy",
	};
	vcollide_t testModels[ARRAYSIZE(pFileNames)];
	for ( int i = 0; i < ARRAYSIZE(pFileNames); i++ )
	{
		ReadPHYFile( pFileNames[i], testModels[i] );
	}
	SetPriorityClass( GetCurrentProcess(), REALTIME_PRIORITY_CLASS );
	SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_HIGHEST );
	float totalTime = 0.0f;
	int loopCount = ARRAYSIZE(pFileNames);
#if VPROF_LEVEL > 0
//	loopCount = 3;
#endif
	for ( int i = 0; i < loopCount; i++ )
	{
		if ( testModels[i].solidCount < 1 )
		{
			Msg("Failed to load %s, skipping test!\n", pFileNames[i] );
			continue;
		}
		Msg("Benchmark %s!\n\n", pFileNames[i] );

		benchresults_t results;
		memset( &results, 0, sizeof(results));
		int numRepeats = 3;
#if VPROF_LEVEL > 0
		numRepeats = 1;
#endif
		for ( int j = 0; j < numRepeats; j++ )
		{
			Benchmark_PHY( testModels[i].solids[0], &results );
		}
		Msg("%.2f ms [%.2f X] %d/%d hits\n", results.totalTime, IMPROVEMENT_FACTOR(results.totalTime, g_Baselines[i].total), results.collisionHits, results.collisionTests);
		Msg("%.2f ms rays \t[%.2f X] \t%.2f ms boxes [%.2f X]\n", 
			results.rayTime, IMPROVEMENT_FACTOR(results.rayTime, g_Baselines[i].ray), 
			results.boxTime, IMPROVEMENT_FACTOR(results.boxTime, g_Baselines[i].box));
		totalTime += results.totalTime;
	}
	SetPriorityClass( GetCurrentProcess(), NORMAL_PRIORITY_CLASS );

	Msg("\n%.2fs total \t[%.2f X]!\n", totalTime, IMPROVEMENT_FACTOR(totalTime, g_TotalBaseline) );
	return 0;
}