|
@@ -0,0 +1,721 @@
|
|
|
+/*
|
|
|
+ Copyright (c) 2018, Sebastian Reiter (s.b.reiter@gmail.com)
|
|
|
+ All rights reserved.
|
|
|
+
|
|
|
+ Redistribution and use in source and binary forms, with or without
|
|
|
+ modification, are permitted provided that the following conditions are met:
|
|
|
+ * Redistributions of source code must retain the above copyright
|
|
|
+ notice, this list of conditions and the following disclaimer.
|
|
|
+ * Redistributions in binary form must reproduce the above copyright
|
|
|
+ notice, this list of conditions and the following disclaimer in the
|
|
|
+ documentation and/or other materials provided with the distribution.
|
|
|
+
|
|
|
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
|
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
|
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
|
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
|
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
|
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
|
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
+*/
|
|
|
+
|
|
|
+
|
|
|
+/** \file
|
|
|
+ * \brief Provides functions to read **stl files** into user provided arrays
|
|
|
+ *
|
|
|
+ * The central function of this file is `ReadStlFile(...)`. It automatically recognizes
|
|
|
+ * whether an *ASCII* or a *Binary* file is to be read. It identifies matching corner
|
|
|
+ * coordinates of triangles with each other, so that the resulting coordinate
|
|
|
+ * array does not contain the same coordinate-triple multiple times.
|
|
|
+ *
|
|
|
+ * The function operates on template container types. Those containers should
|
|
|
+ * have similar interfaces as `std::vector` and operate on `float` or `double` types
|
|
|
+ * (`TNumberContainer`) or on `int` or `size_t` types (`TIndexContainer`).
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * A conveniance class `StlMesh` is also provided, which makes accessing triangle
|
|
|
+ * corners and corresponding corner coordinates much more easy. It still provides
|
|
|
+ * raw access to the underlying data arrays.
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * ### Usage example 1 (using `StlMesh`):
|
|
|
+ *
|
|
|
+ * \code
|
|
|
+ * try {
|
|
|
+ * stl_reader::StlMesh <float, unsigned int> mesh ("geometry.stl");
|
|
|
+ *
|
|
|
+ * for(size_t itri = 0; itri < mesh.num_tris(); ++itri) {
|
|
|
+ * std::cout << "coordinates of triangle " << itri << ": ";
|
|
|
+ * for(size_t icorner = 0; icorner < 3; ++icorner) {
|
|
|
+ * const float* c = mesh.tri_corner_coords (itri, icorner);
|
|
|
+ * // or alternatively:
|
|
|
+ * // float* c = mesh.vrt_coords (mesh.tri_corner_ind (itri, icorner));
|
|
|
+ * std::cout << "(" << c[0] << ", " << c[1] << ", " << c[2] << ") ";
|
|
|
+ * }
|
|
|
+ * std::cout << std::endl;
|
|
|
+ *
|
|
|
+ * float* n = mesh.tri_normal (itri);
|
|
|
+ * std::cout << "normal of triangle " << itri << ": "
|
|
|
+ * << "(" << n[0] << ", " << n[1] << ", " << n[2] << ")\n";
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ * catch (std::exception& e) {
|
|
|
+ * std::cout << e.what() << std::endl;
|
|
|
+ * }
|
|
|
+ * \endcode
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * ### Usage example 2 (using `StlMesh` and *solids*)
|
|
|
+ *
|
|
|
+ * \code
|
|
|
+ * try {
|
|
|
+ * stl_reader::StlMesh <float, unsigned int> mesh ("geometry.stl");
|
|
|
+ *
|
|
|
+ * for(size_t isolid = 0; isolid < mesh.num_solids(); ++isolid) {
|
|
|
+ * std::cout << "solid " << isolid << std::endl;
|
|
|
+ *
|
|
|
+ * for(size_t itri = mesh.solid_tris_begin(isolid);
|
|
|
+ * itri < mesh.solid_tris_end(isolid); ++itri)
|
|
|
+ * {
|
|
|
+ * const float* n = mesh.tri_normal (itri);
|
|
|
+ * std::cout << "normal of triangle " << itri << ": "
|
|
|
+ * << "(" << n[0] << ", " << n[1] << ", " << n[2] << ")\n";
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ * catch (std::exception& e) {
|
|
|
+ * std::cout << e.what() << std::endl;
|
|
|
+ * }
|
|
|
+ * \endcode
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * ### Usage example 3 (using raw data arrays)
|
|
|
+ *
|
|
|
+ * \code
|
|
|
+ * std::vector<float> coords, normals;
|
|
|
+ * std::vector<unsigned int> tris, solids;
|
|
|
+ *
|
|
|
+ * try {
|
|
|
+ * stl_reader::ReadStlFile ("geometry.stl", coords, normals, tris, solids);
|
|
|
+ * const size_t numTris = tris.size() / 3;
|
|
|
+ * for(size_t itri = 0; itri < numTris; ++itri) {
|
|
|
+ * std::cout << "coordinates of triangle " << itri << ": ";
|
|
|
+ * for(size_t icorner = 0; icorner < 3; ++icorner) {
|
|
|
+ * float* c = &coords[3 * tris [3 * itri + icorner]];
|
|
|
+ * std::cout << "(" << c[0] << ", " << c[1] << ", " << c[2] << ") ";
|
|
|
+ * }
|
|
|
+ * std::cout << std::endl;
|
|
|
+ *
|
|
|
+ * float* n = &normals [3 * itri];
|
|
|
+ * std::cout << "normal of triangle " << itri << ": "
|
|
|
+ * << "(" << n[0] << ", " << n[1] << ", " << n[2] << ")\n";
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ * catch (std::exception& e) {
|
|
|
+ * std::cout << e.what() << std::endl;
|
|
|
+ * }
|
|
|
+ * \endcode
|
|
|
+ *
|
|
|
+ * If you do not want to use exceptions, you may define the macro
|
|
|
+ * STL_READER_NO_EXCEPTIONS before including 'stl_reader.h'. In that case,
|
|
|
+ * functions will return `false` if an error occurred.
|
|
|
+ */
|
|
|
+
|
|
|
+#ifndef __H__STL_READER
|
|
|
+#define __H__STL_READER
|
|
|
+
|
|
|
+#include <algorithm>
|
|
|
+#include <exception>
|
|
|
+#include <fstream>
|
|
|
+#include <sstream>
|
|
|
+#include <vector>
|
|
|
+
|
|
|
+#ifdef STL_READER_NO_EXCEPTIONS
|
|
|
+ #define STL_READER_THROW(msg) return false;
|
|
|
+ #define STL_READER_COND_THROW(cond, msg) if(cond) return false;
|
|
|
+#else
|
|
|
+ /// Throws an std::runtime_error with the given message.
|
|
|
+ #define STL_READER_THROW(msg) {std::stringstream ss; ss << msg; throw(std::runtime_error(ss.str()));}
|
|
|
+
|
|
|
+ /// Throws an std::runtime_error with the given message, if the given condition evaluates to true.
|
|
|
+ #define STL_READER_COND_THROW(cond, msg) if(cond){std::stringstream ss; ss << msg; throw(std::runtime_error(ss.str()));}
|
|
|
+#endif
|
|
|
+
|
|
|
+
|
|
|
+namespace stl_reader {
|
|
|
+
|
|
|
+/// Reads an ASCII or binary stl file into several arrays
|
|
|
+/** Reads a stl file and writes its coordinates, normals and triangle-corner-indices
|
|
|
+ * to the provided containers. It also fills a container solidRangesOut, which
|
|
|
+ * provides the triangle ranges for individual solids.
|
|
|
+ *
|
|
|
+ * Double vertex entries are removed on the fly, so that triangle corners with
|
|
|
+ * equal coordinates are represented by a single coordinate entry in coordsOut.
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * \param filename [in] The name of the file which shall be read
|
|
|
+ *
|
|
|
+ * \param coordsOut [out] Coordinates are written to this container. On termination,
|
|
|
+ * it has size numVertices * 3. Each triple of entries forms a
|
|
|
+ * 3d coordinate. The type TNumberContainer should have the same
|
|
|
+ * interface as std::vector<float>.
|
|
|
+ *
|
|
|
+ * \param normalsOut [out] Face normals are written to this container. On termination,
|
|
|
+ * it has size numFaces * 3. Each triple of entries forms a
|
|
|
+ * 3d normal. The type TNumberContainer should have the same
|
|
|
+ * interface as std::vector<float>.
|
|
|
+ *
|
|
|
+ * \param trisOut [out] Triangle corner indices are written to this container.
|
|
|
+ * On termination, it has size numFaces * 3. Each triple of
|
|
|
+ * entries defines a triangle. The type TIndexContainer should
|
|
|
+ * have the same interface as std::vector<size_t>.
|
|
|
+ * Multiply corner indices from trisOut by 3 to obtain the index
|
|
|
+ * of the first coordinate of that corner in coordsOut.
|
|
|
+ *
|
|
|
+ * \param solidRangesOut [out] On termination, it holds the ranges of triangle indices
|
|
|
+ * for each solid. It has the size numSolids + 1. Each entry
|
|
|
+ * can be interpreted as a end/begin triangle index for the
|
|
|
+ * previous/next solid. E.g., if there are 3 solids, the
|
|
|
+ * returned array would look like this:
|
|
|
+ * \code
|
|
|
+ * {sol1Begin, sol1End/sol2Begin, sol2End/sol3Begin, sol3End}.
|
|
|
+ * \endcode
|
|
|
+ * The type TIndexContainer should have the same interface
|
|
|
+ * as std::vector<size_t>.
|
|
|
+ *
|
|
|
+ * \returns true if the file was successfully read into the provided container.
|
|
|
+ */
|
|
|
+template <class TNumberContainer1, class TNumberContainer2,
|
|
|
+ class TIndexContainer1, class TIndexContainer2>
|
|
|
+bool ReadStlFile(const char* filename,
|
|
|
+ TNumberContainer1& coordsOut,
|
|
|
+ TNumberContainer2& normalsOut,
|
|
|
+ TIndexContainer1& trisOut,
|
|
|
+ TIndexContainer2& solidRangesOut);
|
|
|
+
|
|
|
+
|
|
|
+/// Reads an ASCII stl file into several arrays
|
|
|
+/** \copydetails ReadStlFile
|
|
|
+ * \sa ReadStlFile, ReadStlFile_ASCII
|
|
|
+ */
|
|
|
+template <class TNumberContainer1, class TNumberContainer2,
|
|
|
+ class TIndexContainer1, class TIndexContainer2>
|
|
|
+bool ReadStlFile_ASCII(const char* filename,
|
|
|
+ TNumberContainer1& coordsOut,
|
|
|
+ TNumberContainer2& normalsOut,
|
|
|
+ TIndexContainer1& trisOut,
|
|
|
+ TIndexContainer2& solidRangesOut);
|
|
|
+
|
|
|
+/// Reads a binary stl file into several arrays
|
|
|
+/** \copydetails ReadStlFile
|
|
|
+ * \todo support systems with big endianess
|
|
|
+ * \sa ReadStlFile, ReadStlFile_BINARY
|
|
|
+ */
|
|
|
+template <class TNumberContainer1, class TNumberContainer2,
|
|
|
+ class TIndexContainer1, class TIndexContainer2>
|
|
|
+bool ReadStlFile_BINARY(const char* filename,
|
|
|
+ TNumberContainer1& coordsOut,
|
|
|
+ TNumberContainer2& normalsOut,
|
|
|
+ TIndexContainer1& trisOut,
|
|
|
+ TIndexContainer2& solidRangesOut);
|
|
|
+
|
|
|
+/// Determines whether a stl file has ASCII format
|
|
|
+/** The underlying mechanism is simply checks whether the provided file starts
|
|
|
+ * with the keyword solid. This should work for many stl files, but may
|
|
|
+ * fail, of course.
|
|
|
+ */
|
|
|
+inline bool StlFileHasASCIIFormat(const char* filename);
|
|
|
+
|
|
|
+
|
|
|
+/// convenience mesh class which makes accessing the stl data more easy
|
|
|
+template <class TNumber = float, class TIndex = unsigned int>
|
|
|
+class StlMesh {
|
|
|
+public:
|
|
|
+ /// initializes an empty mesh
|
|
|
+ StlMesh ()
|
|
|
+ {
|
|
|
+ solids.resize (2, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// initializes the mesh from the stl-file specified through filename
|
|
|
+ /** \{ */
|
|
|
+ StlMesh (const char* filename)
|
|
|
+ {
|
|
|
+ read_file (filename);
|
|
|
+ }
|
|
|
+
|
|
|
+ StlMesh (const std::string& filename)
|
|
|
+ {
|
|
|
+ read_file (filename);
|
|
|
+ }
|
|
|
+ /** \} */
|
|
|
+
|
|
|
+ /// fills the mesh with the contents of the specified stl-file
|
|
|
+ /** \{ */
|
|
|
+ bool read_file (const char* filename)
|
|
|
+ {
|
|
|
+ bool res = false;
|
|
|
+
|
|
|
+ #ifndef STL_READER_NO_EXCEPTIONS
|
|
|
+ try {
|
|
|
+ #endif
|
|
|
+
|
|
|
+ res = ReadStlFile (filename, coords, normals, tris, solids);
|
|
|
+
|
|
|
+ #ifndef STL_READER_NO_EXCEPTIONS
|
|
|
+ } catch (std::exception& e) {
|
|
|
+ #else
|
|
|
+ if (!res) {
|
|
|
+ #endif
|
|
|
+
|
|
|
+ coords.clear ();
|
|
|
+ normals.clear ();
|
|
|
+ tris.clear ();
|
|
|
+ solids.clear ();
|
|
|
+ STL_READER_THROW (e.what());
|
|
|
+ }
|
|
|
+
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool read_file (const std::string& filename)
|
|
|
+ {
|
|
|
+ return read_file (filename.c_str());
|
|
|
+ }
|
|
|
+ /** \} */
|
|
|
+
|
|
|
+ /// returns the number of vertices in the mesh
|
|
|
+ size_t num_vrts () const
|
|
|
+ {
|
|
|
+ return coords.size() / 3;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// returns an array of 3 floating point values, one for each coordinate of the vertex
|
|
|
+ const TNumber* vrt_coords (const size_t vi) const
|
|
|
+ {
|
|
|
+ return &coords[vi * 3];
|
|
|
+ }
|
|
|
+
|
|
|
+ /// returns the number of triangles in the mesh
|
|
|
+ size_t num_tris () const
|
|
|
+ {
|
|
|
+ return tris.size() / 3;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// returns an array of 3 indices, one for each corner vertex of the triangle
|
|
|
+ const TIndex* tri_corner_inds (const size_t ti) const
|
|
|
+ {
|
|
|
+ return &tris [ti * 3];
|
|
|
+ }
|
|
|
+
|
|
|
+ /// returns the index of the corner with index `0<=ci<3` of triangle ti
|
|
|
+ const TIndex tri_corner_ind (const size_t ti, const size_t ci) const
|
|
|
+ {
|
|
|
+ return tris [ti * 3 + ci];
|
|
|
+ }
|
|
|
+
|
|
|
+ /** \brief returns an array of 3 floating point values, one for each
|
|
|
+ * coordinate of the specified corner of the specified tri.
|
|
|
+ * \note same result as calling on a `StlMesh mesh`:
|
|
|
+ * \code
|
|
|
+ * mesh.vrt_coords (mesh.tri_corner_ind (itri, icorner))
|
|
|
+ * \endcode
|
|
|
+ */
|
|
|
+ const TNumber* tri_corner_coords (const size_t ti, const size_t ci) const
|
|
|
+ {
|
|
|
+ return &coords[tri_corner_ind(ti, ci) * 3];
|
|
|
+ }
|
|
|
+
|
|
|
+ /// returns an array of 3 floating point values defining the normal of a tri
|
|
|
+ const TNumber* tri_normal (const size_t ti) const
|
|
|
+ {
|
|
|
+ return &normals [ti * 3];
|
|
|
+ }
|
|
|
+
|
|
|
+ /// returns the number of solids of the mesh
|
|
|
+ /** solids can be seen as a partitioning of the triangles of a mesh.
|
|
|
+ * By iterating consecutively from the index of the first triangle of a
|
|
|
+ * solid `si` (using `solid_tris_begin(si)`) to the index of the last
|
|
|
+ * triangle of a solid (using `solid_tris_end(...)-1`), one visits all
|
|
|
+ * triangles of the solid `si`.*/
|
|
|
+ size_t num_solids () const
|
|
|
+ {
|
|
|
+ if(solids.empty ())
|
|
|
+ return 0;
|
|
|
+ return solids.size () - 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// returns the index of the first triangle in the given solid
|
|
|
+ TIndex solid_tris_begin (const size_t si) const
|
|
|
+ {
|
|
|
+ return solids [si];
|
|
|
+ }
|
|
|
+
|
|
|
+ /// returns the index of the triangle behind the last triangle in the given solid
|
|
|
+ TIndex solid_tris_end (const size_t si) const
|
|
|
+ {
|
|
|
+ return solids [si + 1];
|
|
|
+ }
|
|
|
+
|
|
|
+ /// returns a pointer to the coordinate array, containing `num_vrts()*3` entries.
|
|
|
+ /** Storage layout: `x0,y0,z0,x1,y1,z1,...`
|
|
|
+ * \returns pointer to a contiguous array of numbers, or `NULL` if no coords exist.*/
|
|
|
+ const TNumber* raw_coords () const
|
|
|
+ {
|
|
|
+ if(coords.empty())
|
|
|
+ return NULL;
|
|
|
+ return &coords[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ /// returns a pointer to the normal array, containing `num_tris()*3` entries.
|
|
|
+ /** Storage layout: `nx0,ny0,nz0,nx1,ny1,nz1,...`
|
|
|
+ * \returns pointer to a contiguous array of numbers, or `NULL` if no normals exist.*/
|
|
|
+ const TNumber* raw_normals () const
|
|
|
+ {
|
|
|
+ if(normals.empty())
|
|
|
+ return NULL;
|
|
|
+ return &normals[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ /// returns a pointer to the triangle array, containing `num_tris()*3` entries.
|
|
|
+ /** Storage layout: `t0c0,t0c1,t0c2,t1c0,t1c1,t1c2,...`
|
|
|
+ * \returns pointer to a contiguous array of indices, or `NULL` if no tris exist.*/
|
|
|
+ const TIndex* raw_tris () const
|
|
|
+ {
|
|
|
+ if(tris.empty())
|
|
|
+ return NULL;
|
|
|
+ return &tris[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ /// returns a pointer to the solids array, containing `num_solids()+1` entries.
|
|
|
+ /** Storage layout: `s0begin, s0end/s1begin, s1end/s2begin, ..., sNend`
|
|
|
+ * \returns pointer to a contiguous array of indices, or `NULL` if no solids exist.*/
|
|
|
+ const TIndex* raw_solids () const
|
|
|
+ {
|
|
|
+ if(solids.empty())
|
|
|
+ return NULL;
|
|
|
+ return &solids[0];
|
|
|
+ }
|
|
|
+
|
|
|
+private:
|
|
|
+ std::vector<TNumber> coords;
|
|
|
+ std::vector<TNumber> normals;
|
|
|
+ std::vector<TIndex> tris;
|
|
|
+ std::vector<TIndex> solids;
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////////////////
|
|
|
+// IMPLEMENTATION
|
|
|
+////////////////////////////////////////////////////////////////////////////////
|
|
|
+
|
|
|
+
|
|
|
+namespace stl_reader_impl {
|
|
|
+
|
|
|
+ // a coordinate triple with an additional index. The index is required
|
|
|
+ // for RemoveDoubles, so that triangles can be reindexed properly.
|
|
|
+ template <typename number_t, typename index_t>
|
|
|
+ struct CoordWithIndex {
|
|
|
+ number_t data[3];
|
|
|
+ index_t index;
|
|
|
+
|
|
|
+ bool operator == (const CoordWithIndex& c) const
|
|
|
+ {
|
|
|
+ return (c[0] == data[0]) && (c[1] == data[1]) && (c[2] == data[2]);
|
|
|
+ }
|
|
|
+
|
|
|
+ bool operator != (const CoordWithIndex& c) const
|
|
|
+ {
|
|
|
+ return (c[0] != data[0]) || (c[1] != data[1]) || (c[2] != data[2]);
|
|
|
+ }
|
|
|
+
|
|
|
+ bool operator < (const CoordWithIndex& c) const
|
|
|
+ {
|
|
|
+ return (data[0] < c[0])
|
|
|
+ || (data[0] == c[0] && data[1] < c[1])
|
|
|
+ || (data[0] == c[0] && data[1] == c[1] && data[2] < c[2]);
|
|
|
+ }
|
|
|
+
|
|
|
+ inline number_t& operator [] (const size_t i) {return data[i];}
|
|
|
+ inline number_t operator [] (const size_t i) const {return data[i];}
|
|
|
+ };
|
|
|
+
|
|
|
+ // sorts the array coordsWithIndexInOut and copies unique indices to coordsOut.
|
|
|
+ // Triangle-corners are re-indexed on the fly and degenerated triangles are removed.
|
|
|
+ template <class TNumberContainer, class TIndexContainer>
|
|
|
+ void RemoveDoubles (TNumberContainer& uniqueCoordsOut,
|
|
|
+ TIndexContainer& trisInOut,
|
|
|
+ std::vector <CoordWithIndex<
|
|
|
+ typename TNumberContainer::value_type,
|
|
|
+ typename TIndexContainer::value_type> >
|
|
|
+ &coordsWithIndexInOut)
|
|
|
+ {
|
|
|
+ using namespace std;
|
|
|
+
|
|
|
+ typedef typename TNumberContainer::value_type number_t;
|
|
|
+ typedef typename TIndexContainer::value_type index_t;
|
|
|
+
|
|
|
+ sort (coordsWithIndexInOut.begin(), coordsWithIndexInOut.end());
|
|
|
+
|
|
|
+ // first count unique indices
|
|
|
+ index_t numUnique = 1;
|
|
|
+ for(size_t i = 1; i < coordsWithIndexInOut.size(); ++i){
|
|
|
+ if(coordsWithIndexInOut[i] != coordsWithIndexInOut[i - 1])
|
|
|
+ ++numUnique;
|
|
|
+ }
|
|
|
+
|
|
|
+ uniqueCoordsOut.resize (numUnique * 3);
|
|
|
+ vector<index_t> newIndex (coordsWithIndexInOut.size());
|
|
|
+
|
|
|
+ // copy unique coordinates to 'uniqueCoordsOut' and create an index-map
|
|
|
+ // 'newIndex', which allows to re-index triangles later on.
|
|
|
+ index_t curInd = 0;
|
|
|
+ newIndex[0] = 0;
|
|
|
+ for(index_t i = 0; i < 3; ++i)
|
|
|
+ uniqueCoordsOut[i] = coordsWithIndexInOut[0][i];
|
|
|
+
|
|
|
+ for(size_t i = 1; i < coordsWithIndexInOut.size(); ++i){
|
|
|
+ const CoordWithIndex <number_t, index_t> c = coordsWithIndexInOut[i];
|
|
|
+ if(c != coordsWithIndexInOut[i - 1]){
|
|
|
+ ++curInd;
|
|
|
+ for(index_t j = 0; j < 3; ++j)
|
|
|
+ uniqueCoordsOut[curInd * 3 + j] = coordsWithIndexInOut[i][j];
|
|
|
+ }
|
|
|
+
|
|
|
+ newIndex[c.index] = static_cast<index_t> (curInd);
|
|
|
+ }
|
|
|
+
|
|
|
+ // re-index triangles, so that they refer to 'uniqueCoordsOut'
|
|
|
+ // make sure to only add triangles which refer to three different indices
|
|
|
+ index_t numUniqueTriInds = 0;
|
|
|
+ for(index_t i = 0; i < trisInOut.size(); i+=3){
|
|
|
+ int ni[3];
|
|
|
+ for(int j = 0; j < 3; ++j)
|
|
|
+ ni[j] = newIndex[trisInOut[i+j]];
|
|
|
+
|
|
|
+ if((ni[0] != ni[1]) && (ni[0] != ni[2]) && (ni[1] != ni[2])){
|
|
|
+ for(int j = 0; j < 3; ++j)
|
|
|
+ trisInOut[numUniqueTriInds + j] = ni[j];
|
|
|
+ numUniqueTriInds += 3;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(numUniqueTriInds < trisInOut.size())
|
|
|
+ trisInOut.resize (numUniqueTriInds);
|
|
|
+ }
|
|
|
+}// end of namespace stl_reader_impl
|
|
|
+
|
|
|
+
|
|
|
+template <class TNumberContainer1, class TNumberContainer2,
|
|
|
+ class TIndexContainer1, class TIndexContainer2>
|
|
|
+bool ReadStlFile(const char* filename,
|
|
|
+ TNumberContainer1& coordsOut,
|
|
|
+ TNumberContainer2& normalsOut,
|
|
|
+ TIndexContainer1& trisOut,
|
|
|
+ TIndexContainer2& solidRangesOut)
|
|
|
+{
|
|
|
+ if(StlFileHasASCIIFormat(filename))
|
|
|
+ return ReadStlFile_ASCII(filename, coordsOut, normalsOut, trisOut, solidRangesOut);
|
|
|
+ else
|
|
|
+ return ReadStlFile_BINARY(filename, coordsOut, normalsOut, trisOut, solidRangesOut);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+template <class TNumberContainer1, class TNumberContainer2,
|
|
|
+ class TIndexContainer1, class TIndexContainer2>
|
|
|
+bool ReadStlFile_ASCII(const char* filename,
|
|
|
+ TNumberContainer1& coordsOut,
|
|
|
+ TNumberContainer2& normalsOut,
|
|
|
+ TIndexContainer1& trisOut,
|
|
|
+ TIndexContainer2& solidRangesOut)
|
|
|
+{
|
|
|
+ using namespace std;
|
|
|
+ using namespace stl_reader_impl;
|
|
|
+
|
|
|
+ typedef typename TNumberContainer1::value_type number_t;
|
|
|
+ typedef typename TIndexContainer1::value_type index_t;
|
|
|
+
|
|
|
+ coordsOut.clear();
|
|
|
+ normalsOut.clear();
|
|
|
+ trisOut.clear();
|
|
|
+ solidRangesOut.clear();
|
|
|
+
|
|
|
+ ifstream in(filename);
|
|
|
+ STL_READER_COND_THROW(!in, "Couldn't open file " << filename);
|
|
|
+
|
|
|
+ vector<CoordWithIndex <number_t, index_t> > coordsWithIndex;
|
|
|
+
|
|
|
+ string buffer;
|
|
|
+ vector<string> tokens;
|
|
|
+ int lineCount = 1;
|
|
|
+ int maxNumTokens = 0;
|
|
|
+ size_t numFaceVrts = 0;
|
|
|
+
|
|
|
+ while(!(in.eof() || in.fail()))
|
|
|
+ {
|
|
|
+ // read the line and tokenize.
|
|
|
+ // In order to reuse memory in between lines, 'tokens' won't be cleared.
|
|
|
+ // Instead we count the number of tokens using 'tokenCount'.
|
|
|
+ getline(in, buffer);
|
|
|
+
|
|
|
+ istringstream line(buffer);
|
|
|
+ int tokenCount = 0;
|
|
|
+ while(!(line.eof() || line.fail())){
|
|
|
+ if(tokenCount >= maxNumTokens){
|
|
|
+ maxNumTokens = tokenCount + 1;
|
|
|
+ tokens.resize(maxNumTokens);
|
|
|
+ }
|
|
|
+ line >> tokens[tokenCount];
|
|
|
+ ++tokenCount;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(tokenCount > 0)
|
|
|
+ {
|
|
|
+ string& tok = tokens[0];
|
|
|
+ if(tok.compare("vertex") == 0){
|
|
|
+ if(tokenCount < 4){
|
|
|
+ STL_READER_THROW("ERROR while reading from " << filename <<
|
|
|
+ ": vertex not specified correctly in line " << lineCount);
|
|
|
+ }
|
|
|
+
|
|
|
+ // read the position
|
|
|
+ CoordWithIndex <number_t, index_t> c;
|
|
|
+ for(size_t i = 0; i < 3; ++i)
|
|
|
+ c[i] = static_cast<number_t> (atof(tokens[i+1].c_str()));
|
|
|
+ c.index = static_cast<index_t>(coordsWithIndex.size());
|
|
|
+ coordsWithIndex.push_back(c);
|
|
|
+ ++numFaceVrts;
|
|
|
+ }
|
|
|
+ else if(tok.compare("facet") == 0)
|
|
|
+ {
|
|
|
+ STL_READER_COND_THROW(tokenCount < 5,
|
|
|
+ "ERROR while reading from " << filename <<
|
|
|
+ ": triangle not specified correctly in line " << lineCount);
|
|
|
+
|
|
|
+ STL_READER_COND_THROW(tokens[1].compare("normal") != 0,
|
|
|
+ "ERROR while reading from " << filename <<
|
|
|
+ ": Missing normal specifier in line " << lineCount);
|
|
|
+
|
|
|
+ // read the normal
|
|
|
+ for(size_t i = 0; i < 3; ++i)
|
|
|
+ normalsOut.push_back (static_cast<number_t> (atof(tokens[i+2].c_str())));
|
|
|
+
|
|
|
+ numFaceVrts = 0;
|
|
|
+ }
|
|
|
+ else if(tok.compare("outer") == 0){
|
|
|
+ STL_READER_COND_THROW ((tokenCount < 2) || (tokens[1].compare("loop") != 0),
|
|
|
+ "ERROR while reading from " << filename <<
|
|
|
+ ": expecting outer loop in line " << lineCount);
|
|
|
+ }
|
|
|
+ else if(tok.compare("endfacet") == 0){
|
|
|
+ STL_READER_COND_THROW(numFaceVrts != 3,
|
|
|
+ "ERROR while reading from " << filename <<
|
|
|
+ ": bad number of vertices specified for face in line " << lineCount);
|
|
|
+
|
|
|
+ trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 3));
|
|
|
+ trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 2));
|
|
|
+ trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 1));
|
|
|
+ }
|
|
|
+ else if(tok.compare("solid") == 0){
|
|
|
+ solidRangesOut.push_back(static_cast<index_t> (trisOut.size() / 3));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ lineCount++;
|
|
|
+ }
|
|
|
+
|
|
|
+ solidRangesOut.push_back(static_cast<index_t> (trisOut.size() / 3));
|
|
|
+
|
|
|
+ RemoveDoubles (coordsOut, trisOut, coordsWithIndex);
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+template <class TNumberContainer1, class TNumberContainer2,
|
|
|
+ class TIndexContainer1, class TIndexContainer2>
|
|
|
+bool ReadStlFile_BINARY(const char* filename,
|
|
|
+ TNumberContainer1& coordsOut,
|
|
|
+ TNumberContainer2& normalsOut,
|
|
|
+ TIndexContainer1& trisOut,
|
|
|
+ TIndexContainer2& solidRangesOut)
|
|
|
+{
|
|
|
+ using namespace std;
|
|
|
+ using namespace stl_reader_impl;
|
|
|
+
|
|
|
+ typedef typename TNumberContainer1::value_type number_t;
|
|
|
+ typedef typename TIndexContainer1::value_type index_t;
|
|
|
+
|
|
|
+ coordsOut.clear();
|
|
|
+ normalsOut.clear();
|
|
|
+ trisOut.clear();
|
|
|
+ solidRangesOut.clear();
|
|
|
+
|
|
|
+ ifstream in(filename, ios::binary);
|
|
|
+ STL_READER_COND_THROW(!in, "Couldnt open file " << filename);
|
|
|
+
|
|
|
+ char stl_header[80];
|
|
|
+ in.read(stl_header, 80);
|
|
|
+ STL_READER_COND_THROW(!in, "Error while parsing binary stl header in file " << filename);
|
|
|
+
|
|
|
+ unsigned int numTris = 0;
|
|
|
+ in.read((char*)&numTris, 4);
|
|
|
+ STL_READER_COND_THROW(!in, "Couldnt determine number of triangles in binary stl file " << filename);
|
|
|
+
|
|
|
+ vector<CoordWithIndex <number_t, index_t> > coordsWithIndex;
|
|
|
+
|
|
|
+ for(unsigned int tri = 0; tri < numTris; ++tri){
|
|
|
+ float d[12];
|
|
|
+ in.read((char*)d, 12 * 4);
|
|
|
+ STL_READER_COND_THROW(!in, "Error while parsing trianlge in binary stl file " << filename);
|
|
|
+
|
|
|
+ for(int i = 0; i < 3; ++i)
|
|
|
+ normalsOut.push_back (d[i]);
|
|
|
+
|
|
|
+ for(size_t ivrt = 1; ivrt < 4; ++ivrt){
|
|
|
+ CoordWithIndex <number_t, index_t> c;
|
|
|
+ for(size_t i = 0; i < 3; ++i)
|
|
|
+ c[i] = d[ivrt * 3 + i];
|
|
|
+ c.index = static_cast<index_t>(coordsWithIndex.size());
|
|
|
+ coordsWithIndex.push_back(c);
|
|
|
+ }
|
|
|
+
|
|
|
+ trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 3));
|
|
|
+ trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 2));
|
|
|
+ trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 1));
|
|
|
+
|
|
|
+ char addData[2];
|
|
|
+ in.read(addData, 2);
|
|
|
+ STL_READER_COND_THROW(!in, "Error while parsing additional triangle data in binary stl file " << filename);
|
|
|
+ }
|
|
|
+
|
|
|
+ solidRangesOut.push_back(0);
|
|
|
+ solidRangesOut.push_back(static_cast<index_t> (trisOut.size() / 3));
|
|
|
+
|
|
|
+ RemoveDoubles (coordsOut, trisOut, coordsWithIndex);
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+inline bool StlFileHasASCIIFormat(const char* filename)
|
|
|
+{
|
|
|
+ using namespace std;
|
|
|
+ ifstream in(filename);
|
|
|
+ STL_READER_COND_THROW(!in, "Couldnt open file " << filename);
|
|
|
+
|
|
|
+ char chars [256];
|
|
|
+ in.read (chars, 256);
|
|
|
+ string buffer (chars, in.gcount());
|
|
|
+ transform(buffer.begin(), buffer.end(), buffer.begin(), ::tolower);
|
|
|
+ return buffer.find ("solid") != string::npos &&
|
|
|
+ buffer.find ("\n") != string::npos &&
|
|
|
+ buffer.find ("facet") != string::npos &&
|
|
|
+ buffer.find ("normal") != string::npos;
|
|
|
+}
|
|
|
+
|
|
|
+} // end of namespace stl_reader
|
|
|
+
|
|
|
+#endif //__H__STL_READER
|