Jekyll2024-12-01T17:26:06-08:00https://baskin.me/feed.xmlBaskın ŞenbaşlarPersonal websiteMy Google Summer of Code 2019 Work in CGAL Library2019-08-25T00:00:00-07:002019-08-25T00:00:00-07:00https://baskin.me/cgal-gsoc-2019<style>
.completed-task {
color: green;
}
.almost-completed-task {
color: orange;
}
.cgal-gsoc-comparison-table th, .cgal-gsoc-comparison-table td {
text-align: center;
}
.cgal-gsoc-comparison-table img {
width: 250px;
}
</style>
<p class="text-justify">I worked on the <a href="https://doc.cgal.org/latest/Surface_mesh_simplification/index.html">Triangulated Surface Mesh Simplification Package</a> of <a href="https://www.cgal.org/">CGAL</a> in my GSoC 2019 work. Surface mesh simplification -or decimation- is actually pretty self explanatory. Given a surface mesh, try to decrease complexity of the mesh while minimizing the deviation from the original mesh.</p>
<h2 id="triangulated-surface-mesh-simplification-framework-of-cgal">Triangulated Surface Mesh Simplification Framework of CGAL</h2>
<p class="text-justify">CGAL already has a framework for surface mesh simplification algorithms. This framework is parametrized by 4 main functional objects obeying following concepts: <a href="https://doc.cgal.org/latest/Surface_mesh_simplification/classGetCost.html">GetCost</a>, <a href="https://doc.cgal.org/latest/Surface_mesh_simplification/classGetPlacement.html">GetPlacement</a>, <a href="https://doc.cgal.org/latest/Surface_mesh_simplification/classStopPredicate.html">StopPredicate</a>, and <a href="https://doc.cgal.org/latest/Surface_mesh_simplification/classEdgeCollapseSimplificationVisitor.html">EdgeCollapseSimplificationVisitor</a>. The general algorithm can be summarized as follows:</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="code"><pre><span class="n">create</span> <span class="n">a</span> <span class="n">priority</span> <span class="n">queue</span> <span class="n">to</span> <span class="n">contain</span> <span class="n">each</span> <span class="n">edge</span> <span class="n">ordered</span> <span class="n">by</span> <span class="n">the</span> <span class="n">cost</span> <span class="n">of</span> <span class="n">collapsing</span> <span class="n">them</span>
<span class="n">push</span> <span class="n">each</span> <span class="n">edge</span> <span class="n">to</span> <span class="n">the</span> <span class="n">pq</span> <span class="k">with</span> <span class="n">the</span> <span class="n">cost</span> <span class="n">calculated</span> <span class="n">by</span> <span class="n">the</span> <span class="n">GetCost</span> <span class="n">functional</span>
<span class="k">while</span> <span class="n">priority</span> <span class="n">queue</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">empty</span> <span class="ow">and</span> <span class="n">ShouldStop</span> <span class="n">functional</span> <span class="n">returns</span> <span class="n">false</span><span class="p">:</span>
<span class="n">pop</span> <span class="n">an</span> <span class="n">edge</span>
<span class="n">calculate</span> <span class="n">the</span> <span class="n">placement</span> <span class="k">for</span> <span class="n">the</span> <span class="n">new</span> <span class="n">vertex</span> <span class="n">using</span> <span class="n">GetPlacement</span> <span class="n">functional</span>
<span class="n">collapse</span> <span class="n">the</span> <span class="n">edge</span>
<span class="n">update</span> <span class="n">the</span> <span class="n">costs</span> <span class="n">of</span> <span class="n">neighbors</span> <span class="n">of</span> <span class="n">the</span> <span class="n">collapsed</span> <span class="n">edge</span> <span class="ow">in</span> <span class="n">the</span> <span class="n">pq</span> <span class="n">using</span> <span class="n">GetCost</span>
</pre></td></tr></tbody></table></code></pre></figure>
<p class="text-justify">Lastly, the callbacks defined in the <a href="https://doc.cgal.org/latest/Surface_mesh_simplification/classEdgeCollapseSimplificationVisitor.html">EdgeCollapseSimplificationVisitor</a> concept are called at different stages of the algorithm.</p>
<h2 id="my-planned-and-completed-contributions">My Planned and Completed Contributions</h2>
<p class="text-justify">Following is the summary table of the contributions that I planned during my application to Google Summer of Code 2019. The table also includes my completion status for each listed item. All of the contributions in this table are explained in detail later in the post.</p>
<table>
<tbody>
<tr>
<td>#</td>
<td>Task</td>
<td>Description</td>
<td>Status</td>
</tr>
<tr>
<td>1</td>
<td>Implementing Garland&Heckbert Simplification</td>
<td>This task includes implementing Garland&Hecbert surface mesh simplification as described in <a href="https://www.cs.cmu.edu/~./garland/Papers/quadrics.pdf">this paper</a> using the existing mesh simplification framework of CGAL.</td>
<td><span class="completed-task">completed</span></td>
</tr>
<tr>
<td>2</td>
<td>Comparing our Garland&Hecbert to the Old Method used in CGAL and to Other Libraries</td>
<td>This task includes implementing surface mesh simplification using other libraries (namely <a href="https://www.openmesh.org/">OpenMesh</a>, <a href="https://libigl.github.io/">libigl</a>, and <a href="https://www.pmp-library.org/">pmp library</a>); and comparing our results with theirs in terms of runtime and quality. This task also includes comparing our new Garland&Heckbert with the old method used in CGAL, namely Lindstrom&Turk. A new quality comparison metric as described in the Garland&Heckbert paper is also required.</td>
<td><span class="completed-task">completed</span></td>
</tr>
<tr>
<td>3</td>
<td>Documenting the Garland&Heckbert simplification</td>
<td>This task includes documenting the newly implemented Garland&Heckbert simplification and providing examples that describe how to use it.</td>
<td><span class="completed-task">completed</span></td>
</tr>
<tr>
<td>4</td>
<td>Parallelizing the Surface Mesh Simplification Framework of CGAL</td>
<td>This task includes parallizing the surface mesh simplification framework of CGAL.</td>
<td><span class="almost-completed-task">almost completed</span></td>
</tr>
</tbody>
</table>
<p class="text-justify">The first big part my CGAL contribution consisted of first three tasks: Implementing Garland&Heckbert, and getting it ready for the production. The second big part of my CGAL contribution was the last task: parallizing the framework.</p>
<h3 id="0-getting-to-know-cgal-library">0. Getting to Know CGAL Library</h3>
<p class="text-justify">As with any new project, one of the most difficult steps of contributing to CGAL for me was learning the structure of the code. First thing you notice in CGAL is that it is highly templated. I mean, <em>HIGHLY TEMPLATED</em>! The library is so generic that any new eye would be daunted by the extent of it. From the most basic operation of intersecting a plane with a line to the most complex operation of simplifying an entire mesh, you use templates everywhere.</p>
<p class="text-justify">However, once you grasp how the library works, you get amazed by what that genericity provides. I didn’t even touch to the core algorithm while implementing a new version of surface mesh simplification. I only added new files, and plugged things together in a main code. That’s it.</p>
<p class="text-justify">Luckily, CGAL has a lot sources for new developers to ease this initial pain. The first things I did to get on to the development were to read following documents:</p>
<ol class="text-justify">
<li><a href="http://inf.ethz.ch/personal/hoffmann/pub/hhkps-aegk-01a.pdf">An Adaptable and Extensible Geometry Kernel</a>: This rather old paper explains the idea of ‘geometric kernel’ used in CGAL. Geometric kernel is the structure that abstracts geometric primitives that users can use in their geometric algorithms. These geometric primitives are objects like points, lines, planes and operations on these objects like checking if two lines intersect or constructing the line that is perpendicular to a plane that goes through a given point. Abstracting objects and operations allows geometric kernel developers to implement different versions of same operations/objects for different needs. For example, one implementation may be more robust, while another implementation may be more efficient, while another implementation may be exact. Algorithms using these operations through kernels are not effected by changes, and the users may choose the kernel they want by changing a single template parameter. The kernel idea presented in the paper is also extensible meaning new operations and objects can be easily added to existing kernels.</li>
<li><a href="https://doc.cgal.org/latest/Manual/dev_manual.html">The Public Developer Manual of CGAL</a>: My next step was hopping into reading the public developer manual of CGAL. Specificially, I read the Introduction, Coding Conventions, Geometry Kernels, Trait Classes, and Checks sections to get into the development fast. I partially checked other sections as needed after delving into the development.</li>
<li><a href="https://github.com/cgal/cgal/wiki/Guidelines">Developer Guidelines of CGAL</a>: My next reading before starting on development was the guidelines for developers of CGAL. Initially, I read Building, and Package Directory Structure sections of the guidelines as building the code and understanding the layout of directories can be a little challenging in CGAL in the beginning. Even if it seems like there are unneccessary nested directories, later it becomes clear that it is done to have consistent paths in C++ includes, and CMake files.</li>
<li><a href="https://www.cs.cmu.edu/~./garland/Papers/quadrics.pdf">Surface Simplification Using Quadric Error Metrics</a>: Lastly, I read the paper that I was going to implement in CGAL.</li>
</ol>
<h3 id="1-implementing-garlandheckbert-simplification">1. Implementing Garland&Heckbert Simplification</h3>
<p>Commits: <a href="https://github.com/CGAL/cgal-public-dev/commit/22e02f0d65aa0040e3e4ae106d6f1c2fe582f065">22e02f0d</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/0c893d81673fd00cac7cb801013d9fb736ed6fc3">0c893d81</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/b4e313e3238cf4a0af1a52e235c2085626cfdb7d">b4e313e3</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/3c0a754fc18067ab18e961aea76957693af0e025">3c0a754f</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/a2009dcd90a1dd27ff5e3c8cf66faad3ca0847d7">a2009dcd</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/a99175395670788d92255c0ca135eb97e2da4ae2">a9917539</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/a3564e73feda50b25fdeafc71efd2f071e9cd835">a3564e73</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/ed90041770dbb2ae5fc850dcd22951d07b83c324">ed900417</a></p>
<p>I implemented Garland&Heckbert simplification using the existing simplification framework of CGAL.</p>
<p class="text-justify">Garland&Heckbert simplification assigns a 4x4 symmetric matrix to each vertex in the mesh. These 4x4 matrices summarizes vertex’s total distance to the faces incident to it. The first 4x4 matrix assigned to a vertex is called the fundamental error quadric. Assume a vertex has spatial embedding <script type="math/tex">v</script> in homogenous form. For each of the planes <script type="math/tex">% <![CDATA[
p = \begin{bmatrix}a&b&c&d\end{bmatrix}^T %]]></script> incident to a vertex, the distance of <script type="math/tex">v</script> to <script type="math/tex">p</script> is given by <script type="math/tex">v^Tp</script>. Given a set of planes around the vertex, the error of vertex induced by these planes is defined by the paper as</p>
<script type="math/tex; mode=display">% <![CDATA[
\begin{align}
\Delta(v) &= \sum_{p \in planes(v)} (v^T p)^2\\
&=\sum_{p \in planes(v)} (v^T p)(p^T v)\\
&=v^T\left(\sum_{p \in planes(v)} p p^T\right) v\\
&=v^T Q v
\end{align} %]]></script>
<p class="text-justify">The 4x4 matrix <script type="math/tex">Q</script> assigned to <script type="math/tex">v</script> is called the fundamental error quadric. This is the most simple form explained in the paper. However, using only this formula is not good for preserving boundaries of bounded surfaces. Paper suggests penalizing movement of border vertices by adding pseudo-planes for each incident face of the boundary vertex that are perpendicular to incident faces with high penalizing multipliers. My implementation of this final form that handles boundries as well is the <strong>fundamental_error_quadric</strong> function in <a href="https://github.com/CGAL/cgal-public-dev/blob/gsoc2019-SMS_improvements-baskinburak/Surface_mesh_simplification/include/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/internal/GarlandHeckbert_core.h">GarlandHeckbert_core.h</a>.</p>
<p class="text-justify">The quadric matrix <script type="math/tex">Q</script> assigned to the new vertex created after collapsing an edge that has endpoints <script type="math/tex">v_1</script>, and <script type="math/tex">v_2</script> with corresponding quadric matrices <script type="math/tex">Q_1</script>, and <script type="math/tex">Q_2</script>, is simply the sum of these matrices.(<script type="math/tex">Q = Q_1 + Q_2</script>) This operation is implemented in the <strong>combine_matrices</strong> function in <a href="https://github.com/CGAL/cgal-public-dev/blob/gsoc2019-SMS_improvements-baskinburak/Surface_mesh_simplification/include/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/internal/GarlandHeckbert_core.h">GarlandHeckbert_core.h</a>.</p>
<p class="text-justify">Under Garland&Heckbert algorithm, placement of the new vertex after a collapse operation and the cost of the collapse operation are tightly coupled. We solve a single optimization problem to find both the placement and the cost. Given an edge with endpoints <script type="math/tex">v_1</script>, and <script type="math/tex">v_2</script> with corresponding quadric matrices <script type="math/tex">Q_1</script>, and <script type="math/tex">Q_2</script>; the placement <script type="math/tex">v</script>, and the cost of the collapse operation is determined by minimizing the function <script type="math/tex">\Delta(v) = v^TQv</script> where <script type="math/tex">% <![CDATA[
v=\begin{bmatrix}a&b&c&1\end{bmatrix} %]]></script> with <script type="math/tex">a</script>, <script type="math/tex">b</script>, and <script type="math/tex">c</script> being decision variables, and <script type="math/tex">Q = Q_1+Q_2</script>. As stated in the paper, since <script type="math/tex">\Delta</script> is quadratic, finding is minimum is a linear problem. Thus, <script type="math/tex">a</script>, <script type="math/tex">b</script>, and <script type="math/tex">c</script> can be found by solving <script type="math/tex">\frac{\partial\Delta}{\partial a} = 0</script>, <script type="math/tex">\frac{\partial\Delta}{\partial b} = 0</script>, and <script type="math/tex">\frac{\partial\Delta}{\partial c} = 0</script>. This is equivalent to solving the following system where <script type="math/tex">q_{ij}</script> is the element in the <script type="math/tex">i^{th}</script> row, <script type="math/tex">j^{th}</script> column in the <script type="math/tex">Q</script> matrix:</p>
<script type="math/tex; mode=display">% <![CDATA[
\begin{align}
\begin{bmatrix}q_{11}&q_{12}&q_{13}&q_{14}\\q_{21}&q_{22}&q_{23}&q_{24}\\q_{31}&q_{32}&q_{33}&q_{34}\\0&0&0&1\end{bmatrix}v = \begin{bmatrix}0\\0\\0\\1\end{bmatrix}
\end{align} %]]></script>
<p class="text-justify">If the matrix is invertible, solution is simply given as</p>
<script type="math/tex; mode=display">% <![CDATA[
\begin{align}
v = \begin{bmatrix}q_{11}&q_{12}&q_{13}&q_{14}\\q_{21}&q_{22}&q_{23}&q_{24}\\q_{31}&q_{32}&q_{33}&q_{34}\\0&0&0&1\end{bmatrix}^{-1} \begin{bmatrix}0\\0\\0\\1\end{bmatrix}
\end{align} %]]></script>
<p class="text-justify">If the matrix is not invertible, paper suggests optimizing the cost on the line segment connecting <script type="math/tex">v_1</script>, and <script type="math/tex">v_2</script>; and if that fails as well, it suggests choosing either <script type="math/tex">v_1</script>, or <script type="math/tex">v_2</script>, or the midpoint between <script type="math/tex">v_1</script>, and <script type="math/tex">v_2</script>. However, it does not provide the explicit math to do so. Therefore, my next step was solving the optimization problem on the line segment connecting <script type="math/tex">v_1</script> to <script type="math/tex">v_2</script>.</p>
<script type="math/tex; mode=display">% <![CDATA[
\begin{align}
\min_{v}&\ v^T Q v\\
where&\ v \in \overline{v_1v_2}
\end{align} %]]></script>
<p class="text-justify">which is equiavalent to solving</p>
<script type="math/tex; mode=display">% <![CDATA[
\begin{align}
\min_{v}\ &v^T Q v\\
where\ &v = (1-t)v_1 + tv_2\\
& t\in[0, 1]
\end{align} %]]></script>
<p class="text-justify">When we plug <script type="math/tex">v=(1-t)v_1 + tv_2</script> into the problem, problem becomes</p>
<script type="math/tex; mode=display">% <![CDATA[
\begin{align}
\min_{t}\ &(v_2-v_1)^TQ(v_2-v_1)t^2 + 2v_1^TQ(v_2-v_1)t + v_1^TQv_1\\
where\ &t\in [0, 1]
\end{align} %]]></script>
<p class="text-justify">Even if this seems like a complicated problem, it is a pretty easy one since <script type="math/tex">Q</script>, <script type="math/tex">v_1</script>, and <script type="math/tex">v_2</script> are constants. Therefore this is just a parabola with 1 variable. Finding the minimum of a parabola in one dimension is a constant time problem. Only thing I had to make sure was checking if the minimum is in the range <script type="math/tex">[0, 1]</script>. If not, either <script type="math/tex">t=0</script>, or <script type="math/tex">t=1</script> should be minimal.</p>
<p class="text-justify">Formulation above fails at singularities, namely when <script type="math/tex">(v_2-v_1)^TQ(v_2-v_1) = 0</script>. In that case, objective reduces to a line segment, and either <script type="math/tex">v_1</script>, or <script type="math/tex">v_2</script> is the optimal point. If <script type="math/tex">v_1^TQ(v_2-v_1)=0</script> as well, I simply choose the midpoint.</p>
<p class="text-justify">My final implementation of this optimization is the <strong>optimal_point</strong> function in <a href="https://github.com/CGAL/cgal-public-dev/blob/gsoc2019-SMS_improvements-baskinburak/Surface_mesh_simplification/include/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/internal/GarlandHeckbert_core.h">GarlandHeckbert_core.h</a>.</p>
<p class="text-justify">With these three core Garland&Heckbert operations -fundamental_error_quadric, combine_matrices, and optimal_point- in hand, I implemented EdgeCollapseSimplificationVisitor, GetCost, and GetPlacement models for the algorithm. <a href="https://github.com/CGAL/cgal-public-dev/blob/gsoc2019-SMS_improvements-baskinburak/Surface_mesh_simplification/include/CGAL/Surface_mesh_simplification/GarlandHeckbert_edge_collapse_visitor_base.h">GarlandHeckbert_edge_collapse_visitor_base</a> is a model for the EdgeCollapseSimplificationVisitor concept, which populates the state structure that the algorithm uses with fundamental error quadrics. It also updates quadrics of vertices after collapse operations. <a href="https://github.com/CGAL/cgal-public-dev/blob/gsoc2019-SMS_improvements-baskinburak/Surface_mesh_simplification/include/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/GarlandHeckbert_cost.h">GarlandHeckbert_cost</a> is a model for the GetCost concept which calculates the cost of collapsing an edge. Lastly, <a href="https://github.com/CGAL/cgal-public-dev/blob/gsoc2019-SMS_improvements-baskinburak/Surface_mesh_simplification/include/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/GarlandHeckbert_placement.h">GarlandHeckbert_placement</a> is a model for the GetPlacement concept which computes the optimal placement point for the new vertex created after the collapse operation. I also implemented a trivial stop predicate -<a href="https://github.com/CGAL/cgal-public-dev/blob/gsoc2019-SMS_improvements-baskinburak/Surface_mesh_simplification/include/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/GarlandHeckbert_cost_stop_predicate.h">GarlandHeckbert_cost_stop_predicate</a>- that runs the algorithm until the minimum cost of collapsing any edge exceeds a given threshold.</p>
<h3 id="2-comparing-our-garlandheckbert-to-the-old-method-used-in-cgal-and-to-other-libraries">2. Comparing our Garland&Heckbert to the Old Method used in CGAL and to Other Libraries</h3>
<p>Commits: <a href="https://github.com/CGAL/cgal-public-dev/commit/facec0196ad84ba8a458c856a7e6ae2ca48731e5">facec019</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/587c5f8af71b49e84f0cfd22434dc10f5c4607ce">587c5f8a</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/d0dbe88edf2c0717b5bbaf8658996dfed4390485">d0dbe88e</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/6abd5da95b231dd8b61dac0eba2a1ec9007b80ce">6abd5da9</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/592bf5fb286ab082503b3c64bf6eb28ccebc5383">592bf5fb</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/f472c6b155b3b4508735bec146457cd3a5b832a8">f472c6b1</a></p>
<p class="text-justify">After completing Garland&Heckbert implementation, my next task was comparing it to the old method used in CGAL, namely <a href="https://www.cc.gatech.edu/~turk/my_papers/memless_vis98.pdf">Lindstrom&Turk</a>; and to other mesh simplification libraries, namely <a href="https://www.openmesh.org/">OpenMesh</a>, <a href="https://libigl.github.io/">libigl</a>, and <a href="https://www.pmp-library.org/">pmp library</a>. The comparisons had two metrics: runtime, and simplification quality. As simplification quality metric I used two different distance functions. First one was the <a href="https://en.wikipedia.org/wiki/Hausdorff_distance">Hausdorff distance</a> that was already implemented in CGAL. It is a rather fast evaluation metric that tries to measure distance between two meshes with only 1 point. The second distance function is presented in the Garland&Heckbert paper, which tries to measure distance between two meshes by averaging distances of sampled points. Specifically, distance <script type="math/tex">D</script> between meshes <script type="math/tex">M_i</script>, and <script type="math/tex">M_n</script>, is calculated as follows where <script type="math/tex">X_i</script>, and <script type="math/tex">X_n</script> are sampled points respectively from <script type="math/tex">M_i</script>, and <script type="math/tex">M_n</script>:</p>
<script type="math/tex; mode=display">\begin{align}
D(M_i, M_n) = \frac{1}{|X_n| + |X_i|}\left(\sum_{v\in X_n}d^2(v, M_i) + \sum_{v\in X_i}d^2(v, M_n)\right)
\end{align}</script>
<p class="text-justify">where <script type="math/tex">d(v, M)</script> is the minimum distance that point <script type="math/tex">v</script> has to any face of mesh <script type="math/tex">M</script>. My implementation of this distance metric can be found in <a href="https://github.com/CGAL/cgal-public-dev/blob/gsoc2019-SMS_improvements-baskinburak/Surface_mesh_simplification/examples/Surface_mesh_simplification/utils/edge_collapse_distance.cpp">edge_collapse_distance.cpp</a>.</p>
<p class="text-justify">My implementations of OpenMesh simplification can be found in <a href="https://github.com/CGAL/cgal-public-dev/blob/gsoc2019-SMS_improvements-baskinburak/Surface_mesh_simplification/examples/Surface_mesh_simplification/other_library_examples/openmesh_polymesh_simplification.cpp">openmesh_polymesh_simplification.cpp</a>, and <a href="https://github.com/CGAL/cgal-public-dev/blob/gsoc2019-SMS_improvements-baskinburak/Surface_mesh_simplification/examples/Surface_mesh_simplification/other_library_examples/openmesh_trimesh_simplification.cpp">openmesh_trimesh_simplification.cpp</a>. My implementation of libigl simplification can be found in <a href="https://github.com/CGAL/cgal-public-dev/blob/gsoc2019-SMS_improvements-baskinburak/Surface_mesh_simplification/examples/Surface_mesh_simplification/other_library_examples/libigl_simplification.cpp">libigl_simplification.cpp</a>, and my implementation of pmp simplification can be found in <a href="https://github.com/CGAL/cgal-public-dev/blob/gsoc2019-SMS_improvements-baskinburak/Surface_mesh_simplification/examples/Surface_mesh_simplification/other_library_examples/pmp_simplification.cpp">pmp_simplification.cpp</a>. My example code for using Garland&Heckbert that I used for comparisons can be found in <a href="https://github.com/CGAL/cgal-public-dev/blob/592bf5fb286ab082503b3c64bf6eb28ccebc5383/Surface_mesh_simplification/examples/Surface_mesh_simplification/edge_collapse_surface_mesh_garland_heckbert.cpp">edge_collapse_surface_mesh_garland_heckbert.cpp</a>, and the example I used for Lindstrom&Turk is the same code with different GetCost, and GetPlacement models.</p>
<p class="text-justify">The results of my comparisons are summarized in the following tables.</p>
<table class="cgal-gsoc-comparison-table">
<caption>Comparison Input Summaries</caption>
<tr>
<th>#</th>
<th>Mesh</th>
<th>Original Mesh Image</th>
<th>Edge Count</th>
<th>Edge Count After Simplification</th>
</tr>
<tr>
<td>1</td>
<td>man.off</td>
<td><a href="/assets/images/cgal-gsoc-2019/man.png"><img src="/assets/images/cgal-gsoc-2019/man.png" /></a></td>
<td>52479</td>
<td>5247</td>
</tr>
<tr>
<td>2</td>
<td>1255206.stl</td>
<td><a href="/assets/images/cgal-gsoc-2019/1255206.png"><img src="/assets/images/cgal-gsoc-2019/1255206.png" /></a></td>
<td>1408410</td>
<td>14082</td>
</tr>
<tr>
<td>3</td>
<td>472069.stl|</td>
<td><a href="/assets/images/cgal-gsoc-2019/472069.png"><img src="/assets/images/cgal-gsoc-2019/472069.png" /></a></td>
<td>183735</td>
<td>9186</td>
</tr>
</table>
<table class="cgal-gsoc-comparison-table">
<caption>Mesh #1(man.off) Simplification Comparison</caption>
<tr>
<th>Simplification Algorithm</th>
<th>Resulting Mesh Image</th>
<th>Runtime</th>
<th>Hausdorff Distance</th>
<th>Garland&Heckbert Distance</th>
</tr>
<tr>
<td>CGAL Garland&Heckbert (my implementation)</td>
<td><a href="/assets/images/cgal-gsoc-2019/man-gh.png"><img src="/assets/images/cgal-gsoc-2019/man-gh.png" /></a></td>
<td>439ms</td>
<td>0.00361888</td>
<td>2.70529e-07 </td>
</tr>
<tr>
<td>CGAL Lindstrom&Turk (old algorithm used in CGAL)</td>
<td><a href="/assets/images/cgal-gsoc-2019/man-lt.png"><img src="/assets/images/cgal-gsoc-2019/man-lt.png" /></a></td>
<td>1246ms</td>
<td>0.00744421</td>
<td>2.73331e-07</td>
</tr>
<tr>
<td>OpenMesh</td>
<td><a href="/assets/images/cgal-gsoc-2019/man-openmesh.png"><img src="/assets/images/cgal-gsoc-2019/man-openmesh.png" /></a></td>
<td>156ms</td>
<td>0.00427624</td>
<td>5.86795e-07</td>
</tr>
<tr>
<td>libigl</td>
<td>seg-faults during read</td>
<td>seg-faults during read</td>
<td>seg-faults during read</td>
<td>seg-faults during read</td>
</tr>
<tr>
<td>pmp library</td>
<td><a href="/assets/images/cgal-gsoc-2019/man-pmp.png"><img src="/assets/images/cgal-gsoc-2019/man-pmp.png" /></a></td>
<td>355ms</td>
<td>0.00437239</td>
<td>5.23769e-07</td>
</tr>
</table>
<p class="text-justify">In this rather simple mesh (man.off), the fastest algorithm is OpenMesh with 156ms runtime. pmp follows it with 355ms, and our new Garland&Heckbert follows it with 439ms. Linstrom&Turk has a runtime of 1246ms, and libigl gives segmentation fault during read. However, since this mesh is too small in size, it is not a proper input for speed comparison. In terms of quality of the resulting mesh, our Garland&Heckbert results in a mesh with highest quality in terms of both distance functions.</p>
<table class="cgal-gsoc-comparison-table">
<caption>Mesh #2(1255206.stl) Simplification Comparison</caption>
<tr>
<th>Simplification Algorithm</th>
<th>Resulting Mesh Image</th>
<th>Runtime</th>
<th>Hausdorff Distance</th>
<th>Garland&Heckbert Distance</th>
</tr>
<tr>
<td>CGAL Garland&Heckbert (my implementation)</td>
<td><a href="/assets/images/cgal-gsoc-2019/1255206-gh.png"><img src="/assets/images/cgal-gsoc-2019/1255206-gh.png" /></a></td>
<td>18184ms</td>
<td>0.0235652</td>
<td>mesh too big to compute</td>
</tr>
<tr>
<td>CGAL Lindstrom&Turk (old algorithm used in CGAL)</td>
<td><a href="/assets/images/cgal-gsoc-2019/1255206-lt.png"><img src="/assets/images/cgal-gsoc-2019/1255206-lt.png" /></a></td>
<td>71465ms</td>
<td>0.0308491</td>
<td>mesh too big to compute</td>
</tr>
<tr>
<td>OpenMesh</td>
<td><a href="/assets/images/cgal-gsoc-2019/1255206-openmesh.png"><img src="/assets/images/cgal-gsoc-2019/1255206-openmesh.png" /></a></td>
<td>11677ms</td>
<td>0.0260296</td>
<td>mesh too big to compute</td>
</tr>
<tr>
<td>libigl</td>
<td><a href="/assets/images/cgal-gsoc-2019/1255206-igl.png"><img src="/assets/images/cgal-gsoc-2019/1255206-igl.png" /></a></td>
<td>31157ms</td>
<td>0.0754579</td>
<td>mesh too big to compute</td>
</tr>
<tr>
<td>pmp library</td>
<td><a href="/assets/images/cgal-gsoc-2019/1255206-pmp.png"><img src="/assets/images/cgal-gsoc-2019/1255206-pmp.png" /></a></td>
<td>34609ms</td>
<td>0.0582223</td>
<td>mesh too big to compute</td>
</tr>
</table>
<p class="text-justify">This example is big enough that speed comparison would be appropriate. Clearly, OpenMesh is the fastest algorithm with 11677ms runtime among the 5 algorithms. Our new Garland&Heckbert comes second with 18184ms. libigl and pmp have runtimes of 31157ms, and 34609ms respectively. CGAL’s old simplification implementation of Lindstrom&Turk comes last with 71465ms. This clearly shows the speed improvement CGAL gets with Garland&Heckbert. Also, our new Garland&Heckbert produces the best result with Hausdorff distance of 0.0235652, while OpenMesh follows it with Hausdorff distance of 0.0260296.</p>
<table class="cgal-gsoc-comparison-table">
<caption>Mesh #3(472069.stl) Simplification Comparison</caption>
<tr>
<th>Simplification Algorithm</th>
<th>Resulting Mesh Image</th>
<th>Runtime</th>
<th>Hausdorff Distance</th>
<th>Garland&Heckbert Distance</th>
</tr>
<tr>
<td>CGAL Garland&Heckbert (my implementation)</td>
<td><a href="/assets/images/cgal-gsoc-2019/472069-gh.png"><img src="/assets/images/cgal-gsoc-2019/472069-gh.png" /></a></td>
<td>1715ms</td>
<td>0.414447</td>
<td>0.000221441</td>
</tr>
<tr>
<td>CGAL Lindstrom&Turk (old algorithm used in CGAL)</td>
<td><a href="/assets/images/cgal-gsoc-2019/472069-lt.png"><img src="/assets/images/cgal-gsoc-2019/472069-lt.png" /></a></td>
<td>4325ms</td>
<td>0.281543</td>
<td>0.000239468</td>
</tr>
<tr>
<td>OpenMesh</td>
<td><a href="/assets/images/cgal-gsoc-2019/472069-openmesh.png"><img src="/assets/images/cgal-gsoc-2019/472069-openmesh.png" /></a></td>
<td>898ms</td>
<td>0.399568</td>
<td>0.0010369</td>
</tr>
<tr>
<td>libigl</td>
<td><a href="/assets/images/cgal-gsoc-2019/472069-igl.png"><img src="/assets/images/cgal-gsoc-2019/472069-igl.png" /></a></td>
<td>3033ms</td>
<td>1.58775</td>
<td>0.000258089</td>
</tr>
<tr>
<td>pmp library</td>
<td><a href="/assets/images/cgal-gsoc-2019/472069-pmp.png"><img src="/assets/images/cgal-gsoc-2019/472069-pmp.png" /></a></td>
<td>1846ms</td>
<td>0.209697</td>
<td>0.00104904</td>
</tr>
</table>
<p class="text-justify">This example confirms our finding that OpenMesh is faster than our new Garland&Heckbert while our new Garland&Heckbert produces more quality results than OpenMesh. Even if both results have similar Hausdorff distances, Garland&Heckbert distance is a better metric (as it averages error over multiple samples instead of using only 1 point), and our Garland&Heckbert has better quality in terms of it.</p>
<h3 id="3-documenting-the-garlandheckbert-simplification">3. Documenting the Garland&Heckbert simplification</h3>
<p>Commits: <a href="https://github.com/CGAL/cgal-public-dev/commit/dedb46b6c2a30b5aa658bca5a4437623b7b64500">dedb46b6</a></p>
<p class="text-justify">At this stage, algorithm was complete; and comparisons were done. I have written user and developer documentations for the code I have written. The documentation commit can be found <a href="https://github.com/CGAL/cgal-public-dev/commit/dedb46b6c2a30b5aa658bca5a4437623b7b64500">here</a>.</p>
<h3 id="4-parallelizing-the-surface-mesh-simplification-framework-of-cgal">4. Parallelizing the Surface Mesh Simplification Framework of CGAL</h3>
<p>Commits: <a href="https://github.com/CGAL/cgal-public-dev/commit/3ed639d4da353674b2153b16627e2eb85435ee1b">3ed639d4</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/cf289aab388bfb35b23b4f73e49792a7548500f1">cf289aab</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/1cf02827729561a20aa27b6d9a07404237af8c58">1cf02827</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/37eea4acbefb175754aa1f712cd1ac80fe28bf15">37eea4ac</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/02bd8a91c54a21067511607762e6aaf9ab63d998">02bd8a91</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/d2f39ba69561d7b2206593a37b10dfb251245c4a">d2f39ba6</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/0244ab8eaf3881dd997fac47d249c881c087749b">0244ab8e</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/e9d1a8ca525dcf3346c509c3d50240e21c263b15">e9d1a8ca</a></p>
<p class="text-justify">The next step in my GSoC experience was parallelizing the already existing simplification framework of CGAL. This part was a bit more challenging for me as I was not in the realm defined by the design of simplification package anymore. I was there to change, and update the internals of the algorithm.</p>
<p class="text-justify">I started with learning the parallization approach used in CGAL. CGAL uses Intel TBB as its threading library. I read the complete <a href="https://software.intel.com/en-us/tbb-user-guide">developer guide of Intel TBB</a> to not have any understanding problems with the library itself. My mentor suggested me to check out the parallization implemented in the <a href="https://doc.cgal.org/latest/Mesh_3/index.html">Mesh_3 package of CGAL</a> as it resembles the algorithm we are going to implement. Mesh_3 is a mesh generation package that uses <a href="https://www.cs.cmu.edu/~quake/tripaper/triangle3.html">Delaunay Refinement</a> to generate 3D meshes.</p>
<p class="text-justify">Parallized mesh generation algorithm in the Mesh_3 package uses a filtered multimap as a priority queue to order refinement operations between different faces or cells. It uses a <a href="https://doc.cgal.org/latest/STL_Extension/classCGAL_1_1Spatial__lock__grid__3.html">spatial lock structure</a> to lock some parts of the mesh for synchronization purposes. It also provides some wrapper constructs around Intel TBB, that eases the use of the library, and adds batch job capabilities to it.</p>
<p class="text-justify">My first step for implementing the parallellized version of mesh simplification was to create the structure for distinguishing parallel and sequential versions of the algorithm. Since CGAL is a library, we shouldn’t break any existing code that uses CGAL. The assumption that people has with the simplification package of CGAL is that it is sequential. So, all of the existing code that uses this package should run in sequential mode without any modification. I created the structure that can be found in <a href="https://github.com/CGAL/cgal-public-dev/commit/3ed639d4da353674b2153b16627e2eb85435ee1b">3ed639d4</a> for this purpose.</p>
<p class="text-justify">Next, I adopted the filtered multimap implemented in the Mesh_3 package. it is a structure without update/remove capabilities. So, my first approach was to make it lazy. I supported the structure with an external list, and removed elements lazily during pop operations. My first approach can be found in <a href="https://github.com/CGAL/cgal-public-dev/commit/1cf02827729561a20aa27b6d9a07404237af8c58">1cf02827</a>. However, the result was really slow. Therefore I made it modifiable. The original version had only pushing and popping capabilities. I added update and remove capabilities to it as well. The resulting filtered multimap can be found in <a href="https://github.com/CGAL/cgal-public-dev/blob/gsoc2019-SMS_improvements-baskinburak/Surface_mesh_simplification/include/CGAL/Surface_mesh_simplification/internal/Filtered_multimap_container.h">Filtered_multimap_container.h</a>.</p>
<p class="text-justify">Surface mesh simplification algorithm in CGAL has two main phases: the <code class="highlighter-rouge">collect()</code> phase, and the <code class="highlighter-rouge">loop()</code> phase. I parallelized these two steps.</p>
<h4 id="parallelizing-the-collect-phase"><em>Parallelizing the <code class="highlighter-rouge">collect()</code> Phase</em></h4>
<p class="text-justify">In this phase, the algorithm creates the priority queue that the <code class="highlighter-rouge">loop()</code> is going to use, and populates it with initial costs of collapsing each edge in the mesh. Since this is a read-only operation from the perspective of the mesh, parallelizing it required no real synchronization. I switched sequential collect to parallel collect with a simple task spawning approach for each iteration. My initial implementation can be found in <a href="https://github.com/CGAL/cgal-public-dev/commit/cf289aab388bfb35b23b4f73e49792a7548500f1">cf289aab</a>. Several updates to this operation exists in other commits but they are generally minor fixes, or fixes due to the priority queue structure update.</p>
<h4 id="parallelizing-the-loop-phase"><em>Parallelizing the <code class="highlighter-rouge">loop()</code> Phase</em></h4>
<p class="text-justify">The next step was parallelizing the <code class="highlighter-rouge">loop()</code>. The <code class="highlighter-rouge">loop()</code> is the main part of the algorithm that collapses edges until the StopPredicate is true. My approach was collapsing a small number of edges in parallel, and waiting for all collapse operations to reach a barrier, and repeating the same thing again. Requiring this barrier with small number of collapses is required since collapsing one edge changes cost of collapsing others. We can’t just collapse everything together. More parallellized we get, more deviation we create with the result of the sequential version. There are two constants that determine the degree of parallelization: <code class="highlighter-rouge">number_of_allowed_parallel_collapses</code>, and <code class="highlighter-rouge">max_allowed_cost_jump_percentage</code>. As their names suggest, <code class="highlighter-rouge">number_of_allowed_parallel_collapses</code> determines maximum allowed number parallel collapses between two barriers and <code class="highlighter-rouge">max_allowed_cost_jump_percentage</code> determines the cost ratio between max and min cost edges between two barriers. My implementation of the <code class="highlighter-rouge">loop()</code> can be found in <a href="https://github.com/CGAL/cgal-public-dev/commit/37eea4acbefb175754aa1f712cd1ac80fe28bf15">37eea4ac</a>, <a href="https://github.com/CGAL/cgal-public-dev/commit/0244ab8eaf3881dd997fac47d249c881c087749b">0244ab8e</a>.</p>
<p class="text-justify">During collapse operations several threads work on the same mesh structure, and potentially on neighboring edges. Therefore, a synchronization structure is needed. First, I used the same structure that Mesh_3 package uses: <a href="https://doc.cgal.org/latest/STL_Extension/classCGAL_1_1Spatial__lock__grid__3.html">Spatial_lock_grid_3</a>. This structure allows threads to lock grid cells in a 3-dimensional grid. I locked 3D embeddings of the two ring of endpoints of an edge. This may seem enough for collapse operation, however during the collapse operation, while one vertex gets removed, the position of other changes. This may create situations where the same vertex gets locked twice by two different threads. To solve this situation, I implemented a new lock structure that works directly on vertices instead of their 3D embeddings. My implementation of this structure can be found in <a href="https://github.com/CGAL/cgal-public-dev/blob/gsoc2019-SMS_improvements-baskinburak/Surface_mesh_simplification/include/CGAL/Surface_mesh_simplification/internal/Vertex_lock_structure.h">Vertex_lock_structure.h</a>.</p>
<h2 id="moving-forward">Moving Forward</h2>
<p class="text-justify">Even if the parallel version seems complete, I am still dealing with occasional synchronization issues that break the mesh structure. These problems are currently the only things that I couldn’t complete yet. I am currently working on these and I am confident that I will be able to solve them.</p>
<p class="text-justify">CGAL has always been the library that I wanted to use in my projects. However, since it seemed very complicated because of its templated nature, I was always afraid to use it. This summer, I not only used it but became a developer of it. It is probably the most extensive C++ computational geometry library out there and I feel proud to be a part of it. I will continue contributing to CGAL, and use it in my projects.</p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?config=TeX-MML-AM_CHTML" async=""></script>