<commit_before>#include "Tetris.h"

#include <iostream>
#include "TMTheme.h"
#include "TMScreen.h"

const char* VERSION = "ver 2.1";

bool TheTetrisMaster::Initialize()
{
	m_bNewGamePending = true;
	m_bGameOver = false;
	m_bBlockWasPlaced = false;

	// Load up game config
	m_gameConfig.AddVar("fullscreen", "false");
	m_gameConfig.AddVar("win_width", 1024);
	m_gameConfig.AddVar("win_height", 768);
	m_gameConfig.AddVar("audio", "true");
	m_gameConfig.LoadConfigFile("TTM.cfg");
	return true;
}

void TheTetrisMaster::PreCreateWindow(std::string& strTitle, int& iWidth, int& iHeight, int& iBitsPerPixel, bool& bFullScreen)
{
	strTitle = GAME_TITLE;
	iWidth = SCREEN_WIDTH;
	iHeight = SCREEN_HEIGHT;
	iBitsPerPixel = BITS_PER_PIXEL;
	bFullScreen = m_gameConfig["fullscreen"] == "true";
}

void TheTetrisMaster::InitializeAudioStreamMgr(AudioStreamMgr& rAudioStreamMgr)
{
	if ( m_gameConfig["audio"] == "true" )
		rAudioStreamMgr.Initialize();
}

// Yes, macros are evil, but this makes the code cleaner and is only used
// in TheTetrisMaster::LoadGameData() below
#define LOAD_TRANSP_WIN(var, name) \
	if (true) { \
		ImageData subImgData = imgData.GetSubImageData(0, 0, name##_WIDTH, name##_HEIGHT); \
		var = Texture::Load(subImgData); \
		var->SetAlphaBlending(true, fTranspLevel); \
		var->Pos().Set(name##_X, name##_Y); \
	} else

bool TheTetrisMaster::LoadGameData()
{
	// Resize window in windowed mode
	IWindow& rWindow = GameApp::Instance().GetWindow();
	if ( !rWindow.IsFullScreen() )
	{
		rWindow.Resize(m_gameConfig["win_width"].AsInt(), m_gameConfig["win_height"].AsInt());
	}

	// Load theme
	TMTheme theme;
	theme.LoadTheme(m_strThemeName);

	// Grab transparency level
	float fTranspLevel = theme.GetTranspLevel();

	// Load up the background (cut up into smaller textures for older video card compatibility)
	ImageData imgData = ImageData::LoadRAW(theme.GetBackgroundImageFileName(), RAW_SZ(SCREEN_WIDTH), RAW_SZ(SCREEN_HEIGHT), 3);
	imgData.Scale(SCALE_FACTOR, SCALE_FACTOR);
	m_background.TileImage(imgData, SCREEN_WIDTH/4, SCREEN_HEIGHT/2);

	// Load up the map window
	imgData = ImageData::LoadRAW(theme.GetTranpWinImageFileName(), RAW_SZ(MAP_WIDTH), RAW_SZ(MAP_HEIGHT), 3);
	imgData.Scale(SCALE_FACTOR, SCALE_FACTOR);
	m_mapWindow.TileImage(imgData, MAP_WIDTH, MAP_HEIGHT/2, MAP_X, MAP_Y);
	m_mapWindow.SetAlphaBlending(true, fTranspLevel);

	// From the map window, load up the other windows
	LOAD_TRANSP_WIN(m_gameInfoWindow, GAMEINFO_WIN);
	LOAD_TRANSP_WIN(m_linesLevelWindow, LINES_LEVEL_WIN);
	LOAD_TRANSP_WIN(m_scoreWindow, SCORE_WIN);
	LOAD_TRANSP_WIN(m_timeWindow, TIME_WIN);
	LOAD_TRANSP_WIN(m_nextWindow, NEXT_WIN);

	// Load up the blocks
	imgData = ImageData::LoadRAW(theme.GetAllBlocksImageFileName(), 70, 10, 3);
	for (int i=0; i<NUM_BLOCKS; ++i)
	{
		m_allBlocks[i] = Texture::Load( imgData.GetSubImageData(i*10, 0, 10, 10).Scale(SCALE_FACTOR, SCALE_FACTOR) );
		m_allBlocks[i]->SetAlphaBlending(true);
	}

	// Load the game info screen
	imgData = ImageData::LoadRAW("Graphics/gameinfo.raw", RAW_SZ(GAMEINFO_WIN_WIDTH), RAW_SZ(GAMEINFO_WIN_HEIGHT));
	imgData.AddAlphaChannel(Color3UB::Black()); // Add alpha using black as the transparent color
	m_gameInfo = Texture::Load(imgData.Scale(SCALE_FACTOR, SCALE_FACTOR));
	m_gameInfo->Pos().Set(GAMEINFO_WIN_X, GAMEINFO_WIN_Y);

	// Load game sounds and music
	AudioStreamMgr& rAudioStreamMgr = GameApp::Instance().GetAudioStreamMgr();

	m_pGameMusic = rAudioStreamMgr.LoadMusicFile( theme.GetGameMusicFileName() );
	m_pPlaceBlockSound = rAudioStreamMgr.LoadSoundFile( theme.GetPlaceBlockSoundFileName(), 5 );
	m_p123LinesSound = rAudioStreamMgr.LoadSoundFile( theme.GetLinesSoundFileName(), 3 );
	m_pTetrisSound = rAudioStreamMgr.LoadSoundFile( theme.GetTetrisSoundFileName(), 3 );
	m_pChangeLevelSound = rAudioStreamMgr.LoadSoundFile( theme.GetChangeLevelSoundFileName(), 1 );
	m_pGameOverSound = rAudioStreamMgr.LoadSoundFile( theme.GetGameOverSoundFileName(), 1 );

	m_pGameMusic->SetRepeat(true); // Make sure music loops

#if 1 // Don't set volumes, the current audio library handles it very well.
	m_pGameMusic->SetVolume(0.8f); // theme.GetMusicVolume() );

	m_pPlaceBlockSound->SetVolume( theme.GetSoundVolume() );
	m_p123LinesSound->SetVolume( theme.GetSoundVolume() );
	m_pTetrisSound->SetVolume( theme.GetSoundVolume() );
	m_pChangeLevelSound->SetVolume( theme.GetSoundVolume() );
	m_pGameOverSound->SetVolume( theme.GetSoundVolume() );
#endif

	// Load up the font
	m_font.Initialize(TMFont::SHADOWS_ON, theme.GetFontColor(), theme.GetFontShadowColor());

	return true;
}

void TheTetrisMaster::ProcessInput(const KeyboardMgr& rKeyboardMgr)
{
	static TMScreen* pScreen = TMScreen::GetInstance();
	static TMMap* pMap = pScreen->GetMap();

	// If new game is pending, we're waiting for user to hit space to start
	if ( m_bNewGamePending )
	{
		if ( rKeyboardMgr[VK_SPACE].Pressed() )
		{
			m_bNewGamePending = false;
			TMScreen::GetInstance()->NewGame();
		}

		return; // Don't continue until Space is pressed
	}

	static bool bPaused = pScreen->IsPaused();
	if ( rKeyboardMgr['P'].Pressed() )
	{
		bPaused = !bPaused;
		pScreen->PauseGame(bPaused);

		// Pause/Resume all musica and sound
		if ( bPaused )
			GameApp::Instance().GetAudioStreamMgr().PauseAll();
		else
			GameApp::Instance().GetAudioStreamMgr().ResumeAll();
	}

	if ( bPaused )
		return; // Don't continue in paused state

	// Get amount of MS elapsed since last iteration
	float fDeltaTimeMS = GameApp::Instance().GetDeltaTimeMS();

	// Timing variables
	const float fRotTimeMS = 250;

	// Drop time must be at least as fast as maximum drop speed.
	// According to tetris spec, at max level 9, it drops at 0.05 second
	// intervals (=50 ms)
	const float fMoveDownMS = 50;
	const float fMoveDownAfterBlockPlacementsMS = fMoveDownMS * 10;

	const float fMoveHorzMS = 80;
	const float fFirstHorzMS = 300;
	static int numTimesMovedLeft = 0;
	static int numTimesMovedRight = 0;
	const float fMoveLeftMS = numTimesMovedLeft == 1 ? fFirstHorzMS : fMoveHorzMS;
	const float fMoveRightMS = numTimesMovedRight == 1 ? fFirstHorzMS : fMoveHorzMS;

	bool bDownTimeElapsed; // Used to track if a key has been down a given amount of time

	if ( rKeyboardMgr['A'].Down(fRotTimeMS, fDeltaTimeMS, bDownTimeElapsed) )
	{
		if ( bDownTimeElapsed )
			pMap->RotateCurrBlockRight();
	}
	else if ( rKeyboardMgr['S'].Down(fRotTimeMS, fDeltaTimeMS, bDownTimeElapsed) )
	{
		if ( bDownTimeElapsed )
			pMap->RotateCurrBlockLeft();
	}

	const bool bLeftDown = rKeyboardMgr[VK_LEFT].Down(fMoveLeftMS, fDeltaTimeMS, bDownTimeElapsed);
	if ( bLeftDown )
	{
		if ( bDownTimeElapsed )
		{
			pMap->MoveCurrBlockLeft();
			++numTimesMovedLeft;
		}
	}
	else
	{
		numTimesMovedLeft = 0;
	}

	const bool bRightDown = rKeyboardMgr[VK_RIGHT].Down(fMoveRightMS, fDeltaTimeMS, bDownTimeElapsed);
	if ( !bLeftDown && bRightDown )
	{
		if ( bDownTimeElapsed )
		{
			pMap->MoveCurrBlockRight();
			++numTimesMovedRight;
		}
	}
	else
	{
		numTimesMovedRight = 0;
	}

	// For moving blocks down, we want the delay to be longer when a block is just placed so that
	// the next piece doesn't start dropping at full speed as soon as it's spawned, giving the player
	// a chance to optionally let go of the down button and line up the piece.
	float fActualMoveDownMS = m_bBlockWasPlaced? fMoveDownAfterBlockPlacementsMS : fMoveDownMS;
	if ( rKeyboardMgr[VK_DOWN].Down(fActualMoveDownMS, fDeltaTimeMS, bDownTimeElapsed) && bDownTimeElapsed )
	{
		m_bBlockWasPlaced = pMap->MoveCurrBlockDown();
	}
}

bool TheTetrisMaster::RenderFrame(IRenderer& renderer)
{
	// While new game is pending, just render the background and display message
	if ( m_bNewGamePending )
	{
		GetBackgroundTexture().RenderQuad();
		m_font.WriteCentered(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, "Press SPACE to start");
	}
	else
	{
		// Just tell the screen instance to do its work. It returns false
		// on Game Over
		m_bGameOver = TMScreen::GetInstance()->DoIteration() == false;
	}

	// Write game version
	m_font.Write(SCREEN_WIDTH-SCALE(80), SCREEN_HEIGHT-SCALE(15), VERSION);

	return true;
}

void TheTetrisMaster::PostFrameLogic()
{
	if ( !m_bNewGamePending && m_bGameOver )
	{
		// Game over just happened
		TMScreen::GetInstance()->DoGameOver();
		m_bNewGamePending = true; // Start waiting for a new game
	}
}
<commit_msg>Make pressing up immediately place the current block in straight line downwards<commit_after>#include "Tetris.h"

#include <iostream>
#include "TMTheme.h"
#include "TMScreen.h"

const char* VERSION = "ver 2.1";

bool TheTetrisMaster::Initialize()
{
	m_bNewGamePending = true;
	m_bGameOver = false;
	m_bBlockWasPlaced = false;

	// Load up game config
	m_gameConfig.AddVar("fullscreen", "false");
	m_gameConfig.AddVar("win_width", 1024);
	m_gameConfig.AddVar("win_height", 768);
	m_gameConfig.AddVar("audio", "true");
	m_gameConfig.LoadConfigFile("TTM.cfg");
	return true;
}

void TheTetrisMaster::PreCreateWindow(std::string& strTitle, int& iWidth, int& iHeight, int& iBitsPerPixel, bool& bFullScreen)
{
	strTitle = GAME_TITLE;
	iWidth = SCREEN_WIDTH;
	iHeight = SCREEN_HEIGHT;
	iBitsPerPixel = BITS_PER_PIXEL;
	bFullScreen = m_gameConfig["fullscreen"] == "true";
}

void TheTetrisMaster::InitializeAudioStreamMgr(AudioStreamMgr& rAudioStreamMgr)
{
	if ( m_gameConfig["audio"] == "true" )
		rAudioStreamMgr.Initialize();
}

// Yes, macros are evil, but this makes the code cleaner and is only used
// in TheTetrisMaster::LoadGameData() below
#define LOAD_TRANSP_WIN(var, name) \
	if (true) { \
		ImageData subImgData = imgData.GetSubImageData(0, 0, name##_WIDTH, name##_HEIGHT); \
		var = Texture::Load(subImgData); \
		var->SetAlphaBlending(true, fTranspLevel); \
		var->Pos().Set(name##_X, name##_Y); \
	} else

bool TheTetrisMaster::LoadGameData()
{
	// Resize window in windowed mode
	IWindow& rWindow = GameApp::Instance().GetWindow();
	if ( !rWindow.IsFullScreen() )
	{
		rWindow.Resize(m_gameConfig["win_width"].AsInt(), m_gameConfig["win_height"].AsInt());
	}

	// Load theme
	TMTheme theme;
	theme.LoadTheme(m_strThemeName);

	// Grab transparency level
	float fTranspLevel = theme.GetTranspLevel();

	// Load up the background (cut up into smaller textures for older video card compatibility)
	ImageData imgData = ImageData::LoadRAW(theme.GetBackgroundImageFileName(), RAW_SZ(SCREEN_WIDTH), RAW_SZ(SCREEN_HEIGHT), 3);
	imgData.Scale(SCALE_FACTOR, SCALE_FACTOR);
	m_background.TileImage(imgData, SCREEN_WIDTH/4, SCREEN_HEIGHT/2);

	// Load up the map window
	imgData = ImageData::LoadRAW(theme.GetTranpWinImageFileName(), RAW_SZ(MAP_WIDTH), RAW_SZ(MAP_HEIGHT), 3);
	imgData.Scale(SCALE_FACTOR, SCALE_FACTOR);
	m_mapWindow.TileImage(imgData, MAP_WIDTH, MAP_HEIGHT/2, MAP_X, MAP_Y);
	m_mapWindow.SetAlphaBlending(true, fTranspLevel);

	// From the map window, load up the other windows
	LOAD_TRANSP_WIN(m_gameInfoWindow, GAMEINFO_WIN);
	LOAD_TRANSP_WIN(m_linesLevelWindow, LINES_LEVEL_WIN);
	LOAD_TRANSP_WIN(m_scoreWindow, SCORE_WIN);
	LOAD_TRANSP_WIN(m_timeWindow, TIME_WIN);
	LOAD_TRANSP_WIN(m_nextWindow, NEXT_WIN);

	// Load up the blocks
	imgData = ImageData::LoadRAW(theme.GetAllBlocksImageFileName(), 70, 10, 3);
	for (int i=0; i<NUM_BLOCKS; ++i)
	{
		m_allBlocks[i] = Texture::Load( imgData.GetSubImageData(i*10, 0, 10, 10).Scale(SCALE_FACTOR, SCALE_FACTOR) );
		m_allBlocks[i]->SetAlphaBlending(true);
	}

	// Load the game info screen
	imgData = ImageData::LoadRAW("Graphics/gameinfo.raw", RAW_SZ(GAMEINFO_WIN_WIDTH), RAW_SZ(GAMEINFO_WIN_HEIGHT));
	imgData.AddAlphaChannel(Color3UB::Black()); // Add alpha using black as the transparent color
	m_gameInfo = Texture::Load(imgData.Scale(SCALE_FACTOR, SCALE_FACTOR));
	m_gameInfo->Pos().Set(GAMEINFO_WIN_X, GAMEINFO_WIN_Y);

	// Load game sounds and music
	AudioStreamMgr& rAudioStreamMgr = GameApp::Instance().GetAudioStreamMgr();

	m_pGameMusic = rAudioStreamMgr.LoadMusicFile( theme.GetGameMusicFileName() );
	m_pPlaceBlockSound = rAudioStreamMgr.LoadSoundFile( theme.GetPlaceBlockSoundFileName(), 5 );
	m_p123LinesSound = rAudioStreamMgr.LoadSoundFile( theme.GetLinesSoundFileName(), 3 );
	m_pTetrisSound = rAudioStreamMgr.LoadSoundFile( theme.GetTetrisSoundFileName(), 3 );
	m_pChangeLevelSound = rAudioStreamMgr.LoadSoundFile( theme.GetChangeLevelSoundFileName(), 1 );
	m_pGameOverSound = rAudioStreamMgr.LoadSoundFile( theme.GetGameOverSoundFileName(), 1 );

	m_pGameMusic->SetRepeat(true); // Make sure music loops

#if 1 // Don't set volumes, the current audio library handles it very well.
	m_pGameMusic->SetVolume(0.8f); // theme.GetMusicVolume() );

	m_pPlaceBlockSound->SetVolume( theme.GetSoundVolume() );
	m_p123LinesSound->SetVolume( theme.GetSoundVolume() );
	m_pTetrisSound->SetVolume( theme.GetSoundVolume() );
	m_pChangeLevelSound->SetVolume( theme.GetSoundVolume() );
	m_pGameOverSound->SetVolume( theme.GetSoundVolume() );
#endif

	// Load up the font
	m_font.Initialize(TMFont::SHADOWS_ON, theme.GetFontColor(), theme.GetFontShadowColor());

	return true;
}

void TheTetrisMaster::ProcessInput(const KeyboardMgr& rKeyboardMgr)
{
	static TMScreen* pScreen = TMScreen::GetInstance();
	static TMMap* pMap = pScreen->GetMap();

	// If new game is pending, we're waiting for user to hit space to start
	if ( m_bNewGamePending )
	{
		if ( rKeyboardMgr[VK_SPACE].Pressed() )
		{
			m_bNewGamePending = false;
			TMScreen::GetInstance()->NewGame();
		}

		return; // Don't continue until Space is pressed
	}

	static bool bPaused = pScreen->IsPaused();
	if ( rKeyboardMgr['P'].Pressed() )
	{
		bPaused = !bPaused;
		pScreen->PauseGame(bPaused);

		// Pause/Resume all musica and sound
		if ( bPaused )
			GameApp::Instance().GetAudioStreamMgr().PauseAll();
		else
			GameApp::Instance().GetAudioStreamMgr().ResumeAll();
	}

	if ( bPaused )
		return; // Don't continue in paused state

	// Get amount of MS elapsed since last iteration
	float fDeltaTimeMS = GameApp::Instance().GetDeltaTimeMS();

	// Timing variables
	const float fRotTimeMS = 250;

	// Drop time must be at least as fast as maximum drop speed.
	// According to tetris spec, at max level 9, it drops at 0.05 second
	// intervals (=50 ms)
	const float fMoveDownMS = 50;
	const float fMoveDownAfterBlockPlacementsMS = fMoveDownMS * 10;

	const float fMoveHorzMS = 80;
	const float fFirstHorzMS = 300;
	static int numTimesMovedLeft = 0;
	static int numTimesMovedRight = 0;
	const float fMoveLeftMS = numTimesMovedLeft == 1 ? fFirstHorzMS : fMoveHorzMS;
	const float fMoveRightMS = numTimesMovedRight == 1 ? fFirstHorzMS : fMoveHorzMS;

	bool bDownTimeElapsed; // Used to track if a key has been down a given amount of time

	if ( rKeyboardMgr['A'].Down(fRotTimeMS, fDeltaTimeMS, bDownTimeElapsed) )
	{
		if ( bDownTimeElapsed )
			pMap->RotateCurrBlockRight();
	}
	else if ( rKeyboardMgr['S'].Down(fRotTimeMS, fDeltaTimeMS, bDownTimeElapsed) )
	{
		if ( bDownTimeElapsed )
			pMap->RotateCurrBlockLeft();
	}

	const bool bLeftDown = rKeyboardMgr[VK_LEFT].Down(fMoveLeftMS, fDeltaTimeMS, bDownTimeElapsed);
	if ( bLeftDown )
	{
		if ( bDownTimeElapsed )
		{
			pMap->MoveCurrBlockLeft();
			++numTimesMovedLeft;
		}
	}
	else
	{
		numTimesMovedLeft = 0;
	}

	const bool bRightDown = rKeyboardMgr[VK_RIGHT].Down(fMoveRightMS, fDeltaTimeMS, bDownTimeElapsed);
	if ( !bLeftDown && bRightDown )
	{
		if ( bDownTimeElapsed )
		{
			pMap->MoveCurrBlockRight();
			++numTimesMovedRight;
		}
	}
	else
	{
		numTimesMovedRight = 0;
	}

	// For moving blocks down, we want the delay to be longer when a block is just placed so that
	// the next piece doesn't start dropping at full speed as soon as it's spawned, giving the player
	// a chance to optionally let go of the down button and line up the piece.
	float fActualMoveDownMS = m_bBlockWasPlaced? fMoveDownAfterBlockPlacementsMS : fMoveDownMS;
	if ( rKeyboardMgr[VK_DOWN].Down(fActualMoveDownMS, fDeltaTimeMS, bDownTimeElapsed) && bDownTimeElapsed )
	{
		m_bBlockWasPlaced = pMap->MoveCurrBlockDown();
	}

	if ( rKeyboardMgr[VK_UP].Pressed() )
	{
		while (!pMap->MoveCurrBlockDown()) {}
	}
}

bool TheTetrisMaster::RenderFrame(IRenderer& renderer)
{
	// While new game is pending, just render the background and display message
	if ( m_bNewGamePending )
	{
		GetBackgroundTexture().RenderQuad();
		m_font.WriteCentered(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, "Press SPACE to start");
	}
	else
	{
		// Just tell the screen instance to do its work. It returns false
		// on Game Over
		m_bGameOver = TMScreen::GetInstance()->DoIteration() == false;
	}

	// Write game version
	m_font.Write(SCREEN_WIDTH-SCALE(80), SCREEN_HEIGHT-SCALE(15), VERSION);

	return true;
}

void TheTetrisMaster::PostFrameLogic()
{
	if ( !m_bNewGamePending && m_bGameOver )
	{
		// Game over just happened
		TMScreen::GetInstance()->DoGameOver();
		m_bNewGamePending = true; // Start waiting for a new game
	}
}
<|endoftext|>


<commit_before>#include <new>
#include <iostream>
#include <windows.h>
#include <mfapi.h>
#include <Mfidl.h>
#include <shlwapi.h>
#include <codecapi.h>
#include <iostream>

#include "CSession.h"
#include "SafeRelease.h"
#include "WindowsError.h"

#pragma comment(lib, "mfplat")
#pragma comment(lib, "mf")
#pragma comment(lib, "mfuuid")
#pragma comment(lib, "shlwapi")

#pragma warning(disable : 4996)

struct H264ProfileInfo
{
	UINT32  profile;
	MFRatio fps;
	MFRatio frame_size;
	UINT32  bitrate;
};

H264ProfileInfo h264_profiles[] =
{
	{ eAVEncH264VProfile_Base,{ 15, 1 },{ 176, 144 },   128000 },
	{ eAVEncH264VProfile_Base,{ 15, 1 },{ 352, 288 },   384000 },
	{ eAVEncH264VProfile_Base,{ 30, 1 },{ 352, 288 },   384000 },
	{ eAVEncH264VProfile_Base,{ 29970, 1000 },{ 320, 240 },   528560 },
	{ eAVEncH264VProfile_Base,{ 15, 1 },{ 720, 576 },  4000000 },
	{ eAVEncH264VProfile_Main,{ 25, 1 },{ 720, 576 }, 10000000 },
	{ eAVEncH264VProfile_Main,{ 30, 1 },{ 352, 288 }, 10000000 },
};

struct AACProfileInfo
{
	UINT32  samplesPerSec;
	UINT32  numChannels;
	UINT32  bitsPerSample;
	UINT32  bytesPerSec;
	UINT32  aacProfile;
};

AACProfileInfo aac_profiles[] =
{
	{ 96000, 2, 16, 24000, 0x29 },
	{ 48000, 2, 16, 24000, 0x29 },
	{ 44100, 2, 16, 16000, 0x29 },
	{ 44100, 2, 16, 12000, 0x29 },
};

int video_profile = 0;
int audio_profile = 0;

void CreateMediaSource(PCWSTR pszURL, IMFMediaSource **ppSource)
{
	MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID;
	SafeReleasePointerWrapper<IMFSourceResolver> pResolver(nullptr);
	SafeReleasePointerWrapper<IUnknown> pSource(nullptr);

	// Create the source resolver.
	HRESULT hr = MFCreateSourceResolver(pResolver.getPointer());
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	// Use the source resolver to create the media source
	hr = pResolver.get()->CreateObjectFromURL(pszURL, MF_RESOLUTION_MEDIASOURCE, NULL, &ObjectType, pSource.getPointer());
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	// Get the IMFMediaSource interface from the media source.
	hr = pSource.get()->QueryInterface(IID_PPV_ARGS(ppSource));
}

void GetSourceDuration(IMFMediaSource *pSource, MFTIME *pDuration)
{
	*pDuration = 0;

	IMFPresentationDescriptor *pPD = NULL;

	HRESULT hr = pSource->CreatePresentationDescriptor(&pPD);
	if (SUCCEEDED(hr))
	{
		hr = pPD->GetUINT64(MF_PD_DURATION, (UINT64*)pDuration);
		pPD->Release();
	}
	else
	{
		throw WindowsError(hr);
	}
}

void CreateAACProfile(DWORD index, IMFAttributes **ppAttributes)
{
	if (index >= ARRAYSIZE(h264_profiles))
	{
		throw WindowsError(E_INVALIDARG);
	}

	const AACProfileInfo& profile = aac_profiles[index];

	SafeReleasePointerWrapper<IMFAttributes> pAttributes(nullptr);

	HRESULT hr = MFCreateAttributes(pAttributes.getPointer(), 7);
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_AAC);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, profile.bitsPerSample);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, profile.samplesPerSec);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, profile.numChannels);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, profile.bytesPerSec);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 1);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_AAC_AUDIO_PROFILE_LEVEL_INDICATION, profile.aacProfile);
	}
	if (SUCCEEDED(hr))
	{
		*ppAttributes = pAttributes.get();
		(*ppAttributes)->AddRef();
	}
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}
}

void CreateH264Profile(DWORD index, IMFAttributes **ppAttributes)
{
	if (index >= ARRAYSIZE(h264_profiles))
	{
		throw WindowsError(E_INVALIDARG);
	}

	SafeReleasePointerWrapper<IMFAttributes> pAttributes(nullptr);

	const H264ProfileInfo& profile = h264_profiles[index];

	HRESULT hr = MFCreateAttributes(pAttributes.getPointer(), 5);
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_MPEG2_PROFILE, profile.profile);
	}
	if (SUCCEEDED(hr))
	{
		hr = MFSetAttributeSize(pAttributes.get(), MF_MT_FRAME_SIZE, profile.frame_size.Numerator, profile.frame_size.Numerator);
	}
	if (SUCCEEDED(hr))
	{
		hr = MFSetAttributeRatio(pAttributes.get(), MF_MT_FRAME_RATE, profile.fps.Numerator, profile.fps.Denominator);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_AVG_BITRATE, profile.bitrate);
	}
	if (SUCCEEDED(hr))
	{
		*ppAttributes = pAttributes.get();
		(*ppAttributes)->AddRef();
	}
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}
}

void CreateTranscodeProfile(IMFTranscodeProfile **ppProfile)
{
	SafeReleasePointerWrapper<IMFTranscodeProfile> pProfile(nullptr);
	SafeReleasePointerWrapper<IMFAttributes> pAudio(nullptr);
	SafeReleasePointerWrapper<IMFAttributes> pVideo(nullptr);
	SafeReleasePointerWrapper<IMFAttributes> pContainer(nullptr);

	HRESULT hr = MFCreateTranscodeProfile(pProfile.getPointer());
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	// Audio attributes.
	CreateAACProfile(audio_profile, pAudio.getPointer());
	pProfile.get()->SetAudioAttributes(pAudio.get());
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	// Video attributes.
	CreateH264Profile(video_profile, pVideo.getPointer());
	pProfile.get()->SetVideoAttributes(pVideo.get());
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	// Container attributes.
	hr = MFCreateAttributes(pContainer.getPointer(), 1);
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	hr = pContainer.get()->SetGUID(MF_TRANSCODE_CONTAINERTYPE, MFTranscodeContainerType_MPEG4);
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	hr = pProfile.get()->SetContainerAttributes(pContainer.get());
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	*ppProfile = pProfile.get();
	(*ppProfile)->AddRef();
}

void RunEncodingSession(CSession *pSession, MFTIME duration)
{
	const DWORD WAIT_PERIOD = 500;
	const int   UPDATE_INCR = 5;

	HRESULT hr = S_OK;
	MFTIME pos;
	LONGLONG prev = 0;
	while (1)
	{
		hr = pSession->Wait(WAIT_PERIOD);
		if (hr == E_PENDING)
		{
			hr = pSession->GetEncodingPosition(&pos);

			LONGLONG percent = (100 * pos) / duration;
			if (percent >= prev + UPDATE_INCR)
			{
				std::cout << percent << "% .. ";
				prev = percent;
			}
		}
		else
		{
			std::cout << std::endl;
			break;
		}
	}
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}
}

void EncodeFile(PCWSTR pszInput, PCWSTR pszOutput)
{

	SafeReleasePointerWrapper<IMFTranscodeProfile> pProfile(nullptr);
	SafeReleasePointerWrapper<IMFMediaSource> pSource(nullptr);
	SafeReleasePointerWrapper<IMFTopology> pTopology(nullptr);
	SafeReleasePointerWrapper<CSession> pSession(nullptr);

	CreateMediaSource(pszInput, pSource.getPointer());

	MFTIME duration = 0;
	GetSourceDuration(pSource.get(), &duration);
	std::cout << "Duration: " << duration << std::endl;

	CreateTranscodeProfile(pProfile.getPointer());

	IMFAttributes* imfAttrs = nullptr;
	UINT32 count = 0;
	HRESULT hr = pProfile.get()->GetContainerAttributes(&imfAttrs);
	if (FAILED(hr))
	{
		if (pSource.get())
		{
			pSource.get()->Shutdown();
		}
		throw WindowsError(hr);
	}

	hr = MFCreateTranscodeTopology(pSource.get(), pszOutput, pProfile.get(), pTopology.getPointer());
	if (FAILED(hr))
	{
		if (pSource.get())
		{
			pSource.get()->Shutdown();
		}
		throw WindowsError(hr);
	}

	hr = CSession::Create(pSession.getPointer());
	if (FAILED(hr))
	{
		if (pSource.get())
		{
			pSource.get()->Shutdown();
		}
		throw WindowsError(hr);
	}

	hr = pSession.get()->StartEncodingSession(pTopology.get());
	if (FAILED(hr))
	{
		if (pSource.get())
		{
			pSource.get()->Shutdown();
		}
		throw WindowsError(hr);
	}

	RunEncodingSession(pSession.get(), duration);

	if (pSource.get())
	{
		pSource.get()->Shutdown();
	}

}

int main(int argc, char* argv[])
{

	HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);

	if (argc < 3 || argc > 5)
	{
		std::cout << "Usage:" << std::endl;
		std::cout << "input output [ audio_profile video_profile ]" << std::endl;
		return 1;
	}

	if (argc > 3)
	{
		audio_profile = atoi(argv[3]);
	}
	if (argc > 4)
	{
		video_profile = atoi(argv[4]);
	}

	HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	if (SUCCEEDED(hr))
	{
		hr = MFStartup(MF_VERSION);
		if (SUCCEEDED(hr))
		{
			// arg1
			size_t arg1Length = mbsrtowcs(NULL, (const char**)&argv[1], 0, NULL);
			wchar_t* arg1 = new wchar_t[arg1Length + 1]();
			arg1Length = mbsrtowcs(arg1, (const char**)&argv[1], arg1Length + 1, NULL);

			// arg2
			size_t arg2Length = mbsrtowcs(NULL, (const char**)&argv[2], 0, NULL);
			wchar_t* arg2 = new wchar_t[arg2Length + 1]();
			arg1Length = mbsrtowcs(arg2, (const char**)&argv[2], arg2Length + 1, NULL);

			try
			{
				EncodeFile(arg1, arg2);
			}
			catch (const WindowsError& err)
			{
				std::cout << "Catched exception - " << err.toString() << std::endl;
			}

			delete[] arg1;
			delete[] arg2;

			MFShutdown();
		}
		else
		{
			std::cout << "Can't MFStartup: " << std::hex << hr << std::endl;
		}
		CoUninitialize();
	}
	else
	{
		std::cout << "Can't CoInitializeEx: " << std::hex << hr << std::endl;
	}

	if (SUCCEEDED(hr))
	{
		std::cout << "Done." << std::endl;
	}
	else
	{
		std::cout << "Error: " << std::hex << hr << std::endl;
	}

	return 0;
}
<commit_msg>Rely on SafeReleasePointerWrapper<IMFMediaSource> for cleanup<commit_after>#include <new>
#include <iostream>
#include <windows.h>
#include <mfapi.h>
#include <Mfidl.h>
#include <shlwapi.h>
#include <codecapi.h>
#include <iostream>

#include "CSession.h"
#include "SafeRelease.h"
#include "WindowsError.h"

#pragma comment(lib, "mfplat")
#pragma comment(lib, "mf")
#pragma comment(lib, "mfuuid")
#pragma comment(lib, "shlwapi")

#pragma warning(disable : 4996)

struct H264ProfileInfo
{
	UINT32  profile;
	MFRatio fps;
	MFRatio frame_size;
	UINT32  bitrate;
};

H264ProfileInfo h264_profiles[] =
{
	{ eAVEncH264VProfile_Base,{ 15, 1 },{ 176, 144 },   128000 },
	{ eAVEncH264VProfile_Base,{ 15, 1 },{ 352, 288 },   384000 },
	{ eAVEncH264VProfile_Base,{ 30, 1 },{ 352, 288 },   384000 },
	{ eAVEncH264VProfile_Base,{ 29970, 1000 },{ 320, 240 },   528560 },
	{ eAVEncH264VProfile_Base,{ 15, 1 },{ 720, 576 },  4000000 },
	{ eAVEncH264VProfile_Main,{ 25, 1 },{ 720, 576 }, 10000000 },
	{ eAVEncH264VProfile_Main,{ 30, 1 },{ 352, 288 }, 10000000 },
};

struct AACProfileInfo
{
	UINT32  samplesPerSec;
	UINT32  numChannels;
	UINT32  bitsPerSample;
	UINT32  bytesPerSec;
	UINT32  aacProfile;
};

AACProfileInfo aac_profiles[] =
{
	{ 96000, 2, 16, 24000, 0x29 },
	{ 48000, 2, 16, 24000, 0x29 },
	{ 44100, 2, 16, 16000, 0x29 },
	{ 44100, 2, 16, 12000, 0x29 },
};

int video_profile = 0;
int audio_profile = 0;

void CreateMediaSource(PCWSTR pszURL, IMFMediaSource **ppSource)
{
	MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID;
	SafeReleasePointerWrapper<IMFSourceResolver> pResolver(nullptr);
	SafeReleasePointerWrapper<IUnknown> pSource(nullptr);

	// Create the source resolver.
	HRESULT hr = MFCreateSourceResolver(pResolver.getPointer());
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	// Use the source resolver to create the media source
	hr = pResolver.get()->CreateObjectFromURL(pszURL, MF_RESOLUTION_MEDIASOURCE, NULL, &ObjectType, pSource.getPointer());
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	// Get the IMFMediaSource interface from the media source.
	hr = pSource.get()->QueryInterface(IID_PPV_ARGS(ppSource));
}

void GetSourceDuration(IMFMediaSource *pSource, MFTIME *pDuration)
{
	*pDuration = 0;

	IMFPresentationDescriptor *pPD = NULL;

	HRESULT hr = pSource->CreatePresentationDescriptor(&pPD);
	if (SUCCEEDED(hr))
	{
		hr = pPD->GetUINT64(MF_PD_DURATION, (UINT64*)pDuration);
		pPD->Release();
	}
	else
	{
		throw WindowsError(hr);
	}
}

void CreateAACProfile(DWORD index, IMFAttributes **ppAttributes)
{
	if (index >= ARRAYSIZE(h264_profiles))
	{
		throw WindowsError(E_INVALIDARG);
	}

	const AACProfileInfo& profile = aac_profiles[index];

	SafeReleasePointerWrapper<IMFAttributes> pAttributes(nullptr);

	HRESULT hr = MFCreateAttributes(pAttributes.getPointer(), 7);
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_AAC);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, profile.bitsPerSample);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, profile.samplesPerSec);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, profile.numChannels);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, profile.bytesPerSec);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 1);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_AAC_AUDIO_PROFILE_LEVEL_INDICATION, profile.aacProfile);
	}
	if (SUCCEEDED(hr))
	{
		*ppAttributes = pAttributes.get();
		(*ppAttributes)->AddRef();
	}
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}
}

void CreateH264Profile(DWORD index, IMFAttributes **ppAttributes)
{
	if (index >= ARRAYSIZE(h264_profiles))
	{
		throw WindowsError(E_INVALIDARG);
	}

	SafeReleasePointerWrapper<IMFAttributes> pAttributes(nullptr);

	const H264ProfileInfo& profile = h264_profiles[index];

	HRESULT hr = MFCreateAttributes(pAttributes.getPointer(), 5);
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_MPEG2_PROFILE, profile.profile);
	}
	if (SUCCEEDED(hr))
	{
		hr = MFSetAttributeSize(pAttributes.get(), MF_MT_FRAME_SIZE, profile.frame_size.Numerator, profile.frame_size.Numerator);
	}
	if (SUCCEEDED(hr))
	{
		hr = MFSetAttributeRatio(pAttributes.get(), MF_MT_FRAME_RATE, profile.fps.Numerator, profile.fps.Denominator);
	}
	if (SUCCEEDED(hr))
	{
		hr = pAttributes.get()->SetUINT32(MF_MT_AVG_BITRATE, profile.bitrate);
	}
	if (SUCCEEDED(hr))
	{
		*ppAttributes = pAttributes.get();
		(*ppAttributes)->AddRef();
	}
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}
}

void CreateTranscodeProfile(IMFTranscodeProfile **ppProfile)
{
	SafeReleasePointerWrapper<IMFTranscodeProfile> pProfile(nullptr);
	SafeReleasePointerWrapper<IMFAttributes> pAudio(nullptr);
	SafeReleasePointerWrapper<IMFAttributes> pVideo(nullptr);
	SafeReleasePointerWrapper<IMFAttributes> pContainer(nullptr);

	HRESULT hr = MFCreateTranscodeProfile(pProfile.getPointer());
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	// Audio attributes.
	CreateAACProfile(audio_profile, pAudio.getPointer());
	pProfile.get()->SetAudioAttributes(pAudio.get());
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	// Video attributes.
	CreateH264Profile(video_profile, pVideo.getPointer());
	pProfile.get()->SetVideoAttributes(pVideo.get());
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	// Container attributes.
	hr = MFCreateAttributes(pContainer.getPointer(), 1);
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	hr = pContainer.get()->SetGUID(MF_TRANSCODE_CONTAINERTYPE, MFTranscodeContainerType_MPEG4);
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	hr = pProfile.get()->SetContainerAttributes(pContainer.get());
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	*ppProfile = pProfile.get();
	(*ppProfile)->AddRef();
}

void RunEncodingSession(CSession *pSession, MFTIME duration)
{
	const DWORD WAIT_PERIOD = 500;
	const int   UPDATE_INCR = 5;

	HRESULT hr = S_OK;
	MFTIME pos;
	LONGLONG prev = 0;
	while (1)
	{
		hr = pSession->Wait(WAIT_PERIOD);
		if (hr == E_PENDING)
		{
			hr = pSession->GetEncodingPosition(&pos);

			LONGLONG percent = (100 * pos) / duration;
			if (percent >= prev + UPDATE_INCR)
			{
				std::cout << percent << "% .. ";
				prev = percent;
			}
		}
		else
		{
			std::cout << std::endl;
			break;
		}
	}
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}
}

void EncodeFile(PCWSTR pszInput, PCWSTR pszOutput)
{

	SafeReleasePointerWrapper<IMFTranscodeProfile> pProfile(nullptr);
	SafeReleasePointerWrapper<IMFMediaSource> pSource(nullptr);
	SafeReleasePointerWrapper<IMFTopology> pTopology(nullptr);
	SafeReleasePointerWrapper<CSession> pSession(nullptr);

	CreateMediaSource(pszInput, pSource.getPointer());

	MFTIME duration = 0;
	GetSourceDuration(pSource.get(), &duration);
	std::cout << "Duration: " << duration << std::endl;

	CreateTranscodeProfile(pProfile.getPointer());

	IMFAttributes* imfAttrs = nullptr;
	UINT32 count = 0;
	HRESULT hr = pProfile.get()->GetContainerAttributes(&imfAttrs);
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	hr = MFCreateTranscodeTopology(pSource.get(), pszOutput, pProfile.get(), pTopology.getPointer());
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	hr = CSession::Create(pSession.getPointer());
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	hr = pSession.get()->StartEncodingSession(pTopology.get());
	if (FAILED(hr))
	{
		throw WindowsError(hr);
	}

	RunEncodingSession(pSession.get(), duration);

}

int main(int argc, char* argv[])
{

	HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);

	if (argc < 3 || argc > 5)
	{
		std::cout << "Usage:" << std::endl;
		std::cout << "input output [ audio_profile video_profile ]" << std::endl;
		return 1;
	}

	if (argc > 3)
	{
		audio_profile = atoi(argv[3]);
	}
	if (argc > 4)
	{
		video_profile = atoi(argv[4]);
	}

	HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	if (SUCCEEDED(hr))
	{
		hr = MFStartup(MF_VERSION);
		if (SUCCEEDED(hr))
		{
			// arg1
			size_t arg1Length = mbsrtowcs(NULL, (const char**)&argv[1], 0, NULL);
			wchar_t* arg1 = new wchar_t[arg1Length + 1]();
			arg1Length = mbsrtowcs(arg1, (const char**)&argv[1], arg1Length + 1, NULL);

			// arg2
			size_t arg2Length = mbsrtowcs(NULL, (const char**)&argv[2], 0, NULL);
			wchar_t* arg2 = new wchar_t[arg2Length + 1]();
			arg1Length = mbsrtowcs(arg2, (const char**)&argv[2], arg2Length + 1, NULL);

			try
			{
				EncodeFile(arg1, arg2);
			}
			catch (const WindowsError& err)
			{
				std::cout << "Catched exception - " << err.toString() << std::endl;
			}

			delete[] arg1;
			delete[] arg2;

			MFShutdown();
		}
		else
		{
			std::cout << "Can't MFStartup: " << std::hex << hr << std::endl;
		}
		CoUninitialize();
	}
	else
	{
		std::cout << "Can't CoInitializeEx: " << std::hex << hr << std::endl;
	}

	if (SUCCEEDED(hr))
	{
		std::cout << "Done." << std::endl;
	}
	else
	{
		std::cout << "Error: " << std::hex << hr << std::endl;
	}

	return 0;
}
<|endoftext|>


<commit_before>/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2022, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include "opengl-precomp.h"	 // Precompiled header
//
#include <mrpt/containers/yaml.h>
#include <mrpt/math/geometry.h>	 // crossProduct3D()
#include <mrpt/math/ops_containers.h>  // dotProduct()
#include <mrpt/opengl/TRenderMatrices.h>

#include <Eigen/Dense>

using namespace mrpt::opengl;

void TRenderMatrices::computeOrthoProjectionMatrix(
	float left, float right, float bottom, float top, float znear, float zfar)
{
	ASSERT_GT_(zfar, znear);
	m_last_z_near = znear;
	m_last_z_far = zfar;

	p_matrix.setIdentity();

	p_matrix(0, 0) = 2.0f / (right - left);
	p_matrix(1, 1) = 2.0f / (top - bottom);
	p_matrix(2, 2) = -2.0f / (zfar - znear);
	p_matrix(0, 3) = -(right + left) / (right - left);
	p_matrix(1, 3) = -(top + bottom) / (top - bottom);
	p_matrix(2, 3) = -(zfar + znear) / (zfar - znear);
}

void TRenderMatrices::computeNoProjectionMatrix(float znear, float zfar)
{
	ASSERT_GT_(zfar, znear);
	m_last_z_near = znear;
	m_last_z_far = zfar;

	p_matrix.setIdentity();
}

// Replacement for obsolete: gluPerspective() and glOrtho()
void TRenderMatrices::computeProjectionMatrix(float znear, float zfar)
{
	ASSERT_GT_(FOV, .0f);
	ASSERT_GT_(zfar, znear);
	ASSERT_GT_(zfar, .0f);
	ASSERT_GE_(znear, .0f);

	m_last_z_near = znear;
	m_last_z_far = zfar;

	if (pinhole_model.has_value())
	{
		const auto& phm = pinhole_model.value();

		// Equivalent to gluPerspective(), from pinhole camera intrinsic
		// parameters (cx,cy,fx,fy):
		ASSERT_EQUAL_(viewport_width, pinhole_model->ncols);
		ASSERT_EQUAL_(viewport_height, pinhole_model->nrows);

		const int W = pinhole_model->ncols, H = pinhole_model->nrows;

		// See: e.g.
		// http://ksimek.github.io/2013/06/03/calibrated_cameras_in_opengl/
		mrpt::math::CMatrixFloat44 persp;
		persp.setZero();

		persp(0, 0) = phm.fx();
		persp(1, 1) = phm.fy();

		persp(0, 2) = -phm.cx();
		persp(1, 2) = -H + phm.cy();
		persp(2, 2) = (zfar + znear);
		persp(3, 2) = -1.0f;
		persp(2, 3) = zfar * znear;

		// glOrtho(-W/2, W/2, -H/2, H/2, near, far);

		computeOrthoProjectionMatrix(
			0, W, 0 /*bottom*/, H /*top*/, znear, zfar);

		// glMultMatrix(persp);
		p_matrix.asEigen() *= persp.asEigen();
	}
	else if (is_projective)
	{
		// Was: gluPerspective()
		// Based on GLM's perspective (MIT license).

		const float aspect = viewport_width / (1.0f * viewport_height);
		ASSERT_GT_(
			std::abs(aspect - std::numeric_limits<float>::epsilon()), .0f);

		const float f = 1.0f / std::tan(mrpt::DEG2RAD(FOV) / 2.0f);
		p_matrix.setZero();

		p_matrix(0, 0) = f / aspect;
		p_matrix(1, 1) = f;
		p_matrix(2, 2) = -(zfar + znear) / (zfar - znear);
		p_matrix(3, 2) = -1.0f;
		p_matrix(2, 3) = -(2.0f * zfar * znear) / (zfar - znear);
	}
	else
	{
		// Was:
		// glOrtho(-Ax, Ax, -Ay, Ay, -0.5 * m_clip_max, 0.5 * m_clip_max);

		const float ratio = viewport_width / (1.0f * viewport_height);
		float Ax = eyeDistance * 0.5f;
		float Ay = eyeDistance * 0.5f;

		if (ratio > 1) Ax *= ratio;
		else
		{
			if (ratio != 0) Ay /= ratio;
		}

		const auto left = -.5f * Ax, right = .5f * Ax;
		const auto bottom = -.5f * Ay, top = .5f * Ay;
		computeOrthoProjectionMatrix(left, right, bottom, top, znear, zfar);
	}
}

// Replacement for deprecated OpenGL gluLookAt():
void TRenderMatrices::applyLookAt()
{
	using mrpt::math::TVector3D;

	// Note: Use double instead of float to avoid numerical innacuracies that
	// are really noticeable with the naked eye when elevation is close to 90
	// deg (!)
	TVector3D forward = TVector3D(pointing - eye);
	const double fn = forward.norm();
	ASSERT_(fn != 0);
	forward *= 1.0 / fn;

	// Side = forward x up
	TVector3D side = mrpt::math::crossProduct3D(forward, up);
	const double sn = side.norm();
	ASSERT_(sn != 0);
	side *= 1.0 / sn;

	// Recompute up as: up = side x forward
	const TVector3D up2 = mrpt::math::crossProduct3D(side, forward);

	//  s.x   s.y   s.z  -dot(s, eye)
	//  u.x   u.y   u.z  -dot(u, eye)
	// -f.x  -f.y  -f.z  dot(up, eye)
	//   0     0     0      1

	mrpt::math::CMatrixFloat44 m(mrpt::math::UNINITIALIZED_MATRIX);
	// Axis X:
	m(0, 0) = d2f(side[0]);
	m(0, 1) = d2f(side[1]);
	m(0, 2) = d2f(side[2]);
	// Axis Y:
	m(1, 0) = d2f(up2[0]);
	m(1, 1) = d2f(up2[1]);
	m(1, 2) = d2f(up2[2]);
	// Axis Z:
	m(2, 0) = d2f(-forward[0]);
	m(2, 1) = d2f(-forward[1]);
	m(2, 2) = d2f(-forward[2]);
	// Translation:
	m(0, 3) = d2f(-mrpt::math::dotProduct<3, double>(side, eye));
	m(1, 3) = d2f(-mrpt::math::dotProduct<3, double>(up2, eye));
	m(2, 3) = d2f(mrpt::math::dotProduct<3, double>(forward, eye));
	// Last row:
	m(3, 0) = .0f;
	m(3, 1) = .0f;
	m(3, 2) = .0f;
	m(3, 3) = 1.f;

	// Homogeneous matrices composition:
	// Overwrite projection matrix:
	// p_matrix.asEigen() = p_matrix.asEigen() * m.asEigen();
	v_matrix = m;
}

void TRenderMatrices::projectPoint(
	float x, float y, float z, float& proj_u, float& proj_v,
	float& proj_z_depth) const
{
	const Eigen::Matrix<float, 4, 1, Eigen::ColMajor> proj =
		pmv_matrix.asEigen() *
		Eigen::Matrix<float, 4, 1, Eigen::ColMajor>(x, y, z, 1);
	proj_u = proj[3] ? proj[0] / proj[3] : 0;
	proj_v = proj[3] ? proj[1] / proj[3] : 0;
	proj_z_depth = proj[2];
}

void TRenderMatrices::projectPointPixels(
	float x, float y, float z, float& proj_u_px, float& proj_v_px,
	float& proj_depth) const
{
	projectPoint(x, y, z, proj_u_px, proj_v_px, proj_depth);
	proj_u_px = (proj_u_px + 1.0f) * (viewport_width * 0.5f);
	proj_v_px = (proj_v_px + 1.0f) * (viewport_height * 0.5f);
}

void TRenderMatrices::saveToYaml(mrpt::containers::yaml& c) const
{
	c = mrpt::containers::yaml::Map();

	MCP_SAVE(c, initialized);
	MCP_SAVE(c, viewport_width);
	MCP_SAVE(c, viewport_height);
	MCP_SAVE(c, FOV);
	MCP_SAVE_DEG(c, azimuth);
	MCP_SAVE_DEG(c, elev);
	MCP_SAVE(c, eyeDistance);
	MCP_SAVE(c, is_projective);

	c["eye"] = eye.asString();
	c["pointing"] = pointing.asString();
	c["up"] = up.asString();

	c["p_matrix"] = mrpt::containers::yaml::FromMatrix(p_matrix);
	c["v_matrix"] = mrpt::containers::yaml::FromMatrix(v_matrix);
	c["m_matrix"] = mrpt::containers::yaml::FromMatrix(m_matrix);
}

void TRenderMatrices::print(std::ostream& o) const
{
	mrpt::containers::yaml c;
	saveToYaml(c);
	o << c;
}
<commit_msg>FIX: invalid initialization of camera matrices<commit_after>/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2022, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include "opengl-precomp.h"	 // Precompiled header
//
#include <mrpt/containers/yaml.h>
#include <mrpt/math/geometry.h>	 // crossProduct3D()
#include <mrpt/math/ops_containers.h>  // dotProduct()
#include <mrpt/opengl/TRenderMatrices.h>

#include <Eigen/Dense>

using namespace mrpt::opengl;

void TRenderMatrices::computeOrthoProjectionMatrix(
	float left, float right, float bottom, float top, float znear, float zfar)
{
	ASSERT_GT_(zfar, znear);
	m_last_z_near = znear;
	m_last_z_far = zfar;

	p_matrix.setIdentity();

	p_matrix(0, 0) = 2.0f / (right - left);
	p_matrix(1, 1) = 2.0f / (top - bottom);
	p_matrix(2, 2) = -2.0f / (zfar - znear);
	p_matrix(0, 3) = -(right + left) / (right - left);
	p_matrix(1, 3) = -(top + bottom) / (top - bottom);
	p_matrix(2, 3) = -(zfar + znear) / (zfar - znear);
}

void TRenderMatrices::computeNoProjectionMatrix(float znear, float zfar)
{
	ASSERT_GT_(zfar, znear);
	m_last_z_near = znear;
	m_last_z_far = zfar;

	p_matrix.setIdentity();
	v_matrix.setIdentity();
}

// Replacement for obsolete: gluPerspective() and glOrtho()
void TRenderMatrices::computeProjectionMatrix(float znear, float zfar)
{
	ASSERT_GT_(FOV, .0f);
	ASSERT_GT_(zfar, znear);
	ASSERT_GT_(zfar, .0f);
	ASSERT_GE_(znear, .0f);

	m_last_z_near = znear;
	m_last_z_far = zfar;

	if (pinhole_model.has_value())
	{
		const auto& phm = pinhole_model.value();

		// Equivalent to gluPerspective(), from pinhole camera intrinsic
		// parameters (cx,cy,fx,fy):
		ASSERT_EQUAL_(viewport_width, pinhole_model->ncols);
		ASSERT_EQUAL_(viewport_height, pinhole_model->nrows);

		const int W = pinhole_model->ncols, H = pinhole_model->nrows;

		// See: e.g.
		// http://ksimek.github.io/2013/06/03/calibrated_cameras_in_opengl/
		mrpt::math::CMatrixFloat44 persp;
		persp.setZero();

		persp(0, 0) = phm.fx();
		persp(1, 1) = phm.fy();

		persp(0, 2) = -phm.cx();
		persp(1, 2) = -H + phm.cy();
		persp(2, 2) = (zfar + znear);
		persp(3, 2) = -1.0f;
		persp(2, 3) = zfar * znear;

		// glOrtho(-W/2, W/2, -H/2, H/2, near, far);

		computeOrthoProjectionMatrix(
			0, W, 0 /*bottom*/, H /*top*/, znear, zfar);

		// glMultMatrix(persp);
		p_matrix.asEigen() *= persp.asEigen();
	}
	else if (is_projective)
	{
		// Was: gluPerspective()
		// Based on GLM's perspective (MIT license).

		const float aspect = viewport_width / (1.0f * viewport_height);
		ASSERT_GT_(
			std::abs(aspect - std::numeric_limits<float>::epsilon()), .0f);

		const float f = 1.0f / std::tan(mrpt::DEG2RAD(FOV) / 2.0f);
		p_matrix.setZero();

		p_matrix(0, 0) = f / aspect;
		p_matrix(1, 1) = f;
		p_matrix(2, 2) = -(zfar + znear) / (zfar - znear);
		p_matrix(3, 2) = -1.0f;
		p_matrix(2, 3) = -(2.0f * zfar * znear) / (zfar - znear);
	}
	else
	{
		// Was:
		// glOrtho(-Ax, Ax, -Ay, Ay, -0.5 * m_clip_max, 0.5 * m_clip_max);

		const float ratio = viewport_width / (1.0f * viewport_height);
		float Ax = eyeDistance * 0.5f;
		float Ay = eyeDistance * 0.5f;

		if (ratio > 1) Ax *= ratio;
		else
		{
			if (ratio != 0) Ay /= ratio;
		}

		const auto left = -.5f * Ax, right = .5f * Ax;
		const auto bottom = -.5f * Ay, top = .5f * Ay;
		computeOrthoProjectionMatrix(left, right, bottom, top, znear, zfar);
	}
}

// Replacement for deprecated OpenGL gluLookAt():
void TRenderMatrices::applyLookAt()
{
	using mrpt::math::TVector3D;

	// Note: Use double instead of float to avoid numerical innacuracies that
	// are really noticeable with the naked eye when elevation is close to 90
	// deg (!)
	TVector3D forward = TVector3D(pointing - eye);
	const double fn = forward.norm();
	ASSERT_(fn != 0);
	forward *= 1.0 / fn;

	// Side = forward x up
	TVector3D side = mrpt::math::crossProduct3D(forward, up);
	const double sn = side.norm();
	ASSERT_(sn != 0);
	side *= 1.0 / sn;

	// Recompute up as: up = side x forward
	const TVector3D up2 = mrpt::math::crossProduct3D(side, forward);

	//  s.x   s.y   s.z  -dot(s, eye)
	//  u.x   u.y   u.z  -dot(u, eye)
	// -f.x  -f.y  -f.z  dot(up, eye)
	//   0     0     0      1

	mrpt::math::CMatrixFloat44 m(mrpt::math::UNINITIALIZED_MATRIX);
	// Axis X:
	m(0, 0) = d2f(side[0]);
	m(0, 1) = d2f(side[1]);
	m(0, 2) = d2f(side[2]);
	// Axis Y:
	m(1, 0) = d2f(up2[0]);
	m(1, 1) = d2f(up2[1]);
	m(1, 2) = d2f(up2[2]);
	// Axis Z:
	m(2, 0) = d2f(-forward[0]);
	m(2, 1) = d2f(-forward[1]);
	m(2, 2) = d2f(-forward[2]);
	// Translation:
	m(0, 3) = d2f(-mrpt::math::dotProduct<3, double>(side, eye));
	m(1, 3) = d2f(-mrpt::math::dotProduct<3, double>(up2, eye));
	m(2, 3) = d2f(mrpt::math::dotProduct<3, double>(forward, eye));
	// Last row:
	m(3, 0) = .0f;
	m(3, 1) = .0f;
	m(3, 2) = .0f;
	m(3, 3) = 1.f;

	// Homogeneous matrices composition:
	// Overwrite projection matrix:
	// p_matrix.asEigen() = p_matrix.asEigen() * m.asEigen();
	v_matrix = m;
}

void TRenderMatrices::projectPoint(
	float x, float y, float z, float& proj_u, float& proj_v,
	float& proj_z_depth) const
{
	const Eigen::Matrix<float, 4, 1, Eigen::ColMajor> proj =
		pmv_matrix.asEigen() *
		Eigen::Matrix<float, 4, 1, Eigen::ColMajor>(x, y, z, 1);
	proj_u = proj[3] ? proj[0] / proj[3] : 0;
	proj_v = proj[3] ? proj[1] / proj[3] : 0;
	proj_z_depth = proj[2];
}

void TRenderMatrices::projectPointPixels(
	float x, float y, float z, float& proj_u_px, float& proj_v_px,
	float& proj_depth) const
{
	projectPoint(x, y, z, proj_u_px, proj_v_px, proj_depth);
	proj_u_px = (proj_u_px + 1.0f) * (viewport_width * 0.5f);
	proj_v_px = (proj_v_px + 1.0f) * (viewport_height * 0.5f);
}

void TRenderMatrices::saveToYaml(mrpt::containers::yaml& c) const
{
	c = mrpt::containers::yaml::Map();

	MCP_SAVE(c, initialized);
	MCP_SAVE(c, viewport_width);
	MCP_SAVE(c, viewport_height);
	MCP_SAVE(c, FOV);
	MCP_SAVE_DEG(c, azimuth);
	MCP_SAVE_DEG(c, elev);
	MCP_SAVE(c, eyeDistance);
	MCP_SAVE(c, is_projective);

	c["eye"] = eye.asString();
	c["pointing"] = pointing.asString();
	c["up"] = up.asString();

	c["p_matrix"] = mrpt::containers::yaml::FromMatrix(p_matrix);
	c["v_matrix"] = mrpt::containers::yaml::FromMatrix(v_matrix);
	c["m_matrix"] = mrpt::containers::yaml::FromMatrix(m_matrix);
}

void TRenderMatrices::print(std::ostream& o) const
{
	mrpt::containers::yaml c;
	saveToYaml(c);
	o << c;
}
<|endoftext|>


<commit_before>/**
 * @file   DynamicPot.cpp
 * @brief  DynamicPot class implementation.
 * @author zer0
 * @date   2019-04-09
 */

#include <libtbag/tpot/details/DynamicPot.hpp>
#include <libtbag/debug/Assert.hpp>

#include <cstdlib>

// -------------------
NAMESPACE_LIBTBAG_OPEN
// -------------------

namespace tpot    {
namespace details {

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain0 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(0)
{
    _func = SharedFunc((FakeFunction*)new PotMain0(pot), [](FakeFunction * f){
        delete (PotMain0*)f;
    });
    checkParams();
}

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain1 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(1)
{
    _func = SharedFunc((FakeFunction*)new PotMain1(pot), [](FakeFunction * f){
        delete (PotMain1*)f;
    });
    checkParams();
}

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain2 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(2)
{
    _func = SharedFunc((FakeFunction*)new PotMain2(pot), [](FakeFunction * f){
        delete (PotMain2*)f;
    });
    checkParams();
}

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain3 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(3)
{
    _func = SharedFunc((FakeFunction*)new PotMain3(pot), [](FakeFunction * f){
        delete (PotMain3*)f;
    });
    checkParams();
}

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain4 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(4)
{
    _func = SharedFunc((FakeFunction*)new PotMain4(pot), [](FakeFunction * f){
        delete (PotMain4*)f;
    });
    checkParams();
}

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain5 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(5)
{
    _func = SharedFunc((FakeFunction*)new PotMain5(pot), [](FakeFunction * f){
        delete (PotMain5*)f;
    });
    checkParams();
}

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain6 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(6)
{
    _func = SharedFunc((FakeFunction*)new PotMain6(pot), [](FakeFunction * f){
        delete (PotMain6*)f;
    });
    checkParams();
}

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain7 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(7)
{
    _func = SharedFunc((FakeFunction*)new PotMain7(pot), [](FakeFunction * f){
        delete (PotMain7*)f;
    });
    checkParams();
}

DynamicPot::~DynamicPot()
{
    // EMPTY.
}

void DynamicPot::checkParams()
{
    assert(!NAME.empty());
    assert(!REMARKS.empty());
    assert(!HELP.empty());
    assert(0 <= COMPARE_AND(MAIN_TYPE) <= 7);
    assert(static_cast<bool>(_func));
}

char const * const DynamicPot::getName() const TBAG_NOEXCEPT
{
    return NAME.c_str();
}

char const * const DynamicPot::getRemarks() const TBAG_NOEXCEPT
{
    return REMARKS.c_str();
}

char const * const DynamicPot::getHelp() const TBAG_NOEXCEPT
{
    return HELP.c_str();
}

int DynamicPot::onMain()
{
    auto * fake_func = _func.get();
    assert(fake_func != nullptr);

    // clang-format off
    switch (MAIN_TYPE) {
    case 0:
        (*((PotMain0*)fake_func))();
        return EXIT_SUCCESS;
    case 1: return (*((PotMain1*)fake_func))();
    case 2: return (*((PotMain2*)fake_func))(params());
    case 3: return (*((PotMain3*)fake_func))(envs());
    case 4: return (*((PotMain4*)fake_func))(args());
    case 5: return (*((PotMain5*)fake_func))(values());
    case 6: return (*((PotMain6*)fake_func))(storage());
    case 7: return (*((PotMain7*)fake_func))(verbose());
    default:
        TBAG_INACCESSIBLE_BLOCK_ASSERT();
    }
    // clang-format on
    return EXIT_FAILURE;
}

} // namespace details
} // namespace tpot

// --------------------
NAMESPACE_LIBTBAG_CLOSE
// --------------------

<commit_msg>Update DynamicPot::checkParams() method.<commit_after>/**
 * @file   DynamicPot.cpp
 * @brief  DynamicPot class implementation.
 * @author zer0
 * @date   2019-04-09
 */

#include <libtbag/tpot/details/DynamicPot.hpp>
#include <libtbag/debug/Assert.hpp>

#include <cstdlib>

// -------------------
NAMESPACE_LIBTBAG_OPEN
// -------------------

namespace tpot    {
namespace details {

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain0 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(0)
{
    _func = SharedFunc((FakeFunction*)new PotMain0(pot), [](FakeFunction * f){
        delete (PotMain0*)f;
    });
    checkParams();
}

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain1 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(1)
{
    _func = SharedFunc((FakeFunction*)new PotMain1(pot), [](FakeFunction * f){
        delete (PotMain1*)f;
    });
    checkParams();
}

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain2 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(2)
{
    _func = SharedFunc((FakeFunction*)new PotMain2(pot), [](FakeFunction * f){
        delete (PotMain2*)f;
    });
    checkParams();
}

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain3 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(3)
{
    _func = SharedFunc((FakeFunction*)new PotMain3(pot), [](FakeFunction * f){
        delete (PotMain3*)f;
    });
    checkParams();
}

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain4 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(4)
{
    _func = SharedFunc((FakeFunction*)new PotMain4(pot), [](FakeFunction * f){
        delete (PotMain4*)f;
    });
    checkParams();
}

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain5 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(5)
{
    _func = SharedFunc((FakeFunction*)new PotMain5(pot), [](FakeFunction * f){
        delete (PotMain5*)f;
    });
    checkParams();
}

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain6 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(6)
{
    _func = SharedFunc((FakeFunction*)new PotMain6(pot), [](FakeFunction * f){
        delete (PotMain6*)f;
    });
    checkParams();
}

DynamicPot::DynamicPot(std::string const & n, std::string const & r, std::string const & h, PotMain7 const & pot)
        : NAME(n), REMARKS(r), HELP(h), MAIN_TYPE(7)
{
    _func = SharedFunc((FakeFunction*)new PotMain7(pot), [](FakeFunction * f){
        delete (PotMain7*)f;
    });
    checkParams();
}

DynamicPot::~DynamicPot()
{
    // EMPTY.
}

void DynamicPot::checkParams()
{
    assert(!NAME.empty());
    assert(0 <= COMPARE_AND(MAIN_TYPE) <= 7);
    assert(static_cast<bool>(_func));
}

char const * const DynamicPot::getName() const TBAG_NOEXCEPT
{
    return NAME.c_str();
}

char const * const DynamicPot::getRemarks() const TBAG_NOEXCEPT
{
    return REMARKS.c_str();
}

char const * const DynamicPot::getHelp() const TBAG_NOEXCEPT
{
    return HELP.c_str();
}

int DynamicPot::onMain()
{
    auto * fake_func = _func.get();
    assert(fake_func != nullptr);

    // clang-format off
    switch (MAIN_TYPE) {
    case 0:
        (*((PotMain0*)fake_func))();
        return EXIT_SUCCESS;
    case 1: return (*((PotMain1*)fake_func))();
    case 2: return (*((PotMain2*)fake_func))(params());
    case 3: return (*((PotMain3*)fake_func))(envs());
    case 4: return (*((PotMain4*)fake_func))(args());
    case 5: return (*((PotMain5*)fake_func))(values());
    case 6: return (*((PotMain6*)fake_func))(storage());
    case 7: return (*((PotMain7*)fake_func))(verbose());
    default:
        TBAG_INACCESSIBLE_BLOCK_ASSERT();
    }
    // clang-format on
    return EXIT_FAILURE;
}

} // namespace details
} // namespace tpot

// --------------------
NAMESPACE_LIBTBAG_CLOSE
// --------------------

<|endoftext|>


<commit_before>/******************************************************************************
 *                    _   _____   __________                                  *
 *                   | | / / _ | / __/_  __/     Visibility                   *
 *                   | |/ / __ |_\ \  / /          Across                     *
 *                   |___/_/ |_/___/ /_/       Space and Time                 *
 *                                                                            *
 * This file is part of VAST. It is subject to the license terms in the       *
 * LICENSE file found in the top-level directory of this distribution and at  *
 * http://vast.io/license. No part of VAST, including this file, may be       *
 * copied, modified, propagated, or distributed except according to the terms *
 * contained in the LICENSE file.                                             *
 ******************************************************************************/

#define SUITE table_index
#include "test.hpp"

#include "fixtures/events.hpp"
#include "fixtures/filesystem.hpp"

#include "vast/bitmap.hpp"
#include "vast/concept/parseable/to.hpp"
#include "vast/concept/parseable/vast/expression.hpp"
#include "vast/concept/printable/stream.hpp"
#include "vast/concept/printable/vast/error.hpp"
#include "vast/concept/printable/vast/event.hpp"
#include "vast/concept/printable/vast/expression.hpp"
#include "vast/system/table_index.hpp"

using namespace vast;
using namespace vast::system;

namespace {

struct fixture : fixtures::events, fixtures::filesystem {
  fixture() {
    directory /= "column-layout";
  }

  template <class T>
  T unbox(expected<T> x) {
    REQUIRE(x);
    return std::move(*x);
  }
};

} // namespace <anonymous>

FIXTURE_SCOPE(table_index_tests, fixture)

TEST(flat type) {
  // Some test data.
  std::vector<int> xs{1, 2, 3, 1, 2, 3, 1, 2, 3};
  MESSAGE("generate test queries");
  auto is1 = unbox(to<predicate>(":int == +1"));
  auto is2 = unbox(to<predicate>(":int == +2"));
  auto is3 = unbox(to<predicate>(":int == +3"));
  auto is4 = unbox(to<predicate>(":int == +4"));
  { // lifetime of the first table index
    auto tbl = unbox(make_table_index(directory, integer_type{}));
    MESSAGE("ingest integer values");
    for (size_t i = 0; i < xs.size(); ++i) {
      event x{xs[i]};
      x.id(i);
      tbl.add(std::move(x));
    }
    MESSAGE("verify table index");
    CHECK_EQUAL(unbox(tbl.lookup(is1)), make_ids({0, 3, 6}, xs.size()));
    CHECK_EQUAL(unbox(tbl.lookup(is2)), make_ids({1, 4, 7}, xs.size()));
    CHECK_EQUAL(unbox(tbl.lookup(is3)), make_ids({2, 5, 8}, xs.size()));
    CHECK_EQUAL(unbox(tbl.lookup(is4)), make_ids({}, xs.size()));
    MESSAGE("(automatically) persist table index to disk");
  }
  { // lifetime scope of the second table index
    MESSAGE("restore table index from disk");
    auto tbl = unbox(make_table_index(directory, integer_type{}));
    MESSAGE("verify table index again");
    CHECK_EQUAL(unbox(tbl.lookup(is1)), make_ids({0, 3, 6}, xs.size()));
    CHECK_EQUAL(unbox(tbl.lookup(is2)), make_ids({1, 4, 7}, xs.size()));
    CHECK_EQUAL(unbox(tbl.lookup(is3)), make_ids({2, 5, 8}, xs.size()));
    CHECK_EQUAL(unbox(tbl.lookup(is4)), make_ids({}, xs.size()));
  }
}

TEST(record type) {
  auto tbl_type = record_type{
    {"x", record_type{
      {"a", integer_type{}},
      {"b", boolean_type{}}
    }},
    {"y", record_type{
      {"a", string_type{}}
    }}
  };
  auto mk_row = [&](int x, bool y, std::string z) {
    return value::make(vector{vector{x, y}, vector{std::move(z)}}, tbl_type);
  };
  // Some test data.
  std::vector<value> xs{mk_row(1, true, "abc"),    mk_row(10, false, "def"),
                        mk_row(5, true, "hello"),  mk_row(1, true, "d e f"),
                        mk_row(15, true, "world"), mk_row(5, true, "bar"),
                        mk_row(10, true, "a b c"), mk_row(10, true, "baz"),
                        mk_row(5, true, "foo"),    mk_row(1, true, "test")};
  MESSAGE(caf::deep_to_string(xs));
  MESSAGE("generate test queries");
  auto x_a_is1 = unbox(to<predicate>("x.a == +1"));
  { // lifetime of the first table index
    auto tbl = unbox(make_table_index(directory, tbl_type));
    CHECK_EQUAL(tbl.num_data_columns(), 3u);
    MESSAGE("ingest table values");
    for (size_t i = 0; i < xs.size(); ++i) {
      event x{xs[i]};
      x.id(i);
      MESSAGE("ingest" <<  caf::deep_to_string(x));
      tbl.add(std::move(x));
    }
    MESSAGE("verify table index");
    CHECK_EQUAL(unbox(tbl.lookup(x_a_is1)), make_ids({0, 3, 9}, xs.size()));
    MESSAGE("(automatically) persist table index to disk");
  }
  { // lifetime scope of the second table index
    MESSAGE("restore table index from disk");
    auto tbl = unbox(make_table_index(directory, tbl_type));
    MESSAGE("verify table index again");
    CHECK_EQUAL(unbox(tbl.lookup(x_a_is1)), make_ids({0, 3, 9}, xs.size()));
  }
}

TEST(bro conn logs) {
  auto pred = unbox(to<predicate>("id.resp_p == 995/?"));
  const auto tbl_type = bro_conn_log[0].type();
  { // lifetime of the first table index
    MESSAGE("generate column layout for bro conn logs");
    auto tbl = unbox(make_table_index(directory, tbl_type));
    CHECK_EQUAL(tbl.num_meta_columns(), 2u);
    MESSAGE("ingesting events");
    for (auto& entry : bro_conn_log) {
      auto err = tbl.add(entry);
      if (err) {
        FAIL("error during ingestion: " << caf::to_string(err));
      }
    }
    MESSAGE("verify table index");
    auto result = unbox(tbl.lookup(pred));
    CHECK_EQUAL(rank(result), 53u);
    auto check_uid = [](const event& e, const std::string& uid) {
      auto& v = get<vector>(e.data());
      return v[1] == uid;
    };
    for (auto i : select(result))
      if (i == 819)
        CHECK(check_uid(bro_conn_log[819], "KKSlmtmkkxf")); // first
      else if (i == 3594)
        CHECK(check_uid(bro_conn_log[3594], "GDzpFiROJQi")); // intermediate
      else if (i == 6338)
        CHECK(check_uid(bro_conn_log[6338], "zwCckCCgXDb")); // last
    MESSAGE("(automatically) persist table index to disk");
  }
  { // lifetime scope of the second table index
    MESSAGE("restore table index from disk");
    auto tbl = unbox(make_table_index(directory, tbl_type));
    MESSAGE("verify table index again");
    auto result = unbox(tbl.lookup(pred));
    CHECK_EQUAL(rank(result), 53u);
    auto check_uid = [](const event& e, const std::string& uid) {
      auto& v = get<vector>(e.data());
      return v[1] == uid;
    };
    for (auto i : select(result))
      if (i == 819)
        CHECK(check_uid(bro_conn_log[819], "KKSlmtmkkxf")); // first
      else if (i == 3594)
        CHECK(check_uid(bro_conn_log[3594], "GDzpFiROJQi")); // intermediate
      else if (i == 6338)
        CHECK(check_uid(bro_conn_log[6338], "zwCckCCgXDb")); // last
  }
}

FIXTURE_SCOPE_END()
<commit_msg>Streamline and improve unit test<commit_after>/******************************************************************************
 *                    _   _____   __________                                  *
 *                   | | / / _ | / __/_  __/     Visibility                   *
 *                   | |/ / __ |_\ \  / /          Across                     *
 *                   |___/_/ |_/___/ /_/       Space and Time                 *
 *                                                                            *
 * This file is part of VAST. It is subject to the license terms in the       *
 * LICENSE file found in the top-level directory of this distribution and at  *
 * http://vast.io/license. No part of VAST, including this file, may be       *
 * copied, modified, propagated, or distributed except according to the terms *
 * contained in the LICENSE file.                                             *
 ******************************************************************************/

#define SUITE table_index
#include "test.hpp"

#include "fixtures/events.hpp"
#include "fixtures/filesystem.hpp"

#include "vast/bitmap.hpp"
#include "vast/concept/parseable/to.hpp"
#include "vast/concept/parseable/vast/expression.hpp"
#include "vast/concept/printable/stream.hpp"
#include "vast/concept/printable/vast/error.hpp"
#include "vast/concept/printable/vast/event.hpp"
#include "vast/concept/printable/vast/expression.hpp"
#include "vast/system/table_index.hpp"

using namespace vast;
using namespace vast::system;

namespace {

struct fixture : fixtures::events, fixtures::filesystem {
  template <class T>
  T unbox(expected<T> x) {
    if (!x)
      FAIL("error: " << x.error());
    return std::move(*x);
  }

  ids query(std::string_view what) {
    return unbox(tbl->lookup(unbox(to<expression>(what))));
  }

  void reset(table_index&& new_tbl) {
    tbl = std::make_unique<table_index>(std::move(new_tbl));
  }

  void reset(expected<table_index>&& new_tbl) {
    if (!new_tbl)
      FAIL("error: " << new_tbl.error());
    reset(std::move(*new_tbl));
  }

  void add(event x) {
    tbl->add(std::move(x));
  }

  std::unique_ptr<table_index> tbl;
};

} // namespace <anonymous>

FIXTURE_SCOPE(table_index_tests, fixture)

TEST(flat type) {
  MESSAGE("generate table layout for flat integer type");
  reset(make_table_index(directory, integer_type{}));
  MESSAGE("ingest test data (integers)");
  std::vector<int> xs{1, 2, 3, 1, 2, 3, 1, 2, 3};
  for (size_t i = 0; i < xs.size(); ++i) {
    event x{xs[i]};
    x.id(i);
    add(std::move(x));
  }
  auto res = [&](auto... args) {
    return make_ids({args...}, xs.size());
  };
  MESSAGE("verify table index");
  CHECK_EQUAL(query(":int == +1"), res(0, 3, 6));
  CHECK_EQUAL(query(":int == +2"), res(1, 4, 7));
  CHECK_EQUAL(query(":int == +3"), res(2, 5, 8));
  CHECK_EQUAL(query(":int == +4"), res());
  CHECK_EQUAL(query(":int != +1"), res(1, 2, 4, 5, 7, 8));
  CHECK_EQUAL(query("!(:int == +1)"), res(1, 2, 4, 5, 7, 8));
  CHECK_EQUAL(query(":int > +1 && :int < +3"), res(1, 4, 7));
  MESSAGE("(automatically) persist table index and restore from disk");
  reset(make_table_index(directory, integer_type{}));
  MESSAGE("verify table index again");
  CHECK_EQUAL(query(":int == +1"), res(0, 3, 6));
  CHECK_EQUAL(query(":int == +2"), res(1, 4, 7));
  CHECK_EQUAL(query(":int == +3"), res(2, 5, 8));
  CHECK_EQUAL(query(":int == +4"), res());
  CHECK_EQUAL(query(":int != +1"), res(1, 2, 4, 5, 7, 8));
  CHECK_EQUAL(query("!(:int == +1)"), res(1, 2, 4, 5, 7, 8));
  CHECK_EQUAL(query(":int > +1 && :int < +3"), res(1, 4, 7));
}

TEST(record type) {
  MESSAGE("generate table layout for record type");
  auto tbl_type = record_type{
    {"x", record_type{
      {"a", integer_type{}},
      {"b", boolean_type{}}
    }},
    {"y", record_type{
      {"a", string_type{}}
    }}
  };
  reset(make_table_index(directory, tbl_type));
  MESSAGE("ingest test data (records)");
  auto mk_row = [&](int x, bool y, std::string z) {
    return value::make(vector{vector{x, y}, vector{std::move(z)}}, tbl_type);
  };
  // Some test data.
  std::vector<value> xs{mk_row(1, true, "abc"),     mk_row(10, false, "def"),
                        mk_row(5, true, "hello"),   mk_row(1, true, "d e f"),
                        mk_row(15, true, "world"),  mk_row(5, true, "bar"),
                        mk_row(10, false, "a b c"), mk_row(10, false, "baz"),
                        mk_row(5, false, "foo"),    mk_row(1, true, "test")};
  for (size_t i = 0; i < xs.size(); ++i) {
    event x{xs[i]};
    x.id(i);
    add(std::move(x));
  }
  auto res = [&](auto... args) {
    return make_ids({args...}, xs.size());
  };
  MESSAGE("verify table index");
  CHECK_EQUAL(query("x.a == +1"), res(0, 3, 9));
  CHECK_EQUAL(query("x.a > +1"), res(1, 2, 4, 5, 6, 7, 8));
  CHECK_EQUAL(query("x.a > +1 && x.b == T"), res(2, 4, 5));
  MESSAGE("(automatically) persist table index and restore from disk");
  reset(make_table_index(directory, tbl_type));
  MESSAGE("verify table index again");
  CHECK_EQUAL(query("x.a == +1"), res(0, 3, 9));
  CHECK_EQUAL(query("x.a > +1"), res(1, 2, 4, 5, 6, 7, 8));
  CHECK_EQUAL(query("x.a > +1 && x.b == T"), res(2, 4, 5));
}

TEST(bro conn logs) {
  MESSAGE("generate table layout for bro conn logs");
  auto tbl_type = bro_conn_log[0].type();
  reset(make_table_index(directory, tbl_type));
  CHECK_EQUAL(tbl->num_meta_columns(), 2u);
  MESSAGE("ingest test data (bro conn log)");
  for (auto& entry : bro_conn_log) {
    add(entry);
  }
  MESSAGE("verify table index");
  CHECK_EQUAL(rank(query("id.resp_p == 995/?")), 53u);
  CHECK_EQUAL(rank(query("id.resp_p == 5355/?")), 49u);
  CHECK_EQUAL(rank(query("id.resp_p == 995/? || id.resp_p == 5355/?")), 102u);
  CHECK_EQUAL(rank(query("&time > 1970-01-01")), bro_conn_log.size());
  CHECK_EQUAL(rank(query("proto == \"udp\"")), 5306u);
  CHECK_EQUAL(rank(query("proto == \"tcp\"")), 3135u);
  CHECK_EQUAL(rank(query("uid == \"nkCxlvNN8pi\"")), 1u);
  CHECK_EQUAL(rank(query("orig_bytes < 400")), 5332u);
  CHECK_EQUAL(rank(query("orig_bytes < 400 && proto == \"udp\"")), 4357u);
  MESSAGE("(automatically) persist table index and restore from disk");
  reset(make_table_index(directory, tbl_type));
  MESSAGE("verify table index again");
  CHECK_EQUAL(rank(query("id.resp_p == 995/?")), 53u);
  CHECK_EQUAL(rank(query("id.resp_p == 5355/?")), 49u);
  CHECK_EQUAL(rank(query("id.resp_p == 995/? || id.resp_p == 5355/?")), 102u);
  CHECK_EQUAL(rank(query("&time > 1970-01-01")), bro_conn_log.size());
  CHECK_EQUAL(rank(query("proto == \"udp\"")), 5306u);
  CHECK_EQUAL(rank(query("proto == \"tcp\"")), 3135u);
  CHECK_EQUAL(rank(query("uid == \"nkCxlvNN8pi\"")), 1u);
  CHECK_EQUAL(rank(query("orig_bytes < 400")), 5332u);
  CHECK_EQUAL(rank(query("orig_bytes < 400 && proto == \"udp\"")), 4357u);
}

FIXTURE_SCOPE_END()
<|endoftext|>


<commit_before>#include "view.h"
#include "util/tileID.h"
#include "platform.h"
#include "glm/gtx/string_cast.hpp"

constexpr float View::s_maxZoom; // Create a stack reference to the static member variable

View::View(int _width, int _height, ProjectionType _projType) {

    setMapProjection(_projType);
    setSize(_width, _height);
    setZoom(m_initZoom); // Arbitrary zoom for testing

    setPosition(0.0, 0.0);

    m_changed = false;
    m_dirty = true;

}

void View::setMapProjection(ProjectionType _projType) {

    switch(_projType) {
        case ProjectionType::mercator:
            m_projection.reset(new MercatorProjection());
            break;
        default:
            logMsg("Error: not a valid map projection specified.\n Setting map projection to mercator by default");
            m_projection.reset(new MercatorProjection());
            break;
    }

    m_dirty = true;

}

const MapProjection& View::getMapProjection() {
    return *m_projection.get();
}

void View::setPixelScale(float _pixelsPerPoint) {

    m_pixelScale = _pixelsPerPoint;
    m_dirty = true;

}

void View::setSize(int _width, int _height) {

    m_vpWidth = _width;
    m_vpHeight = _height;
    m_aspect = (float)_width / (float)_height;
    m_dirty = true;

}

void View::setPosition(double _x, double _y) {

    m_pos.x = _x;
    m_pos.y = _y;
    m_dirty = true;

}

void View::setZoom(float _z) {

    // ensure zoom value is allowed
    m_zoom = glm::clamp(_z, 0.0f, s_maxZoom);
    m_dirty = true;

}

void View::translate(double _dx, double _dy) {

    setPosition(m_pos.x + _dx, m_pos.y + _dy);

}

void View::zoom(float _dz) {
    if( ( (m_zoom + _dz - m_initZoom) > 0.0 )) {
        m_isZoomIn = true;
    }
    else {
        m_isZoomIn = false;
    }
    setZoom(m_zoom + _dz);

}

void View::update() {

    if (!m_dirty) {
        return;
    }

    updateMatrices();

    updateTiles();

    m_dirty = false;

    m_changed = true;

}

const glm::dmat4 View::getViewProjectionMatrix() {

    return m_proj * m_view;

}

glm::dmat2 View::getBoundsRect() const {

    double hw = m_width * 0.5;
    double hh = m_height * 0.5;
    return glm::dmat2(m_pos.x - hw, m_pos.y - hh, m_pos.x + hw, m_pos.y + hh);

}

float View::toWorldDistance(float _screenDistance) const {
    float metersPerTile = 2 * MapProjection::HALF_CIRCUMFERENCE * pow(2, -m_zoom);
    return _screenDistance * metersPerTile / (m_pixelScale * m_pixelsPerTile);
}

const std::set<TileID>& View::getVisibleTiles() {

    return m_visibleTiles;

}

bool View::changedSinceLastCheck() {

    if (m_changed) {
        m_changed = false;
        return true;
    }
    return false;

}

void View::updateMatrices() {

    // find dimensions of tiles in world space at new zoom level
    float worldTileSize = 2 * MapProjection::HALF_CIRCUMFERENCE * pow(2, -m_zoom);

    // viewport height in world space is such that each tile is [m_pixelsPerTile] px square in screen space
    float screenTileSize = m_pixelsPerTile * m_pixelScale;
    m_height = (float)m_vpHeight * worldTileSize / screenTileSize;
    m_width = m_height * m_aspect;

    // set vertical field-of-view
    double fovy = PI * 0.5;

    // we assume portrait orientation by default, so in landscape
    // mode we scale the vertical FOV such that the wider dimension
    // gets the intended FOV
    if (m_width > m_height) {
        fovy /= m_aspect;
    }

    // set camera z to produce desired viewable area
    m_pos.z = m_height * 0.5 / tan(fovy * 0.5);

    // set near clipping distance as a function of camera z
    // TODO: this is a simple heuristic that deserves more thought
    double near = m_pos.z / 50.0;

    // update view and projection matrices
    m_view = glm::lookAt(m_pos, m_pos + glm::dvec3(0, 0, -1), glm::dvec3(0, 1, 0));
    m_proj = glm::perspective(fovy, double(m_aspect), near, m_pos.z + 1.0);

}

void View::updateTiles() {

    m_visibleTiles.clear();

    float tileSize = 2 * MapProjection::HALF_CIRCUMFERENCE * pow(2, -(int)m_zoom);
    float invTileSize = 1.0 / tileSize;

    float vpLeftEdge = m_pos.x - m_width * 0.5 + MapProjection::HALF_CIRCUMFERENCE;
    float vpRightEdge = vpLeftEdge + m_width;
    float vpBottomEdge = -m_pos.y - m_height * 0.5 + MapProjection::HALF_CIRCUMFERENCE;
    float vpTopEdge = vpBottomEdge + m_height;

    int tileX = (int) fmax(0, vpLeftEdge * invTileSize);
    int tileY = (int) fmax(0, vpBottomEdge * invTileSize);

    float x = tileX * tileSize;
    float y = tileY * tileSize;

    int maxTileIndex = pow(2, m_zoom);

    while (x < vpRightEdge && tileX < maxTileIndex) {

        while (y < vpTopEdge && tileY < maxTileIndex) {

            m_visibleTiles.insert(TileID(tileX, tileY, m_zoom));

            tileY++;
            y += tileSize;

        }

        tileY = (int) vpBottomEdge * invTileSize;
        y = tileY * tileSize;

        tileX++;
        x += tileSize;
    }

}
<commit_msg>fixed bug: determining zoomin/out<commit_after>#include "view.h"
#include "util/tileID.h"
#include "platform.h"
#include "glm/gtx/string_cast.hpp"

constexpr float View::s_maxZoom; // Create a stack reference to the static member variable

View::View(int _width, int _height, ProjectionType _projType) {

    setMapProjection(_projType);
    setSize(_width, _height);
    setZoom(m_initZoom); // Arbitrary zoom for testing

    setPosition(0.0, 0.0);

    m_changed = false;
    m_dirty = true;

}

void View::setMapProjection(ProjectionType _projType) {

    switch(_projType) {
        case ProjectionType::mercator:
            m_projection.reset(new MercatorProjection());
            break;
        default:
            logMsg("Error: not a valid map projection specified.\n Setting map projection to mercator by default");
            m_projection.reset(new MercatorProjection());
            break;
    }

    m_dirty = true;

}

const MapProjection& View::getMapProjection() {
    return *m_projection.get();
}

void View::setPixelScale(float _pixelsPerPoint) {

    m_pixelScale = _pixelsPerPoint;
    m_dirty = true;

}

void View::setSize(int _width, int _height) {

    m_vpWidth = _width;
    m_vpHeight = _height;
    m_aspect = (float)_width / (float)_height;
    m_dirty = true;

}

void View::setPosition(double _x, double _y) {

    m_pos.x = _x;
    m_pos.y = _y;
    m_dirty = true;

}

void View::setZoom(float _z) {

    // ensure zoom value is allowed
    m_zoom = glm::clamp(_z, 0.0f, s_maxZoom);
    m_dirty = true;

}

void View::translate(double _dx, double _dy) {

    setPosition(m_pos.x + _dx, m_pos.y + _dy);

}

void View::zoom(float _dz) {
    if(_dz > 0.0) {
        m_isZoomIn = true;
    }
    else {
        m_isZoomIn = false;
    }
    setZoom(m_zoom + _dz);

}

void View::update() {

    if (!m_dirty) {
        return;
    }

    updateMatrices();

    updateTiles();

    m_dirty = false;

    m_changed = true;

}

const glm::dmat4 View::getViewProjectionMatrix() {

    return m_proj * m_view;

}

glm::dmat2 View::getBoundsRect() const {

    double hw = m_width * 0.5;
    double hh = m_height * 0.5;
    return glm::dmat2(m_pos.x - hw, m_pos.y - hh, m_pos.x + hw, m_pos.y + hh);

}

float View::toWorldDistance(float _screenDistance) const {
    float metersPerTile = 2 * MapProjection::HALF_CIRCUMFERENCE * pow(2, -m_zoom);
    return _screenDistance * metersPerTile / (m_pixelScale * m_pixelsPerTile);
}

const std::set<TileID>& View::getVisibleTiles() {

    return m_visibleTiles;

}

bool View::changedSinceLastCheck() {

    if (m_changed) {
        m_changed = false;
        return true;
    }
    return false;

}

void View::updateMatrices() {

    // find dimensions of tiles in world space at new zoom level
    float worldTileSize = 2 * MapProjection::HALF_CIRCUMFERENCE * pow(2, -m_zoom);

    // viewport height in world space is such that each tile is [m_pixelsPerTile] px square in screen space
    float screenTileSize = m_pixelsPerTile * m_pixelScale;
    m_height = (float)m_vpHeight * worldTileSize / screenTileSize;
    m_width = m_height * m_aspect;

    // set vertical field-of-view
    double fovy = PI * 0.5;

    // we assume portrait orientation by default, so in landscape
    // mode we scale the vertical FOV such that the wider dimension
    // gets the intended FOV
    if (m_width > m_height) {
        fovy /= m_aspect;
    }

    // set camera z to produce desired viewable area
    m_pos.z = m_height * 0.5 / tan(fovy * 0.5);

    // set near clipping distance as a function of camera z
    // TODO: this is a simple heuristic that deserves more thought
    double near = m_pos.z / 50.0;

    // update view and projection matrices
    m_view = glm::lookAt(m_pos, m_pos + glm::dvec3(0, 0, -1), glm::dvec3(0, 1, 0));
    m_proj = glm::perspective(fovy, double(m_aspect), near, m_pos.z + 1.0);

}

void View::updateTiles() {

    m_visibleTiles.clear();

    float tileSize = 2 * MapProjection::HALF_CIRCUMFERENCE * pow(2, -(int)m_zoom);
    float invTileSize = 1.0 / tileSize;

    float vpLeftEdge = m_pos.x - m_width * 0.5 + MapProjection::HALF_CIRCUMFERENCE;
    float vpRightEdge = vpLeftEdge + m_width;
    float vpBottomEdge = -m_pos.y - m_height * 0.5 + MapProjection::HALF_CIRCUMFERENCE;
    float vpTopEdge = vpBottomEdge + m_height;

    int tileX = (int) fmax(0, vpLeftEdge * invTileSize);
    int tileY = (int) fmax(0, vpBottomEdge * invTileSize);

    float x = tileX * tileSize;
    float y = tileY * tileSize;

    int maxTileIndex = pow(2, m_zoom);

    while (x < vpRightEdge && tileX < maxTileIndex) {

        while (y < vpTopEdge && tileY < maxTileIndex) {

            m_visibleTiles.insert(TileID(tileX, tileY, m_zoom));

            tileY++;
            y += tileSize;

        }

        tileY = (int) vpBottomEdge * invTileSize;
        y = tileY * tileSize;

        tileX++;
        x += tileSize;
    }

}
<|endoftext|>