This week, at work I had to implement a search box for a software I am working on. The search box is to filter some data dynamically as user types a query. I wanted to show a clear (cross) icon at the right side of the search box so that user can clear the results instead of selecting the current query and deleting it manually. Lastly, for clarity I wanted to show a search icon on the left side of search box. The search box looks like this:

After the user enters a query a clear icon appears on the right. The clear icon is in fact a button and clicking it will clear the current search.

It is really easy to make this search box using QLineEdit. We need only the following three lines of code:

1 2 3 4 5 6 |
QLineEdit* _lineEdit = new QLineEdit(); _lineEdit->setClearButtonEnabled(true); _lineEdit->addAction(":/resources/search.ico", QLineEdit::LeadingPosition); _lineEdit->setPlaceHolderText("Search..."); // add _lineEdit to your widget |

Line 2 enables the clear button which adds the clear action and cross icon to the right. Line 3 adds another action with a search icon to the left of the QLineEdit. We don’t listen to this action as it is merely decorative. Line 4 adds a placeholder text which is shown in the QLineEdit but is cleared as soon as user starts typing.

We only connect textChanged(const QString&) signal which is emitted both when a user clicks on the cross icon and when he enters a search query.

Few weeks ago, I published SLogLib (a cross-platform logging library) on GitHub. I wrote the user manual in a readme.md file as is the standard practice at GitHub. However, since most of users don’t have markdown viewers installed on their machines they would either need to access GitHub repository or would have to convert it to more popular format such as PDF or perhaps HTML. For many users going online is becoming standard practice to access documentation but I prefer offline manuals. Thus, I wanted to ship a PDF manual along with the code.

I searched high and low for a standalone tool to convert markdown to PDF but surprisingly there are not a lot of options out there. The first tool I came across was GitPrint. It is conceptually innovative and straightforward to use with GitHub. Just add */your_user_name/repository_name* at the end of http://gitprint.com and it prints the readme.md in the repository to PDF. The PDF generated is of good quality but there are no styling options. Also, it failed to include images in the PDF so I had to kept looking. One of the frequently recommended tool is PanDoc, which is a swiss-army knife to convert files from one markup format into another. However, in my experience it doesn’t do a good job of converting markdown to PDF. Another popular tool online is a markdown-pdf package for Node.js. Since, I have no prior experience with Node.js I haven’t tried it yet.

Earlier this year, I bought a MacBook Pro and installed a markdown editor called MacDown. It is really nice tool with side-by-side rendering of markup and HTML. It can export markdown as PDF and produces very good quality PDF’s. It also supports lots of styling options as well as a CSS to customize PDF generation. In the end, I used it to generate PDF for SLogLib.

Even though I had a PDF for SLogLib, I wanted to find/build a cross-platform tool to convert markdown to PDF.

The basic idea to convert markdown to PDF is simple. First convert markdown to HTML and then print HTML to PDF. I used hoedown to convert markdown to PDF because of several reasons:

- First and foremost it is cross-platform and compiles as a standalone binary for all three main platforms: Windows, Linux, and OSX.
- MacDown uses it too and I was quite happy with its rendering.
- It supports not only standard markdown but also several non-standard extensions.

To converted HTML to PDF one of the most popular tool I came across was wkhtmltopdf. It is also cross-platform and complies into standalone binaries for all popular platforms. In fact, it is possible to download the pre-built library right from its website. Wkhtmltopdf uses a modified version of webkit shipped with Qt. It uses webkit to render the html and print to PDF. However, while testing I found that on a Windows 7 machines there is a serious problem with font kerning. It has been reported by a lot of users but I haven’t found a solution to fix it. Wkhtmltopdf would have been ideal as I could simply write a command line and/or GUI tool wrapping the functionality of hoedown and Wkhtmltopdf.

Screenshot of PDF generated from MacDown.

Screenshot of PDF generated from wkhtmltopdf.

I could not find any other standalone cross-platform tool to convert HTML to PDF. So, for now I decided to use dompdf which is written in PHP. Once I started used PHP I thought why not make it a web based tool. This would allow me to learn about SEO which I have been promising myself to learn one day :). The tools is hosted at http://markdown2pdf.com. At the moment it doesn’t appear in first five pages in Google search for “markdown to pdf” or “markdown 2 pdf”. I am playing with various SEO tools and techniques and hope to get it within first five pages.

My quest for a standalone tool is not yet complete. I will try to find a solution for wkhtmltopdf kerning issue or find another standalone cross-platform tool for converting from HTML to PDF. I will update with my findings on this blog.

Often there is a need to estimate the time it takes for a piece of code to run. This is useful not only for debugging but also for reporting the execution time of lengthy tasks to the user.

On Windows, QueryPerformanceFrequency() and QueryPerformanceCounter() can be used to determine the execution time of a code. QueryPerformanceFrequency() returns the frequency of the current performance counter in counts per second and QueryPerformanceCounter() returns a high resolution (<1µs) time stamp. Together they can be used to determine time it takes to run a piece of code is:

1 2 3 4 5 6 7 8 9 10 11 12 |
LARGE_INTEGER _frequency; QueryPerformanceFrequency(&_frequency); LARGE_INTEGER _start; QueryPerformanceCounter(&_start); // Code which takes a long time to run. LARGE_INTEGER _stop; QueryPerformanceCounter(&_stop); double _intervalInSeconds = (_stop.QuadPart - _start.QuadPart) / _frequency.QuadPart; |

On Linux, clock_gettime can be used to get a time interval with a resolution of nano-seconds. clock_gettime() requires two arguments: clockid_t and timespec structure. To build a timer, CLOCK_MONOTONIC is a good choice for clockid_t as the time is guaranteed to be monotonically increasing. timespec structure have two field: tv_sec (time in seconds) and tv_nsec (time in nanoseconds). Code to determine the time it takes to run a piece of code is:

1 2 3 4 5 6 7 8 9 |
struct timespec _start; clock_gettime(CLOCK_MONOTONIC, &_start); // Code which takes long time to run. struct timespec _stop; clock_gettime(CLOCK_MONOTONIC, &_stop); double _intervalInseconds = (_stop.tv_sec + _stop.tv_nsec*1e-9) - (_start.tv_sec + _start.tv_nsec*1e-9); |

I have written a simple class which can be user on both windows and Linux. It has the following interface:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
class Timer { public: enum TimeUnit { TimeInSeconds = 1, TimeInMilliSeconds = 1000, TimeInMicroSeconds = 1000000 }; public: Timer(); ~Timer(); // On Windows, returns true if high performance timer is available. // On Linux, always returns true. bool IsTimerAvailable(); // Start the timer. void Start(); // Stop the timer and return the time elapsed since the timer was started. double Stop(TimeUnit timeUnit = TimeInMilliSeconds); // Get the time elapsed since Start() was called. double TimeElapsedSinceStart(TimeUnit timeUnit = TimeInMilliSeconds); // Get the total time elapsed between Start() and Stop(). double TotalTimeElasped(TimeUnit timeUnit = TimeInMilliSeconds); }; |

You can download the code from the following links:

Timer.h

Timer.cpp

Timer_Unix.cpp

Timer.zip

A long time ago when I was doing PhD I was implementing a very complex geometric algorithm for computing intersection of two triangular meshes. There were bugs in code which would trigger only in certain edge cases. Since it was a GUI program using std::cout was not an option. Initially I tried writing messages to a file but soon realized it was too tedious as code was spanned across several files and I had to manually insert file names, function names, line numbers for every logging message.

A quick search on Internet revealed many logging libraries. I tried couple of them (unfortunately I can’t remember their names now) but none of them allowed customization of the output. The libraries I came across could output to variety of devices, supported multi-threading and many other fancy features but it was not possible to change the way messages was reported to the user. This was very important to me because I wanted to format my messages in a particular way so that I can easily check how my code was crashing on edge cases.

So, I wrote the first version of SLogLib sometime in 2005. It was build on a single principle that user should be in complete control of how messages are written to devices. In order to do that, SLogLib wraps all information required for logging into a structure called Message and passes it to a Formatter. The Formatter converts the Message structure to a std::string which will be outputted to the device. The Formatter must be written by the user. However, to make it easier to start using SLogLib and illustrate how to write a Formatter few Formatters are included with SLogLib.

Over past decade SLogLib has been very useful to me for a variety of projects and I hope that other can find it useful as well. SLogLib is hosted on Github under MIT license. You can clone of fork it from here: https://github.com/saurabhg17/SLogLib.

In this post I will show how to compute area of a facet in CGAL::Polyhedron_3.

ComputeFacetArea() is a thread-safe functor for computing the area of a given facet in a CGAL::Polyhedron_3. The facet must be a planar polygon with arbitrary number of sides. We need facet’s normal vector to compute it’s area. The facet normals must be be initialized using the ComputeFacetArea()’s constructor. The code for computing the facet normals is presented in this post: Computing normal of all facets in CGAL::Polyhedron_3.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
#ifndef _SMESHLIB_OPERATIONS_COMPUTEFACETAREA_H_ #define _SMESHLIB_OPERATIONS_COMPUTEFACETAREA_H_ #include "PropertyMap.h" namespace SMeshLib { namespace Operations { ; // The ComputeFacetArea is a functor (delegate) to compute the area of a facet in CGAL::Polyhdeon_3. // operator() is thread-safe. // TPolyhedron is a type of CGAL::Polyhdeon_3. // TFacetNormals is a property map associating a normal vector for each facet in the CGAL::Polyhdeon_3. template<class TPolyhedron, class TFacetNormals> struct ComputeFacetArea { public: // Redefine types from TPolyhedron for convenience. typedef typename TPolyhedron::Facet Facet; typedef typename TPolyhedron::Traits::Vector_3 Vector3; typedef typename TPolyhedron::Halfedge_around_facet_const_circulator HalfEdgeConstCirculator; // Return type of operator() required by QtConcurrent. typedef double result_type; public: ComputeFacetArea(const TFacetNormals& facetNormals_) : facetNormals(facetNormals_) {} // Compute the area of a given facet. // The formula for computing facet area, which in general is a planar polygon, is from // http://softsurfer.com/Archive/algorithm_0101/algorithm_0101.htm inline double operator() (const Facet& f) const { std::vector<Vector3> _vectors; HalfEdgeConstCirculator s = f.facet_begin(); HalfEdgeConstCirculator e = s; CGAL_For_all(s, e) { _vectors.push_back(s->vertex()->point() - CGAL::ORIGIN); } Vector3 _sumCrossProducts(CGAL::NULL_VECTOR); const size_t N = _vectors.size(); for(size_t i=0 ; i<N ; ++i) { _sumCrossProducts = _sumCrossProducts + CGAL::cross_product(_vectors[i], _vectors[(i+1)%N]); } return fabs((facetNormals.value(&f) * _sumCrossProducts) / 2.0); } public: // Facet normals are required for computing the area of a facet. const TFacetNormals& facetNormals; }; }; // End namespace Operations. }; // End namespace SMeshLib. #endif // _SMESHLIB_OPERATIONS_COMPUTEFACETAREA_H_ |

Area of a facet *f* can be computed as
double area = ComputeFacetArea(h);.

For most purposes, it is better to compute area of all facets once and cache them for later use. It is best to store the results in an associative container which associates the facet handle with the area. In the following example, I use PropertyMap which is a wrapper for std::set.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
#include "ImportOBJ.h" #include "ComputeFacetArea.h" #include "ComputeFacetNormal.h" #include "PropertyMap.h" #include "CGAL/Simple_cartesian.h" #include "CGAL/Polyhedron_items_3.h" #include "CGAL/HalfedgeDS_list.h" #include "CGAL/Polyhedron_3.h" typedef CGAL::Simple_cartesian<double> Kernel; typedef CGAL::Polyhedron_3<Kernel, CGAL::Polyhedron_items_3, CGAL::HalfedgeDS_list> CgalPolyhedron; // Compute the area of all facets in a CGAL::Polyhedron_3 and stores it in // facetAreas. // TPolyhedron is a type of CGAL::Polyhedron_3. // TFacetAreas is a property map which associates the facet area to facet in // the CGAL::Polyhdeon_3. // TFacetNormals is a property map which associates a normal vector to facet in // the CGAL::Polyhdeon_3. template<class TPolyhedron, class TFacetAreas, class TFacetNormals> void computeFacetAreas(const TPolyhedron& polyhedron, TFacetAreas* facetAreas, const TFacetNormals& facetNormals) { if(facetAreas == 0) { return; } typename TPolyhedron::Facet_const_iterator _begin = polyhedron.facets_begin(); typename TPolyhedron::Facet_const_iterator _end = polyhedron.facets_end(); SMeshLib::Operations::ComputeFacetArea<TPolyhedron, TFacetNormals> _computeFacetArea(facetNormals); CGAL_For_all(_begin, _end) { facetAreas->setValue(&*_begin, _computeFacetArea(*_begin)); } } // Compute the normal of all facets in a CGAL::Polyhedron_3 and stores it in facetNormals. // TPolyhedron is a type of CGAL::Polyhedron_3. // TFacetNormals is a property map which associates a normal vector to facet in the CGAL::Polyhdeon_3. template<class TPolyhedron, class TFacetNormals> void computeFacetNormals(const TPolyhedron& polyhedron, TFacetNormals* facetNormals) { if(facetNormals == 0) { return; } typename TPolyhedron::Facet_const_iterator _begin = polyhedron.facets_begin(); typename TPolyhedron::Facet_const_iterator _end = polyhedron.facets_end(); SMeshLib::Operations::ComputeFacetNormal<TPolyhedron> _computeFacetNormal; CGAL_For_all(_begin, _end) { facetNormals->setValue(&*_begin, _computeFacetNormal(*_begin)); } } void testComputeFacetArea() { CgalPolyhedron _poly; SMeshLib::IO::importOBJ("Venus.obj", &_poly); typedef SMeshLib::PropertyMap<const CgalPolyhedron::Facet*, CgalPolyhedron::Traits::Vector_3> FacetNormalPM; FacetNormalPM _facetNormals; computeFacetNormals(_poly, &_facetNormals); typedef SMeshLib::PropertyMap<const CgalPolyhedron::Facet*, double> FacetAreaPM; FacetAreaPM _facetAreas; computeFacetAreas(_poly, &_facetAreas, _facetNormals); } |

ImportOBJ.h

PropertyMap.h

ComputeFacetNormal.h

ComputeFacetArea.h

TestComputeFacetArea.cpp

Venus.obj

ComputeFacetArea.zip

In this post I will show how to compute normal vector at a vector in CGAL::Polyhedron_3.

ComputeVertexNormal() is a thread-safe functor for computing the normal vector at a given vertex in a CGAL::Polyhedron_3. The normal vector at a vertex is the average of the normal vectors of all facets incident on the vertex. The facet normals must be be initialized using the ComputeVertexNormal()’s constructor. The code for computing the facet normals is presented in this post: Computing normal of all facets in CGAL::Polyhedron_3.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
#ifndef _SMESHLIB_OPERATIONS_COMPUTEVERTEXNORMAL_H_ #define _SMESHLIB_OPERATIONS_COMPUTEVERTEXNORMAL_H_ #include "PropertyMap.h" namespace SMeshLib { namespace Operations { ; // The ComputeVertexNormal is a functor (delegate) to compute the normal of a vertex in CGAL::Polyhdeon_3. // operator() is thread-safe. // TPolyhedron is a type of CGAL::Polyhdeon_3. // TFacetNormals is a property map which associates a normal vector to each facet in the CGAL::Polyhdeon_3. // The vertex normal is the average of all facet normals incident on it. template<class TPolyhedron, class TFacetNormals> struct ComputeVertexNormal { public: // Redefine types from TPoly for convenience. typedef typename TPolyhedron::Vertex Vertex; typedef typename TPolyhedron::Facet Facet; typedef typename TPolyhedron::Traits::Vector_3 Vector3; typedef typename TPolyhedron::Halfedge_around_vertex_const_circulator HalfEdgeConstCirculator; // Return type of operator() required by QtConcurrent. typedef Vector3 result_type; public: ComputeVertexNormal(const TFacetNormals& facetNormals_) : facetNormals(facetNormals_) {} // Compute normal of the given vertex. inline Vector3 operator() (const Vertex& v) const { Vector3 n = CGAL::NULL_VECTOR; HalfEdgeConstCirculator s = v.vertex_begin(); HalfEdgeConstCirculator e = s; CGAL_For_all(s, e) { // Border edge doesn't have facet and hence no normal. if(!s->is_border()) { n = n + facetNormals.value(&*(s->facet())); } } return n/std::sqrt(n*n); } public: const TFacetNormals& facetNormals; }; }; // End namespace Operations. }; // End namespace SMeshLib. #endif // _SMESHLIB_OPERATIONS_COMPUTEVERTEXNORMAL_H_ |

Normal vector at a vertex *v* can be computed as
Vector3 normal = ComputeVertexNormal(f); .

For most purposes, it is better to compute area of all facets once and cache them for later use. It is best to store the results in an associative container which associates the facet handle with the area. In the following example, I use PropertyMap which is a wrapper for std::set.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
#include "ImportOBJ.h" #include "ComputeFacetNormal.h" #include "ComputeVertexNormal.h" #include "PropertyMap.h" #include "CGAL/Simple_cartesian.h" #include <CGAL/Polyhedron_items_3.h> #include "CGAL/HalfedgeDS_list.h" #include "CGAL/Polyhedron_3.h" typedef CGAL::Simple_cartesian<double> Kernel; typedef CGAL::Polyhedron_3<Kernel, CGAL::Polyhedron_items_3, CGAL::HalfedgeDS_list> CgalPolyhedron; // Compute the normal of all vertices in a CGAL::Polyhedron_3 and stores it in // vertexNormals. // TPolyhedron is a type of CGAL::Polyhedron_3. // TVertexNormals is a property map which associates a normal vector to vertex // in the CGAL::Polyhdeon_3. // TFacetNormals is a property map which associates a normal vector to facet // in the CGAL::Polyhdeon_3. template<class TPolyhedron, class TVertexNormals, class TFacetNormals> void computeVertexNormals(const TPolyhedron& polyhedron, TVertexNormals* vertexNormals, const TFacetNormals& facetNormals) { if(vertexNormals == 0) { return; } typename TPolyhedron::Vertex_const_iterator _begin = polyhedron.vertices_begin(); typename TPolyhedron::Vertex_const_iterator _end = polyhedron.vertices_end(); SMeshLib::Operations::ComputeVertexNormal<TPolyhedron, TFacetNormals> _computeVertexNormal(facetNormals); CGAL_For_all(_begin, _end) { vertexNormals->setValue(&*_begin, _computeVertexNormal(*_begin)); } } // Compute the normal of all facets in a CGAL::Polyhedron_3 and stores it in facetNormals. // TPolyhedron is a type of CGAL::Polyhedron_3. // TFacetNormals is a property map which associates a normal vector to facet in the CGAL::Polyhdeon_3. template<class TPolyhedron, class TFacetNormals> void computeFacetNormals(const TPolyhedron& polyhedron, TFacetNormals* facetNormals) { if(facetNormals == 0) { return; } typename TPolyhedron::Facet_const_iterator _begin = polyhedron.facets_begin(); typename TPolyhedron::Facet_const_iterator _end = polyhedron.facets_end(); SMeshLib::Operations::ComputeFacetNormal<TPolyhedron> _computeFacetNormal; CGAL_For_all(_begin, _end) { facetNormals->setValue(&*_begin, _computeFacetNormal(*_begin)); } } void testComputeVertexNormal() { CgalPolyhedron _poly; SMeshLib::IO::importOBJ("Venus.obj", &_poly); typedef SMeshLib::PropertyMap<const CgalPolyhedron::Facet*, CgalPolyhedron::Traits::Vector_3> FacetNormalPM; FacetNormalPM _facetNormals; computeFacetNormals(_poly, &_facetNormals); typedef SMeshLib::PropertyMap<const CgalPolyhedron::Vertex*, CgalPolyhedron::Traits::Vector_3> VertexNormalPM; VertexNormalPM _vertexNormals; computeVertexNormals(_poly, &_vertexNormals, _facetNormals); } |

ImportOBJ.h

PropertyMap.h

ComputeFacetNormal.h

ComputeVertexNormal.h

TestComputeVertexNormal.cpp

Venus.obj

ComputeVertexNormal.zip

In this post I will show how to compute normal vector of a facet in CGAL::Polyhedron_3.

ComputeFacetNormal() is a thread-safe functor for computing the normal vector of a given facet in a CGAL::Polyhedron_3. The facet must be a planar polygon with arbitrary number of sides.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#ifndef _SMESHLIB_OPERATIONS_COMPUTEFACETNORMAL_H_ #define _SMESHLIB_OPERATIONS_COMPUTEFACETNORMAL_H_ namespace SMeshLib { namespace Operations { ; // The ComputeFacetNormal is a functor (delegate) to compute the normal of a facet in CGAL::Polyhdeon_3. // ComputeFacetNormal is thread-safe. // TPolyhedron is a type of CGAL::Polyhdeon_3. template<class TPolyhedron> struct ComputeFacetNormal { public: // Redefine types from TPolyhedron for convenience. typedef typename TPolyhedron::Facet Facet; typedef typename TPolyhedron::Halfedge_const_handle HalfedgeConstHandle; typedef typename TPolyhedron::Traits::Point_3 Point3; typedef typename TPolyhedron::Traits::Vector_3 Vector3; // Return type of operator() required by QtConcurrent. typedef Vector3 result_type; public: // Compute normal of the given facet. // Facet can be triangle, quadrilateral or a polygon as long as its planar. // Use first three vertices to compute the normal. inline Vector3 operator() (const Facet& f) const { HalfedgeConstHandle h = f.halfedge(); Point3 p1 = h->vertex()->point(); Point3 p2 = h->next()->vertex()->point(); Point3 p3 = h->next()->next()->vertex()->point(); Vector3 n = CGAL::cross_product(p2-p1, p3-p1); return n / std::sqrt(n*n); } }; }; // End namespace Operations. }; // End namespace SMeshLib. #endif // _SMESHLIB_OPERATIONS_COMPUTEFACETNORMAL_H_ |

Normal vector of a facet *f* can be computed as
Vector3 normal = ComputeFacetNormal(f);.

For most purposes, it is better to compute area of all facets once and cache them for later use. It is best to store the results in an associative container which associates the facet handle with the area. In the following example, I use PropertyMap which is a wrapper for std::set.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
#include "ImportOBJ.h" #include "ComputeFacetNormal.h" #include "PropertyMap.h" #include "CGAL/Simple_cartesian.h" #include "CGAL/Polyhedron_items_3.h" #include "CGAL/HalfedgeDS_list.h" #include "CGAL/Polyhedron_3.h" typedef CGAL::Simple_cartesian<double> Kernel; typedef CGAL::Polyhedron_3<Kernel, CGAL::Polyhedron_items_3, CGAL::HalfedgeDS_list> CgalPolyhedron; // Compute the normal of all facets in a CGAL::Polyhedron_3 and stores it in // facetNormals. // TPolyhedron is a type of CGAL::Polyhedron_3. // TFacetNormals is a property map which associates a normal vector to facet in // the CGAL::Polyhdeon_3. template<class TPolyhedron, class TFacetNormals> void computeFacetNormals(const TPolyhedron& polyhedron, TFacetNormals* facetNormals) { if(facetNormals == 0) { return; } typename TPolyhedron::Facet_const_iterator _begin = polyhedron.facets_begin(); typename TPolyhedron::Facet_const_iterator _end = polyhedron.facets_end(); SMeshLib::Operations::ComputeFacetNormal<TPolyhedron> _computeFacetNormal; CGAL_For_all(_begin, _end) { facetNormals->setValue(&*_begin, _computeFacetNormal(*_begin)); } } void testComputeFacetNormal() { CgalPolyhedron _poly; SMeshLib::IO::importOBJ("Venus.obj", &_poly); typedef SMeshLib::PropertyMap<const CgalPolyhedron::Facet*, CgalPolyhedron::Traits::Vector_3> FacetNormalPM; FacetNormalPM _facetNormals; computeFacetNormals(_poly, &_facetNormals); } |

ImportOBJ.h

PropertyMap.h

ComputeFacetNormal.h

TestComputeFacetNormal.cpp

Venus.obj

ComputeFacetNormal.zip

In this post I will show how to compute edge length for a half-edge in CGAL::Polyhedron_3.

ComputeEdgeLength() is a thread-safe functor for computing the edge length of a given half-edge in a CGAL::Polyhedron_3.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#ifndef _SMESHLIB_OPERATIONS_COMPUTEEDGELENGTH_H_ #define _SMESHLIB_OPERATIONS_COMPUTEEDGELENGTH_H_ namespace SMeshLib { namespace Operations { ; // The ComputeEdgeLength is a functor (delegate) to compute the length of a halfedge in CGAL::Polyhdeon_3. // ComputeEdgeLength is thread-safe. // TPolyhedron is a type of CGAL::Polyhdeon_3. template<class TPolyhedron> struct ComputeEdgeLength { public: // Redefine types from TPolyhedron for convenience. typedef typename TPolyhedron::Point_3 Point3; typedef typename TPolyhedron::Halfedge Halfedge; // Return type of operator() required by QtConcurrent. typedef double result_type; public: // Compute edge length of a given half edge. inline double operator () (const Halfedge& h) { const Point3& p = h.prev()->vertex()->point(); const Point3& q = h.vertex()->point(); return CGAL::sqrt(CGAL::squared_distance(p, q)); } }; }; // End namespace Operations. }; // End namespace SMeshLib. #endif // _SMESHLIB_OPERATIONS_COMPUTEEDGELENGTH_H_ |

Edge length of a half-edge *h* can be computed as
double length = ComputeEdgeLength(h);.

For most purposes, it is better to compute length of all half-edges once and cache them for later use. It is best to store the results in an associative container which associates the half-edge handle with the edge length. In the following example, I use PropertyMap which is a wrapper for std::set.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
#include "ImportOBJ.h" #include "ComputeEdgeLength.h" #include "PropertyMap.h" #include "CGAL/Simple_cartesian.h" #include "CGAL/Polyhedron_items_3.h" #include "CGAL/HalfedgeDS_list.h" #include "CGAL/Polyhedron_3.h" typedef CGAL::Simple_cartesian<double> Kernel; typedef CGAL::Polyhedron_3<Kernel, CGAL::Polyhedron_items_3, CGAL::HalfedgeDS_list> CgalPolyhedron; // Compute the length of all half-edges in a CGAL::Polyhedron_3 and stores it // in edgeLengths. // TPolyhedron is a type of CGAL::Polyhedron_3. // TEdgeLengths is a property map which associates the edge length to half-edge // in the CGAL::Polyhdeon_3. template<class TPolyhedron, class TEdgeLengths> void computeEdgeLengths(const TPolyhedron& polyhedron, TEdgeLengths* edgeLengths) { if(edgeLengths == 0) { return; } typename TPolyhedron::Halfedge_const_iterator _begin = polyhedron.halfedges_begin(); typename TPolyhedron::Halfedge_const_iterator _end = polyhedron.halfedges_end(); SMeshLib::Operations::ComputeEdgeLength<TPolyhedron> _computeEdgeLength; CGAL_For_all(_begin, _end) { edgeLengths->setValue(&*_begin, _computeEdgeLength(*_begin)); } } void testComputeEdgeLength() { CgalPolyhedron _poly; SMeshLib::IO::importOBJ("Venus.obj", &_poly); typedef SMeshLib::PropertyMap<const CgalPolyhedron::Halfedge*, double> EdgeLengthPM; EdgeLengthPM _edgeLengths; computeEdgeLengths(_poly, &_edgeLengths); } |

ImportOBJ.h

PropertyMap.h

ComputeEdgeLength.h

TestComputeEdgeLength.cpp

Venus.obj

ImportOBJ.zip

CGAL provides high quality generic half-edge data structure for representing polyhedral surfaces as well as many algorithms for mesh processing. However, CGAL doesn’t have any in-build support for building a polyhedron from Wavefront OBJ or PLY file. The following code is a basic OBJ file loader which reads vertex coordinates and faces (can be polygons) from OBJ file. Note that it doesn’t read vertex normals, face normals, or texture coordinates. Code is well commented and should be fairly obvious.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
#ifndef _SMESHLIB_IO_IMPORTOBJ_H_ #define _SMESHLIB_IO_IMPORTOBJ_H_ #include "CGAL/Polyhedron_incremental_builder_3.h" #include "CGAL/Modifier_base.h" #include "CGAL/exceptions.h" #include <string> #include <fstream> #include <exception> namespace SMeshLib { namespace IO { ; // The BuildCgalPolyhedronFromObj class builds a CGAL::Polyhedron_3 from Wavefront OBJ file. // This is very simple reader and only reads vertex coordinates and vertex index for faces. // Faces can be polygons and doesn't have to be triangles. template<class HDS> class BuildCgalPolyhedronFromObj : public CGAL::Modifier_base<HDS> { public: BuildCgalPolyhedronFromObj(const std::string& fileName) : mFileName(fileName) {} void operator() (HDS& hds) { typedef typename HDS::Vertex Vertex; typedef typename Vertex::Point Point; // Open obj file for reading. std::ifstream _file(mFileName.c_str()); if(_file) { return; } // Count the number of vertices and facets. // This is used to reserve memory in HDS. std::string _line; int _numVertices = 0; int _numFacets = 0; while(_file.good()) { std::getline(_file, _line); if(_line.size() > 1) { if(_line[0]=='v' && _line[1]==' ') {++_numVertices;} if(_line[0]=='f' && _line[1]==' ') {++_numFacets;} } } // Rewind file to beginning for reading data. if(!_file.good()) { _file.clear(); } _file.seekg(0); // Postcondition: hds is a valid polyhedral surface. CGAL::Polyhedron_incremental_builder_3<HDS> B(hds, true); // Load the data from OBJ file to HDS. B.begin_surface(_numVertices, _numFacets, int((_numVertices + _numFacets - 2)*2.1)); std::string _token; while(!_file.eof()) { _token = ""; // Reset token. _file >> _token; // if token is v then its a vertex. if(_token=="v") { double x, y, z; _file >> x >> y >> z; B.add_vertex(Point(x, y, z)); } // There are 4 type of facets. // a only vertex index. // a/b vertex and texture index. // a/b/c vertex, texture and normal index. // a//c vertex and normal index. else if(_token=="f") { // Read the remaining line for the facet. std::string _line; std::getline(_file, _line); // Split the line into facet's vertices. // The length of _vertices is equal to the number of vertices for this face. std::istringstream _stream(_line); std::vector<std::string> _vertices; std::copy(std::istream_iterator<std::string>(_stream), std::istream_iterator<std::string>(), std::back_inserter(_vertices)); // For each vertex read only the first number, which is the vertex index. B.begin_facet(); for(size_t i=0 ; i<_vertices.size() ; ++i) { std::string::size_type _pos = _vertices[i].find('/', 0); std::string _indexStr = _vertices[i].substr(0, _pos); B.add_vertex_to_facet(stoi(_indexStr)-1); // -1 is because OBJ file uses 1 based index. } B.end_facet(); } } _file.close(); B.end_surface(); } private: std::string mFileName; }; // Import a OBJ file given by fileName to polyhedron. // TPoly is a type of CGAL::Polyhdeon_3. template<class TPoly> void importOBJ(const std::string& fileName, TPoly* polyhedron) { if(polyhedron) { try { // Build Polyhedron_3 from the OBJ file. BuildCgalPolyhedronFromObj<TPoly::HalfedgeDS> _buildPolyhedron(fileName); // Calls is_valid at the end. Throws an exception in debug mode if polyhedron is not // manifold. polyhedron->delegate(_buildPolyhedron); // CGAL::Assert_exception is thrown in the debug mode when // CGAL::Polyhedron_incremental_builder_3 is destroyed in BuildCgalPolyhedronFromObj. // However, in the release mode assertions is disabled and hence no exception is thrown. // Thus for uniform error reporting, if the polyhedron is not valid then throw a dummy // exception in release mode. if(!polyhedron->is_valid()) { throw CGAL::Assertion_exception("", "", "", 0, ""); } } catch(const CGAL::Assertion_exception&) { std::string _msg = "SMeshLib::importOBJ: Error loading " + fileName; throw std::exception(_msg.c_str()); } } } }; // End namespace IO. }; // End namespace SMeshLib. #endif // _SMESHLIB_IO_IMPORTOBJ_H_ |

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include "ImportOBJ.h" #include "CGAL/Simple_cartesian.h" #include "CGAL/Polyhedron_items_3.h" #include "CGAL/HalfedgeDS_list.h" #include "CGAL/Polyhedron_3.h" typedef CGAL::Simple_cartesian<double> Kernel; typedef CGAL::Polyhedron_3<Kernel, CGAL::Polyhedron_items_3, CGAL::HalfedgeDS_list> CgalPolyhedron; void testImportOBJ() { CgalPolyhedron _poly; SMeshLib::IO::importOBJ("Venus.obj", &_poly); } |