Finished the implementation section

This commit is contained in:
Karma Riuk 2023-06-22 18:19:12 +02:00
parent 9879a04efe
commit a0a5f2f624
4 changed files with 268 additions and 0 deletions

View File

@ -6,6 +6,7 @@
\usepackage[]{float} \usepackage[]{float}
\usepackage[]{multicol} \usepackage[]{multicol}
\usepackage{mathtools} \usepackage{mathtools}
\usepackage[]{listings}
\usepackage{tikz} \usepackage{tikz}
\usepackage{tkz-euclide} \usepackage{tkz-euclide}
@ -21,6 +22,25 @@
\pgfdeclarelayer{edgelayer} \pgfdeclarelayer{edgelayer}
\pgfsetlayers{edgelayer,nodelayer,main} \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/}} \graphicspath{{../figures/}{./figures/}}
\newcommand*{\vv}[1]{\overrightarrow{#1}} \newcommand*{\vv}[1]{\overrightarrow{#1}}

BIN
figures/bounding_box.pdf Normal file

Binary file not shown.

View File

@ -1 +1,178 @@
\section{Implementation} \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<vec2d> points;
vec2d center;
double angle;
double inertia;
double mass;
std::vector<vec2d> global_points();
vec2d speed;
double angular_speed;
}
\end{lstlisting}
Polygons possesses multiple fields. Firstly we have
\lstinline{std::vector<vec2d> 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<vec2d> 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<vec2d> 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.

71
tikzs/bounding_box.tikz Normal file
View File

@ -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}