diff --git a/bachelorproject.tex b/bachelorproject.tex index 130ee3d..ecdedb1 100644 --- a/bachelorproject.tex +++ b/bachelorproject.tex @@ -6,6 +6,7 @@ \usepackage[]{float} \usepackage[]{multicol} \usepackage{mathtools} +\usepackage[]{listings} \usepackage{tikz} \usepackage{tkz-euclide} @@ -21,6 +22,25 @@ \pgfdeclarelayer{edgelayer} \pgfsetlayers{edgelayer,nodelayer,main} + +\lstset{language=C++, + basicstyle=\ttfamily, + keywordstyle=\color{blue}\ttfamily, + stringstyle=\color{red}\ttfamily, + commentstyle=\color{green}\ttfamily, + morecomment=[l][\color{magenta}]{\#}, + emph={std, vector, vec2d, polygon, collision}, + emphstyle=\color{orange}\ttfamily, + numbers=left, + morekeywords={uint}, + numberstyle=\footnotesize, + numbersep=5pt, + frame=lines, + % breaklines=true, + % breaklines=true, + % postbreak=\raisebox{0ex}[0ex][0ex]{\space\ensuremath{\hookrightarrow}}, +} + \graphicspath{{../figures/}{./figures/}} \newcommand*{\vv}[1]{\overrightarrow{#1}} diff --git a/figures/bounding_box.pdf b/figures/bounding_box.pdf new file mode 100644 index 0000000..b34eb5d Binary files /dev/null and b/figures/bounding_box.pdf differ diff --git a/sections/implementation.tex b/sections/implementation.tex index f286ddb..80c34b0 100644 --- a/sections/implementation.tex +++ b/sections/implementation.tex @@ -1 +1,178 @@ \section{Implementation} + +This section will dive into the actual implementing of the project, trying to +describe how the theoretical concepts in section \ref{sec:theory}. + +\subsection{Structure} + +The added code to the project can be divided into three parts +\begin{itemize} + \item polygon generator; + \item collision detection; + \item collision resolution. +\end{itemize} + +Each part will be explained in the following sub-sections. + +\subsubsection{Polygon generator} +Before talking about how we generate polygons, let's first talk about how the +polygons are defined in this project. + +In the file \texttt{polygons.h} we define a polygon as + +\begin{lstlisting}[caption={Polygon class (simplified)},label={lst:polygon}] +class polygon { + std::vector points; + + vec2d center; + double angle; + + double inertia; + double mass; + + std::vector global_points(); + + vec2d speed; + double angular_speed; +} +\end{lstlisting} + +Polygons possesses multiple fields. Firstly we have +\lstinline{std::vector points}, a collection of +\lstinline{vec2d} objects, which represent the set of ordered points that +compose the polygon. Those points are expressed in local coordinates, and the +center of mass of the polygon is placed the origin. + +Now that we know how the polygon is composed, we want to move it around the +space it lives in, that is the purpose of the \lstinline{vec2d center} field. +It represents where the center of mass of the polygon is located in simulation. +Since the shapes also rotate, we need to keep track of the rotation of the +polygon, hence the use of \lstinline{double angle}. All of these fields are used +when computing the result of the method \lstinline{std::vector global_points()}. + +Finally, we have \lstinline{double inertia} and \lstinline{double mass} which, +as their name suggest, store the value for the polygon's inertia and mass, that +are used to calculate the polygons final speed and angular speed, who are +represented by \lstinline{vec2d speed} and \lstinline{double angular_speed}. + +\paragraph{Polygon generator} Now that we know how polygons are represented in +our simulation, we can generate them. In \texttt{polygon\_generator.h} (and its +implementation file \texttt{polygon\_generator.cc}), there are some functions +for that. In Listing \ref{lst:polygon_gen} we can see the signature of the +functions that generate +\begin{itemize} + \item a rectangle of a certain width and height; + \item a square of a certain side length; + \item a triangle given two side lengths and an angle between them; + \item a regular polygon; + \item an arbitrary polygon. +\end{itemize} + +\begin{lstlisting}[caption={Polygon Generator header +file},label={lst:polygon_gen}] +namespace poly_generate { + polygon rectangle(double width, double height); + + inline polygon square(double width) { + assert(width > 0); + return rectangle(width, width, label); + }; + + polygon triangle(double side1, double side2, double angle); + + polygon regular(double radius, uint n_sides); + + polygon general(std::vector points); +}; +\end{lstlisting} + +The implementation of those functions are fairly straight forward, they generate +the appropriate number of points in the correct place. After what they calculate +the mass and subsequent inertia of the polygon as shown in section +\ref{sub:moment}. + +\subsubsection{Collision} +\label{sub:implementation-collision-detection} + +The algorithm described in section~\ref{sub:vertex-collision} is implemented in +\texttt{collision.cc} and exposed to other modules with \texttt{collision.h}. + +The module exposes a collision structure and a collides function. + +\begin{lstlisting}[caption={Collision header file},label={lst:collision}] +struct collision { + bool collides = false; + vec2d impact_point; + vec2d n; + vec2d overlap; +}; + +extern collision collides(polygon& p, polygon& q); +\end{lstlisting} + +The \lstinline{collides} function takes in two polygons and checks with +vertex-collision algorithm whether they collide or not. The result is return +through an instance of \lstinline{struct collision}. The \lstinline{bool collides} +and \lstinline{vec2d impact_point} fields is self-explanatory. The +\lstinline{vec2d n} is the normal vector that pushes \lstinline{polygon& p} away +from \lstinline{polygon& q}. Finally, \lstinline{vec2d overlap} is a scalar +multiplication of \lstinline{vec2d n}. Where the latter is a normalized vector, +the former represents how deep \lstinline{p} is in \lstinline{q}. If we push +\lstinline{p} the exact amount of \lstinline{overlap}, then \lstinline{p} would +just be touching \lstinline{q} with point \lstinline{impact_point}, and no +further overlap would occur. + +In reality, we do not simply push \lstinline{p} by \lstinline{overlap}, but we +push \lstinline{p} and \lstinline{q} away from each other proportionally to +their mass. + +\paragraph{Collision resolution} In \texttt{polygons.cc}, among other functions, +we apply the collision resolution, which simply uses the physics results found +in \ref{sub:resolution}. + +\subsection{Optimization} + +In order to optimise the collision detection and to avoid having to do a check +whether each vertex of every polygon was colliding with each edge of every other +polygon we decided to apply the bounding box acceleration structure. + +The bounding box of an object is, in two dimensions, the rectangle that contains +the object and which the sides are aligned with axis of the coordinate system. + +\begin{figure}[H] + \centering + \inputtikz[.7]{bounding_box} + \caption{Bounding box example} + \label{fig:bounding-box} +\end{figure} + +To get the four coordinates that compose, we just perform a linear scan through +all the points that compose a polygon, record the minimum and maximum of both +they $x$ and $y$ coordinate. + +Once those points have been found, we just perform some basic axis-aligned +rectangle collision detection, which is much less computationally expensive than +checking each vertex-edge pair. + +The complexity of the collision detection algorithm went from $\mathcal{O}(n^2)$ +to $\mathcal{O}(n)$, where $n$ is the total number of vertices across all +polygons in the simulation. + + +\subsection{Known issues} + +The simulation has one major flaw, and it is inherent to the last part of +section \ref{sub:implementation-collision-detection} where we talk about the +\lstinline{overlap} vector: we said that this vector allows to displace both +polygons so that and the end of the collision resolution, they are not +overlapping anymore. This was done to avoid issues where the collision +resolution had taken place in one frame, but the polygons were still overlapping +in the following frame, which lead to polygons getting stuck together. + +The unfortunate consequence of such a decision is that, when we start playing +around with the restitution coefficient and all the shapes start falling to the +ground, the shapes are not able to rest still. Since there is gravity, at each +frame their speed get updated to simulate its effect, but since they are lying +on the floor, the collision detection algorithm kicks in right away, resulting +in the polygon getting pushed completely up, since the ground has infinite mass. +This results in the polygon bouncing on the floor instead of lying down. diff --git a/tikzs/bounding_box.tikz b/tikzs/bounding_box.tikz new file mode 100644 index 0000000..00857c2 --- /dev/null +++ b/tikzs/bounding_box.tikz @@ -0,0 +1,71 @@ +\begin{tikzpicture} + \begin{pgfonlayer}{nodelayer} + \node [style=none] (0) at (0, -1) {}; + \node [style=none] (1) at (0, 8) {}; + \node [style=none] (2) at (-1, 0) {}; + \node [style=none] (3) at (15, 0) {}; + \node [style=none] (4) at (4, 4) {}; + \node [style=none] (5) at (2, 1) {}; + \node [style=none] (6) at (6, 3) {}; + \node [style=none] (7) at (9, 3) {}; + \node [style=none] (8) at (11, 2) {}; + \node [style=none] (9) at (10.5, 6.5) {}; + \node [style=none] (10) at (12.75, 5.75) {}; + \node [style=none] (11) at (13.5, 3.5) {}; + \node [style=none] (12) at (8, 5) {}; + \node [style=none] (13) at (2, 4) {}; + \node [style=none] (14) at (6, 1) {}; + \node [style=none] (15) at (6, 4) {}; + \node [style=none] (16) at (8, 2) {}; + \node [style=none] (17) at (8, 6.5) {}; + \node [style=none] (18) at (13.5, 6.5) {}; + \node [style=none] (19) at (13.5, 2) {}; + \node [style=none] (20) at (2, 0) {}; + \node [style=none] (21) at (6, 0) {}; + \node [style=none] (22) at (0, 4) {}; + \node [style=none] (23) at (0, 1) {}; + \node [style=none] (24) at (-0.25, 1) {$A_{y_{\min}}$}; + \node [style=none] (25) at (-0.25, 4) {$A_{y_{\max}}$}; + \node [style=none] (26) at (2, -0.25) {$A_{x_{\min}}$}; + \node [style=none] (27) at (6, -0.25) {$A_{x_{\max}}$}; + \node [style=none] (28) at (8, 0) {}; + \node [style=none] (29) at (8, -0.25) {$B_{x_{\min}}$}; + \node [style=none] (30) at (13.5, 0) {}; + \node [style=none] (31) at (13.5, -0.25) {$B_{x_{\max}}$}; + \node [style=none] (32) at (0, 2) {}; + \node [style=none] (33) at (-0.25, 2) {$B_{y_{\min}}$}; + \node [style=none] (34) at (0, 6.5) {}; + \node [style=none] (35) at (-0.25, 6.5) {$B_{y_{\max}}$}; + \node [style=none] (36) at (4, 2.75) {$A$}; + \node [style=none] (37) at (10.5, 4.5) {$B$}; + \end{pgfonlayer} + \begin{pgfonlayer}{edgelayer} + \draw [style=Vector] (0.center) to (1.center); + \draw [style=Vector] (2.center) to (3.center); + \draw (5.center) to (4.center); + \draw (4.center) to (6.center); + \draw (6.center) to (5.center); + \draw (12.center) to (9.center); + \draw (9.center) to (10.center); + \draw (10.center) to (11.center); + \draw (11.center) to (8.center); + \draw (8.center) to (7.center); + \draw (7.center) to (12.center); + \draw [style=Dashed] (13.center) to (5.center); + \draw [style=Dashed] (5.center) to (14.center); + \draw [style=Dashed] (14.center) to (15.center); + \draw [style=Dashed] (15.center) to (13.center); + \draw [style=Dashed] (16.center) to (17.center); + \draw [style=Dashed] (17.center) to (18.center); + \draw [style=Dashed] (18.center) to (19.center); + \draw [style=Dashed] (19.center) to (16.center); + \draw [style=Dotted] (19.center) to (30.center); + \draw [style=Dotted] (16.center) to (28.center); + \draw [style=Dotted] (16.center) to (32.center); + \draw [style=Dotted] (13.center) to (22.center); + \draw [style=Dotted] (17.center) to (34.center); + \draw [style=Dotted] (5.center) to (20.center); + \draw [style=Dotted] (5.center) to (23.center); + \draw [style=Dotted] (14.center) to (21.center); + \end{pgfonlayer} +\end{tikzpicture}