\documentclass[10pt, handout]{beamer}

\usetheme{CambridgeUS}
\usecolortheme{rose}
\usepackage[polish]{babel}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{listings}
\usepackage{wasysym}
\lstset{language=C++,
	basicstyle=\ttfamily,
	keywordstyle=\color{blue}\ttfamily,
	stringstyle=\color{red}\ttfamily,
	commentstyle=\color{magenta}\ttfamily,
}

\title{Totalna Kaflacja, czyli teren w grach 2D}
\author{Bartosz Szreder}
\date{}

\begin{document}

\frame{\maketitle}

\begin{frame}{Życie jest nobelon}
	\begin{block}{}
		W BTechu teren podzielony jest na hexy i~występuje w~5 typach:
		\textit{clear}, \textit{rough}, \textit{water}, \textit{light woods}, \textit{heavy woods}. \\\

		\pause
		Ponadto teren może być na~różnych wysokościach (głębokościach), ale tym się nie~przejmujemy na~razie.
	\end{block}

	\pause
	Stan obecny: bleeding edge state-of-the-art technology, czyli jednokolorowe hexy.
	Jesteśmy bardziej retro i~vintage niż gry na~Atari 65 XL/XE.
\end{frame}

\begin{frame}{Kontekst}
	BTech jest silnie podzielony na~część logiczną i~graficzną. Logika nie~wie niczego o~grafice i~tak ma~zostać.
	\begin{itemize}
		\pause
		\item Klasa \texttt{Map} i~\texttt{GraphicsMap} dziedzicząca po \texttt{Map}.
		\pause
		\item Klasa \texttt{Hex} i~\texttt{GraphicsHex} zawierająca wskaźnik na \texttt{Hex}, który reprezentuje.
		\pause
		\item Gdzieś obok tego klasa \texttt{Grid} jako kolekcja \texttt{Hex}ów, ale~wiedząca o~istnieniu \texttt{GraphicsHexów} i~inicjująca je (pozycja na~scenie).
		\pause
		\item Jest jeszcze \texttt{GridGraphicsObject} zawierający wskaźnik do~\texttt{GraphicsHex}, ale jeszcze nie~wiem do~czego służy\ldots
	\end{itemize}
\end{frame}

\begin{frame}[fragile]{Rysowanie}
	\begin{lstlisting}
	void GraphicsHex::paint(...)
	{
	    painter->setBrush(hex->getTerrain());
	    painter->setPen(Qt::NoPen);
	    painter->drawPath(shape());
	    ...
	}
	\end{lstlisting}
\end{frame}

\begin{frame}{Pobożne życzenia}
	Chcemy:
	\begin{itemize}
		\pause
		\item rysować jakieś ładne teksturki,
		\pause
		\item wspierać wiele różnych teksturek dla jednego rodzaju terenu, coby monotonnie nie było,
		\pause
		\item rozsądnie obsługiwać ścieżki do~obrazków w~systemie plików, żeby ładowanie zasobów nie~było zależne od~bieżącego katalogu w~momencie odpalenia programu,
		\pause
		\item w~miarę możliwości nie~popsuć rozgraniczenia między logiką a~grafiką.
		\pause
		\item Bonus \#1: wspierać animowane tekstury.
		\pause
		\item Bonus \#2: wspierać tekstury udające trzeci wymiar (np. ,,wystające'' z~powierzchni mapy drzewa).
	\end{itemize}
\end{frame}

\begin{frame}{The Good}
	Dodajemy klasę \texttt{Tile} (\textit{kafel}), będącą lekkim opakowaniem na~\texttt{QImage}. Trzymamy w~środku:
	\begin{itemize}
		\pause
		\item \texttt{QVector <QImage> frames} -- kolejne ,,klatki'' animowanego kafla. Nieanimowany ma~tylko jedną klatkę.
		\pause
		\item Nic więcej! Dodatkowe informacje o~samym pliku graficznym (np. ścieżka) znajdują~się gdzie indziej.
	\end{itemize}

	\pause
	\begin{block}{}
		Warto zdecydować~się na~ustalony rozmiar pojedynczego kafla, np. $64 \times 64$,  $256 \times 256$ etc. [/personal opinion] \\\ \\
		\pause
		Wtedy wieloklatkowe tekstury rozpoznajemy po~tym, że są~rozmiaru $N \times kN$.
	\end{block}

	\pause
	Ponadto dodajemy do~\texttt{GraphicsHex} atrybut \texttt{const Tile *tile}.
\end{frame}

\begin{frame}[fragile]{Rysowanie lepiej}
	\begin{lstlisting}
	void GraphicsHex::paint(...)
	{
	    painter->setBrush(hex->getTerrain());
	    painter->setPen(Qt::NoPen);
	    painter->drawPath(shape());
	    if (tile != nullptr)
	        painter->drawImage(QRect(...),
	                           tile->currentFrame());
	    ...
	}
	\end{lstlisting}

	\pause
	Metoda \texttt{currentFrame()} zwraca właściwą klatę w~oparciu o~globalny licznik połączony z~timerem.
\end{frame}

\begin{frame}[fragile]{Ładowanie zasobów z~systemu plików -- RFC}
	\begin{itemize}
		\item Jest taka przydatna metoda (statyczna): \texttt{QCoreApplication::applicationDirPath()}.
		\pause
		\item Ustalamy w~projekcie położenie różnych zasobów (np. podkatalog \texttt{data/tiles/})
		i~ładujemy je~korzystajac z~powyższej metody.
		\pause
		\item Trzeba uważać na~ścieżki bezwzględne. Zrobiłem sobie taką pomocniczą funkcyjkę:
	\end{itemize}
	\pause
	\begin{lstlisting}
	QString resolvePath(const QString &path)
	{
	    if (QDir::isAbsolutePath(path))
	        return path;
	    return QCoreApplication::applicationDirPath()
	               + '/' + path;
	}
	\end{lstlisting}
\end{frame}

\begin{frame}{install target smaczny i~zdrowy}
	\begin{itemize}
		\item Trzeba jeszcze upewnić~się, że~zasoby są tam, gdzie być powinny.
		\pause
		\item Dodajemy do~\texttt{CMakeLists.txt}: \\
			\begin{center}\texttt{install (DIRECTORY data DESTINATION "\$\{BTech\_BINARY\_DIR\}"})\end{center}
		\pause
		\item \texttt{make \&\& make install}
	\end{itemize}
\end{frame}

\begin{frame}[fragile]
	\begin{lstlisting}
	QDir tileDir(BTech::resolvePath(...));
	QFileInfoList imgFileList
	    = tileDir.entryInfoList({"*.png"},
	                  QDir::Files | QDir::Readable);

	for (QFileInfo imgFile : imgFileList) {
	    const Tile *tile =
	            TileManager::registerTile(imgFile);
	    if (tile != nullptr)
	        tiles_.append(tile);
	}
	\end{lstlisting}
\end{frame}

\begin{frame}{The Bad -- \texttt{TileManager}}
	Klasa (singleton) skupiająca w~sobie informacje o:
	\begin{itemize}
		\pause
		\item wszystkich plikach graficznych (na~potrzeby edytora map),
		\pause
		\item pamiętaniu które pole ma~który kafel (na~potrzeby serializacji).
	\end{itemize}
\end{frame}

\begin{frame}{Więcej mapowań się nie dało?}
	\begin{itemize}
		\item \texttt{QHash <QString, const Tile *> pathToTile;}
		\item \texttt{QHash <const Tile *, QFileInfo> tileToFileInfo;}
		\item \texttt{QVector <Tile *> tiles;}
		\item \texttt{QHash <QPair <int, int>, const Tile *> coordToTile;}
	\end{itemize}
\end{frame}

\begin{frame}{Międzymordzie}
	\begin{itemize}
		\item \texttt{static QString fileName(const Tile *tile);}
		\item \texttt{static void loadTileDictionary(QDataStream \&in, const QVector <Hex *> \&hexes);}
		\item \texttt{static const Tile * registerTile(const QFileInfo \&tileFile);}
		\item \texttt{static void saveTileDictionary(QDataStream \&out, const QVector <Hex *> \&hexes);}
		\item \texttt{static const Tile * tile(QPair <int, int> hexCoord);}
	\end{itemize}
\end{frame}

\begin{frame}[fragile]{Ładowanie jednego obrazka}
	\begin{lstlisting}
		const Tile * TileManager::loadTileImage
		            (const QString &filePath)
		{
		    QImage image(filePath);
		    if (!image.isNull()
		            && image.height() == Tile::TileSize
		            && image.width() % Tile::TileSize == 0) {
		        Tile *result = new Tile(image);
		        tiles.append(result);
		        pathToTile[filePath] = result;
		        tileToFileInfo[result] = filePath;
		        return result;
		    }

		    return nullptr;
		}
	\end{lstlisting}
\end{frame}

\begin{frame}[fragile]{Przyszedł raz Breżniew w~dzień do~fryzjera}
	\begin{lstlisting}
	const Tile * TileManager::registerTile
	            (const QFileInfo &tileFile)
	{
	    TileManager &manager = instance();

	    const QString filePath = tileFile.filePath();
	    if (manager.pathToTile.contains(filePath))
	        return manager.pathToTile[filePath];

	    return manager.loadTileImage(filePath);
	}
	\end{lstlisting}
\end{frame}

\begin{frame}
	Zapisując mapę, dopisujemy na~jej koniec:
	\begin{itemize}
		\pause
		\item Słownik \texttt{UID}$\to$\texttt{QString path}, zawierajacy tylko obrazki używane na~danej mapie.
		\texttt{UID}y generujemy w~locie.
		\pause
		\item Odwzorowanie ze~współrzędnej \texttt{Hex}a do~\texttt{UID} kafla (dla \texttt{Hex}ów posiadających kafle różne od \texttt{nullptr}).
	\end{itemize}
\end{frame}

\begin{frame}[fragile]
	\begin{lstlisting}
	void TileManager::saveTileDictionary(QDataStream &out,
	        const QVector <Hex *> &hexes)
	{
	    TileManager &manager = instance();

	    UID tileCnt = EmptyUid;
	    QHash <const Tile *, UID> tileToId;
	    QHash <UID, QString> tileDict;

	    manager.coordToTile.clear();
	    for (const Hex *hex : hexes) {
	        const GraphicsHex *graphicsHex
	                    = GraphicsFactory::get(hex);
	        const Tile *tile = graphicsHex->getTile();
\end{lstlisting}
\end{frame}

\begin{frame}[fragile]
	\begin{lstlisting}
	if (tile != nullptr) {
	    QPoint hexCoord = hex->getPoint();
	    UID tileUid;
	    if (tileToId.contains(tile)) {
	        tileUid = tileToId[tile];
	    } else {
	        tileUid = ++tileCnt;
	        tileToId[tile] = tileUid;
	    }

	    manager.coordToTile[
	        qMakePair(hexCoord.x(), hexCoord.y())] = tile;
	    tileDict[tileUid] =
	        manager.tileToFileInfo[tile].filePath();
	}
	\end{lstlisting}
\end{frame}

\begin{frame}[fragile]
	\begin{lstlisting}
	QHash <QPair <int, int>, UID> coordToTileUid;
	for (auto i = manager.coordToTile.constBegin();
	          i != manager.coordToTile.constEnd(); ++i)
	    coordToTileUid[i.key()] = tileToId[i.value()];
	out << tileDict << coordToTileUid;
	\end{lstlisting}
\end{frame}

\begin{frame}[fragile]{Modrzew}
	\begin{lstlisting}
	void TileManager::loadTileDictionary(QDataStream &in,
	        const QVector <Hex *> &hexes)
	{
	    TileManager &manager = instance();

	    QHash <UID, QString> tileDict;
	    QHash <UID, const Tile *> idToTile;
	    QHash <QPair <int, int>, UID> coordToTileUid;
	    in >> tileDict >> coordToTileUid;

	    for (auto i = tileDict.constBegin();
	              i != tileDict.constEnd(); ++i) {
	        const Tile *tile = registerTile(i.value());
	        if (tile != nullptr)
	            idToTile[i.key()] = tile;
	    }
	\end{lstlisting}
\end{frame}

\begin{frame}[fragile]{Chyba działa}
	\begin{lstlisting}
	    manager.coordToTile.clear();
	    for (auto i = coordToTileUid.constBegin();
	              i != coordToTileUid.constEnd(); ++i)
	        manager.coordToTile[i.key()]
	                = idToTile[i.value()];
	\end{lstlisting}
\end{frame}

\begin{frame}[fragile]
	\begin{block}{}
		Po czymś takim mamy załadowany słownik \texttt{coordToTile}
		i~możemy go~wykorzystywać do~odpowiadania na~zapytania o~kafel dla~konkretnej współrzędnej.
	\end{block}

	\pause
	\begin{lstlisting}
	const Tile * TileManager::tile
	        (QPair <int, int> hexCoord)
	{
	    return instance()
	            .coordToTile.value(hexCoord, nullptr);
	}
	\end{lstlisting}
\end{frame}

\begin{frame}{TODO}
	Jeden bonus będzie kłopotliwy.
	\begin{itemize}
		\item Wsparcie dla pseudo-trójwymiarowych grafik.
		\pause
		\item Wymaga odejścia od~ustalonego na~sztywno rozmiaru kafla.
		\pause
		\item Trzeba dodać informację o~przesunięciu kafla podczas rysowania albo mieć dodatkowe założenia
		o~rozmiarze kafla (np. zawsze kwadratowe per klatka).
	\end{itemize}
\end{frame}

\end{document}
